#include "apex_update_listener.h" #include #include #include #include #include #include #include #include #include #undef LOG_TAG #define LOG_TAG "apex_update_listener" namespace { template 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, const std::set& 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::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(), *last_info); } return std::make_unique( Sigil{}, apex_name, apex_info_list_file_name, callback, file_descriptor, watch_descriptor, *last_info); } std::optional> 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 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 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_); }