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