diff options
Diffstat (limited to 'common/apex_update_listener/apex_update_listener.cc')
-rw-r--r-- | common/apex_update_listener/apex_update_listener.cc | 190 |
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_); +} |