blob: 24f38bc30a0429a393eda9337e866baf6e763f8a [file]
#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, const 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_);
}