summaryrefslogtreecommitdiff
path: root/common/apex_update_listener/apex_update_listener.cc
diff options
context:
space:
mode:
Diffstat (limited to 'common/apex_update_listener/apex_update_listener.cc')
-rw-r--r--common/apex_update_listener/apex_update_listener.cc190
1 files changed, 190 insertions, 0 deletions
diff --git a/common/apex_update_listener/apex_update_listener.cc b/common/apex_update_listener/apex_update_listener.cc
new file mode 100644
index 0000000..604f9d2
--- /dev/null
+++ b/common/apex_update_listener/apex_update_listener.cc
@@ -0,0 +1,190 @@
+#include "apex_update_listener.h"
+#include <log/log.h>
+#include <sys/inotify.h>
+#include <sys/types.h>
+#include <tinyxml2.h>
+#include <fstream>
+#include <sstream>
+#include <streambuf>
+#include <string>
+#include <vector>
+
+#undef LOG_TAG
+#define LOG_TAG "apex_update_listener"
+
+namespace {
+
+template <typename T>
+std::string ToString(const T& value) {
+ std::stringstream stream;
+ stream << value;
+ return stream.str();
+}
+
+} // namespace
+
+ApexUpdateListener::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)
+ : apex_name_(apex_name),
+ apex_info_list_file_name_(apex_info_list_file_name),
+ callback_function_(callback),
+ file_descriptor_(fd),
+ watch_descriptor_(wd),
+ last_info_(last_info),
+ thread_(&ApexUpdateListener::ThreadFunction, this) {
+}
+
+ApexUpdateListener::~ApexUpdateListener() {
+ running_ = false;
+ if (watch_descriptor_ >= 0) {
+ inotify_rm_watch(file_descriptor_, watch_descriptor_);
+ }
+ if (file_descriptor_ >= 0) {
+ close(file_descriptor_);
+ }
+ if (thread_.joinable()) {
+ thread_.join();
+ }
+}
+std::unique_ptr<ApexUpdateListener> ApexUpdateListener::Make(
+ const std::string& apex_name, CallbackFunction callback,
+ bool invoke_with_initial_version,
+ const std::string& apex_info_list_file_name) {
+ int file_descriptor = inotify_init();
+ if (file_descriptor < 0) {
+ ALOGE("Failed to inotify_init(): %s (%d)", strerror(errno), errno);
+ return nullptr;
+ }
+ int watch_descriptor = inotify_add_watch(
+ file_descriptor, std::string(apex_info_list_file_name).c_str(),
+ IN_MODIFY | IN_CREATE | IN_DELETE);
+ if (watch_descriptor < 0) {
+ ALOGE("Failed to inotify_add_watch(%s): %s (%d)",
+ std::string(apex_info_list_file_name).c_str(), strerror(errno), errno);
+ close(file_descriptor);
+ return nullptr;
+ }
+ if (!callback) {
+ ALOGE("Called with null callback");
+ return nullptr;
+ }
+ auto last_info = TrySlurpInfo(apex_name, apex_info_list_file_name);
+ if (!last_info.has_value()) {
+ ALOGE("Could not slurp info from %s for package %s",
+ std::string(apex_info_list_file_name).c_str(),
+ std::string(apex_name).c_str());
+ return nullptr;
+ }
+ if (invoke_with_initial_version) {
+ callback(std::set<Info>(), *last_info);
+ }
+
+ return std::make_unique<ApexUpdateListener>(
+ Sigil{}, apex_name, apex_info_list_file_name, callback, file_descriptor,
+ watch_descriptor, *last_info);
+}
+
+std::optional<std::set<ApexUpdateListener::Info>>
+ApexUpdateListener::TrySlurpInfo(const std::string& apex_name,
+ const std::string& apex_info_list_file_name) {
+ tinyxml2::XMLDocument xml_doc;
+ auto status = xml_doc.LoadFile(apex_info_list_file_name.c_str());
+ if (status != tinyxml2::XML_SUCCESS) {
+ ALOGE("XML parsing failed: %d", status);
+ return std::nullopt;
+ }
+ tinyxml2::XMLElement* apex_info_list =
+ xml_doc.FirstChildElement("apex-info-list");
+ if (!apex_info_list) {
+ ALOGE("XML did not contain apex-info-list");
+ return std::nullopt;
+ }
+ std::set<ApexUpdateListener::Info> ret;
+ for (tinyxml2::XMLElement* apex_info =
+ apex_info_list->FirstChildElement("apex-info");
+ apex_info != nullptr;
+ apex_info = apex_info->NextSiblingElement("apex-info")) {
+ if (apex_info->Attribute("moduleName", apex_name.c_str())) {
+ Info info{.module_name = apex_name.c_str()};
+ auto module_path = apex_info->Attribute("modulePath");
+ auto preinstalled_module_path =
+ apex_info->Attribute("preinstalledModulePath");
+ auto version_code = apex_info->Attribute("versionCode");
+ auto version_name = apex_info->Attribute("versionName");
+ auto is_active = apex_info->Attribute("isActive");
+ auto is_factory = apex_info->Attribute("isFactory");
+ if (module_path) {
+ info.module_path = module_path;
+ }
+ if (preinstalled_module_path) {
+ info.preinstalled_module_path = preinstalled_module_path;
+ }
+ if (version_code) {
+ info.version_code = std::strtol(version_code, nullptr, 10);
+ }
+ if (version_name) {
+ info.version_name = version_name;
+ }
+ if (is_active) {
+ info.is_active = !strcmp(is_active, "true");
+ }
+ if (is_factory) {
+ info.is_factory = !strcmp(is_factory, "true");
+ }
+ ret.insert(info);
+ }
+ }
+ if (ret.size()) {
+ return ret;
+ }
+ ALOGE("XML did not contain any apex-info about %s", apex_name.c_str());
+ return std::nullopt;
+}
+
+void ApexUpdateListener::ThreadFunction() {
+ // Maximum number of events to read at a time
+ constexpr int event_number = 16;
+ std::vector<struct inotify_event> events(event_number);
+ do {
+ auto length = read(file_descriptor_, events.data(),
+ event_number * sizeof(inotify_event));
+ if (length == -EINTR) {
+ continue; // Interrupted by signal, try again
+ }
+ if (length < 0 || !running_) {
+ if (running_) {
+ ALOGE("Error reading inotify event from '%s': %s (%d)",
+ apex_info_list_file_name_.c_str(), strerror(errno), errno);
+ }
+ return;
+ }
+
+ for (size_t i = 0; i < length / sizeof(inotify_event); i++) {
+ struct inotify_event& event = events[i];
+
+ if (event.mask & IN_CREATE) {
+ ALOGI("%s created as %s", apex_info_list_file_name_.c_str(),
+ (event.mask & IN_ISDIR) ? "directory" : "file");
+ } else if (event.mask & IN_DELETE) {
+ ALOGI("%s deleted as %s", apex_info_list_file_name_.c_str(),
+ (event.mask & IN_ISDIR) ? "directory" : "file");
+ } else if (event.mask & IN_MODIFY) {
+ ALOGI("%s modified as %s", apex_info_list_file_name_.c_str(),
+ (event.mask & IN_ISDIR) ? "directory" : "file");
+ }
+ // We don't really care how it was updated as long as it wasn't deleted
+ if (event.mask & IN_DELETE) {
+ continue;
+ }
+ auto info = TrySlurpInfo(apex_name_, apex_info_list_file_name_);
+ if (info.has_value()) {
+ if (*info != last_info_ && callback_function_) {
+ callback_function_(last_info_, *info);
+ last_info_ = *info;
+ }
+ }
+ }
+ } while (running_);
+}