1 #include "apex_update_listener.h"
2 #include <log/log.h>
3 #include <sys/inotify.h>
4 #include <sys/types.h>
5 #include <tinyxml2.h>
6 #include <fstream>
7 #include <sstream>
8 #include <streambuf>
9 #include <string>
10 #include <vector>
11
12 #undef LOG_TAG
13 #define LOG_TAG "apex_update_listener"
14
15 namespace {
16
17 template <typename T>
ToString(const T & value)18 std::string ToString(const T& value) {
19 std::stringstream stream;
20 stream << value;
21 return stream.str();
22 }
23
24 } // namespace
25
ApexUpdateListener(ApexUpdateListener::Sigil,const std::string & apex_name,const std::string & apex_info_list_file_name,CallbackFunction callback,int fd,int wd,std::set<Info> last_info)26 ApexUpdateListener::ApexUpdateListener(
27 ApexUpdateListener::Sigil, const std::string& apex_name,
28 const std::string& apex_info_list_file_name, CallbackFunction callback,
29 int fd, int wd, std::set<Info> last_info)
30 : apex_name_(apex_name),
31 apex_info_list_file_name_(apex_info_list_file_name),
32 callback_function_(callback),
33 file_descriptor_(fd),
34 watch_descriptor_(wd),
35 last_info_(last_info),
36 thread_(&ApexUpdateListener::ThreadFunction, this) {
37 }
38
~ApexUpdateListener()39 ApexUpdateListener::~ApexUpdateListener() {
40 running_ = false;
41 if (watch_descriptor_ >= 0) {
42 inotify_rm_watch(file_descriptor_, watch_descriptor_);
43 }
44 if (file_descriptor_ >= 0) {
45 close(file_descriptor_);
46 }
47 if (thread_.joinable()) {
48 thread_.join();
49 }
50 }
Make(const std::string & apex_name,CallbackFunction callback,bool invoke_with_initial_version,const std::string & apex_info_list_file_name)51 std::unique_ptr<ApexUpdateListener> ApexUpdateListener::Make(
52 const std::string& apex_name, CallbackFunction callback,
53 bool invoke_with_initial_version,
54 const std::string& apex_info_list_file_name) {
55 int file_descriptor = inotify_init();
56 if (file_descriptor < 0) {
57 ALOGE("Failed to inotify_init(): %s (%d)", strerror(errno), errno);
58 return nullptr;
59 }
60 int watch_descriptor = inotify_add_watch(
61 file_descriptor, std::string(apex_info_list_file_name).c_str(),
62 IN_MODIFY | IN_CREATE | IN_DELETE);
63 if (watch_descriptor < 0) {
64 ALOGE("Failed to inotify_add_watch(%s): %s (%d)",
65 std::string(apex_info_list_file_name).c_str(), strerror(errno), errno);
66 close(file_descriptor);
67 return nullptr;
68 }
69 if (!callback) {
70 ALOGE("Called with null callback");
71 return nullptr;
72 }
73 auto last_info = TrySlurpInfo(apex_name, apex_info_list_file_name);
74 if (!last_info.has_value()) {
75 ALOGE("Could not slurp info from %s for package %s",
76 std::string(apex_info_list_file_name).c_str(),
77 std::string(apex_name).c_str());
78 return nullptr;
79 }
80 if (invoke_with_initial_version) {
81 callback(std::set<Info>(), *last_info);
82 }
83
84 return std::make_unique<ApexUpdateListener>(
85 Sigil{}, apex_name, apex_info_list_file_name, callback, file_descriptor,
86 watch_descriptor, *last_info);
87 }
88
89 std::optional<std::set<ApexUpdateListener::Info>>
TrySlurpInfo(const std::string & apex_name,const std::string & apex_info_list_file_name)90 ApexUpdateListener::TrySlurpInfo(const std::string& apex_name,
91 const std::string& apex_info_list_file_name) {
92 tinyxml2::XMLDocument xml_doc;
93 auto status = xml_doc.LoadFile(apex_info_list_file_name.c_str());
94 if (status != tinyxml2::XML_SUCCESS) {
95 ALOGE("XML parsing failed: %d", status);
96 return std::nullopt;
97 }
98 tinyxml2::XMLElement* apex_info_list =
99 xml_doc.FirstChildElement("apex-info-list");
100 if (!apex_info_list) {
101 ALOGE("XML did not contain apex-info-list");
102 return std::nullopt;
103 }
104 std::set<ApexUpdateListener::Info> ret;
105 for (tinyxml2::XMLElement* apex_info =
106 apex_info_list->FirstChildElement("apex-info");
107 apex_info != nullptr;
108 apex_info = apex_info->NextSiblingElement("apex-info")) {
109 if (apex_info->Attribute("moduleName", apex_name.c_str())) {
110 Info info{.module_name = apex_name.c_str()};
111 auto module_path = apex_info->Attribute("modulePath");
112 auto preinstalled_module_path =
113 apex_info->Attribute("preinstalledModulePath");
114 auto version_code = apex_info->Attribute("versionCode");
115 auto version_name = apex_info->Attribute("versionName");
116 auto is_active = apex_info->Attribute("isActive");
117 auto is_factory = apex_info->Attribute("isFactory");
118 if (module_path) {
119 info.module_path = module_path;
120 }
121 if (preinstalled_module_path) {
122 info.preinstalled_module_path = preinstalled_module_path;
123 }
124 if (version_code) {
125 info.version_code = std::strtol(version_code, nullptr, 10);
126 }
127 if (version_name) {
128 info.version_name = version_name;
129 }
130 if (is_active) {
131 info.is_active = !strcmp(is_active, "true");
132 }
133 if (is_factory) {
134 info.is_factory = !strcmp(is_factory, "true");
135 }
136 ret.insert(info);
137 }
138 }
139 if (ret.size()) {
140 return ret;
141 }
142 ALOGE("XML did not contain any apex-info about %s", apex_name.c_str());
143 return std::nullopt;
144 }
145
ThreadFunction()146 void ApexUpdateListener::ThreadFunction() {
147 // Maximum number of events to read at a time
148 constexpr int event_number = 16;
149 std::vector<struct inotify_event> events(event_number);
150 do {
151 auto length = read(file_descriptor_, events.data(),
152 event_number * sizeof(inotify_event));
153 if (length == -EINTR) {
154 continue; // Interrupted by signal, try again
155 }
156 if (length < 0 || !running_) {
157 if (running_) {
158 ALOGE("Error reading inotify event from '%s': %s (%d)",
159 apex_info_list_file_name_.c_str(), strerror(errno), errno);
160 }
161 return;
162 }
163
164 for (size_t i = 0; i < length / sizeof(inotify_event); i++) {
165 struct inotify_event& event = events[i];
166
167 if (event.mask & IN_CREATE) {
168 ALOGI("%s created as %s", apex_info_list_file_name_.c_str(),
169 (event.mask & IN_ISDIR) ? "directory" : "file");
170 } else if (event.mask & IN_DELETE) {
171 ALOGI("%s deleted as %s", apex_info_list_file_name_.c_str(),
172 (event.mask & IN_ISDIR) ? "directory" : "file");
173 } else if (event.mask & IN_MODIFY) {
174 ALOGI("%s modified as %s", apex_info_list_file_name_.c_str(),
175 (event.mask & IN_ISDIR) ? "directory" : "file");
176 }
177 // We don't really care how it was updated as long as it wasn't deleted
178 if (event.mask & IN_DELETE) {
179 continue;
180 }
181 auto info = TrySlurpInfo(apex_name_, apex_info_list_file_name_);
182 if (info.has_value()) {
183 if (*info != last_info_ && callback_function_) {
184 callback_function_(last_info_, *info);
185 last_info_ = *info;
186 }
187 }
188 }
189 } while (running_);
190 }
191