diff --git a/init/Android.bp b/init/Android.bp index 80d5c429a..af1e9d375 100644 --- a/init/Android.bp +++ b/init/Android.bp @@ -66,11 +66,14 @@ cc_library_static { "capabilities.cpp", "descriptors.cpp", "devices.cpp", + "firmware_handler.cpp", "import_parser.cpp", "init_parser.cpp", "log.cpp", "parser.cpp", "service.cpp", + "uevent_listener.cpp", + "ueventd_parser.cpp", "util.cpp", ], whole_static_libs: ["libcap"], diff --git a/init/devices.cpp b/init/devices.cpp index e0351a34b..40f9740cc 100644 --- a/init/devices.cpp +++ b/init/devices.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2014 The Android Open Source Project + * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,42 +16,20 @@ #include "devices.h" -#include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include #include -#include #include -#include -#include #include #include #include -#include -#include #include #include -#include #include -#include "keyword_map.h" #include "ueventd.h" #include "util.h" @@ -59,302 +37,10 @@ #error "Do not include init.h in files used by ueventd or watchdogd; it will expose init's globals" #endif -static selabel_handle* sehandle; - -static android::base::unique_fd device_fd; - -Permissions::Permissions(const std::string& name, mode_t perm, uid_t uid, gid_t gid) - : name_(name), perm_(perm), uid_(uid), gid_(gid), prefix_(false), wildcard_(false) { - // If the first * is the last character, then we'll treat name_ as a prefix - // Otherwise, if a * is present, then we do a full fnmatch(). - auto wildcard_position = name_.find('*'); - if (wildcard_position == name_.length() - 1) { - prefix_ = true; - name_.pop_back(); - } else if (wildcard_position != std::string::npos) { - wildcard_ = true; - } -} - -bool Permissions::Match(const std::string& path) const { - if (prefix_) { - return android::base::StartsWith(path, name_.c_str()); - } else if (wildcard_) { - return fnmatch(name_.c_str(), path.c_str(), FNM_PATHNAME) == 0; - } else { - return path == name_; - } - - return false; -} - -bool SysfsPermissions::MatchWithSubsystem(const std::string& path, - const std::string& subsystem) const { - std::string path_basename = android::base::Basename(path); - if (name().find(subsystem) != std::string::npos) { - if (Match("/sys/class/" + subsystem + "/" + path_basename)) return true; - if (Match("/sys/bus/" + subsystem + "/devices/" + path_basename)) return true; - } - return Match(path); -} - -void SysfsPermissions::SetPermissions(const std::string& path) const { - std::string attribute_file = path + "/" + attribute_; - LOG(INFO) << "fixup " << attribute_file << " " << uid() << " " << gid() << " " << std::oct - << perm(); - chown(attribute_file.c_str(), uid(), gid()); - chmod(attribute_file.c_str(), perm()); -} - -// TODO: Move these to be member variables of a future devices class. -std::vector dev_permissions; -std::vector sysfs_permissions; - -bool ParsePermissionsLine(std::vector&& args, std::string* err, bool is_sysfs) { - if (is_sysfs && args.size() != 5) { - *err = "/sys/ lines must have 5 entries"; - return false; - } - - if (!is_sysfs && args.size() != 4) { - *err = "/dev/ lines must have 4 entries"; - return false; - } - - auto it = args.begin(); - const std::string& name = *it++; - - std::string sysfs_attribute; - if (is_sysfs) sysfs_attribute = *it++; - - // args is now common to both sys and dev entries and contains: - std::string& perm_string = *it++; - char* end_pointer = 0; - mode_t perm = strtol(perm_string.c_str(), &end_pointer, 8); - if (end_pointer == nullptr || *end_pointer != '\0') { - *err = "invalid mode '" + perm_string + "'"; - return false; - } - - std::string& uid_string = *it++; - passwd* pwd = getpwnam(uid_string.c_str()); - if (!pwd) { - *err = "invalid uid '" + uid_string + "'"; - return false; - } - uid_t uid = pwd->pw_uid; - - std::string& gid_string = *it++; - struct group* grp = getgrnam(gid_string.c_str()); - if (!grp) { - *err = "invalid gid '" + gid_string + "'"; - return false; - } - gid_t gid = grp->gr_gid; - - if (is_sysfs) { - sysfs_permissions.emplace_back(name, sysfs_attribute, perm, uid, gid); - } else { - dev_permissions.emplace_back(name, perm, uid, gid); - } - return true; -} - -// TODO: Move this to be a member variable of a future devices class. -static std::vector subsystems; - -std::string Subsystem::ParseDevPath(uevent* uevent) const { - std::string devname = devname_source_ == DevnameSource::DEVNAME_UEVENT_DEVNAME - ? uevent->device_name - : android::base::Basename(uevent->path); - - return dir_name_ + "/" + devname; -} - -bool SubsystemParser::ParseSection(std::vector&& args, const std::string& filename, - int line, std::string* err) { - if (args.size() != 2) { - *err = "subsystems must have exactly one name"; - return false; - } - - if (std::find(subsystems.begin(), subsystems.end(), args[1]) != subsystems.end()) { - *err = "ignoring duplicate subsystem entry"; - return false; - } - - subsystem_.name_ = args[1]; - - return true; -} - -bool SubsystemParser::ParseDevName(std::vector&& args, std::string* err) { - if (args[1] == "uevent_devname") { - subsystem_.devname_source_ = Subsystem::DevnameSource::DEVNAME_UEVENT_DEVNAME; - return true; - } - if (args[1] == "uevent_devpath") { - subsystem_.devname_source_ = Subsystem::DevnameSource::DEVNAME_UEVENT_DEVPATH; - return true; - } - - *err = "invalid devname '" + args[1] + "'"; - return false; -} - -bool SubsystemParser::ParseDirName(std::vector&& args, std::string* err) { - if (args[1].front() != '/') { - *err = "dirname '" + args[1] + " ' does not start with '/'"; - return false; - } - - subsystem_.dir_name_ = args[1]; - return true; -} - -bool SubsystemParser::ParseLineSection(std::vector&& args, int line, std::string* err) { - using OptionParser = - bool (SubsystemParser::*)(std::vector && args, std::string * err); - static class OptionParserMap : public KeywordMap { - private: - const Map& map() const override { - // clang-format off - static const Map option_parsers = { - {"devname", {1, 1, &SubsystemParser::ParseDevName}}, - {"dirname", {1, 1, &SubsystemParser::ParseDirName}}, - }; - // clang-format on - return option_parsers; - } - } parser_map; - - auto parser = parser_map.FindFunction(args, err); - - if (!parser) { - return false; - } - - return (this->*parser)(std::move(args), err); -} - -void SubsystemParser::EndSection() { - subsystems.emplace_back(std::move(subsystem_)); -} - -static void fixup_sys_permissions(const std::string& upath, const std::string& subsystem) { - // upaths omit the "/sys" that paths in this list - // contain, so we prepend it... - std::string path = "/sys" + upath; - - for (const auto& s : sysfs_permissions) { - if (s.MatchWithSubsystem(path, subsystem)) s.SetPermissions(path); - } - - if (access(path.c_str(), F_OK) == 0) { - LOG(VERBOSE) << "restorecon_recursive: " << path; - selinux_android_restorecon(path.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE); - } -} - -static std::tuple get_device_permissions( - const std::string& path, const std::vector& links) { - // Search the perms list in reverse so that ueventd.$hardware can override ueventd.rc. - for (auto it = dev_permissions.rbegin(); it != dev_permissions.rend(); ++it) { - if (it->Match(path) || std::any_of(links.begin(), links.end(), - [it](const auto& link) { return it->Match(link); })) { - return {it->perm(), it->uid(), it->gid()}; - } - } - /* Default if nothing found. */ - return {0600, 0, 0}; -} - -static void make_device(const std::string& path, int block, int major, int minor, - const std::vector& links) { - dev_t dev; - char *secontext = NULL; - - auto [mode, uid, gid] = get_device_permissions(path, links); - mode |= (block ? S_IFBLK : S_IFCHR); - - if (sehandle) { - std::vector c_links; - for (const auto& link : links) { - c_links.emplace_back(link.c_str()); - } - c_links.emplace_back(nullptr); - if (selabel_lookup_best_match(sehandle, &secontext, path.c_str(), &c_links[0], mode)) { - PLOG(ERROR) << "Device '" << path << "' not created; cannot find SELinux label"; - return; - } - setfscreatecon(secontext); - } - - dev = makedev(major, minor); - /* Temporarily change egid to avoid race condition setting the gid of the - * device node. Unforunately changing the euid would prevent creation of - * some device nodes, so the uid has to be set with chown() and is still - * racy. Fixing the gid race at least fixed the issue with system_server - * opening dynamic input devices under the AID_INPUT gid. */ - if (setegid(gid)) { - PLOG(ERROR) << "setegid(" << gid << ") for " << path << " device failed"; - goto out; - } - /* If the node already exists update its SELinux label to handle cases when - * it was created with the wrong context during coldboot procedure. */ - if (mknod(path.c_str(), mode, dev) && (errno == EEXIST) && secontext) { - char* fcon = nullptr; - int rc = lgetfilecon(path.c_str(), &fcon); - if (rc < 0) { - PLOG(ERROR) << "Cannot get SELinux label on '" << path << "' device"; - goto out; - } - - bool different = strcmp(fcon, secontext) != 0; - freecon(fcon); - - if (different && lsetfilecon(path.c_str(), secontext)) { - PLOG(ERROR) << "Cannot set '" << secontext << "' SELinux label on '" << path << "' device"; - } - } - -out: - chown(path.c_str(), uid, -1); - if (setegid(AID_ROOT)) { - PLOG(FATAL) << "setegid(AID_ROOT) failed"; - } - - if (secontext) { - freecon(secontext); - setfscreatecon(NULL); - } -} - -// TODO: Move this to be a member variable of a future devices class. -std::vector platform_devices; - -// Given a path that may start with a platform device, find the length of the -// platform device prefix. If it doesn't start with a platform device, return false -bool find_platform_device(const std::string& path, std::string* out_path) { - out_path->clear(); - // platform_devices is searched backwards, since parents are added before their children, - // and we want to match as deep of a child as we can. - for (auto it = platform_devices.rbegin(); it != platform_devices.rend(); ++it) { - auto platform_device_path_length = it->length(); - if (platform_device_path_length < path.length() && - path[platform_device_path_length] == '/' && - android::base::StartsWith(path, it->c_str())) { - *out_path = *it; - return true; - } - } - return false; -} - /* Given a path that may start with a PCI device, populate the supplied buffer * with the PCI domain/bus number and the peripheral ID and return 0. * If it doesn't start with a PCI device, or there is some error, return -1 */ -static bool find_pci_device_prefix(const std::string& path, std::string* result) { +static bool FindPciDevicePrefix(const std::string& path, std::string* result) { result->clear(); if (!android::base::StartsWith(path, "/devices/pci")) return false; @@ -385,7 +71,7 @@ static bool find_pci_device_prefix(const std::string& path, std::string* result) * the supplied buffer with the virtual block device ID and return 0. * If it doesn't start with a virtual block device, or there is some * error, return -1 */ -static bool find_vbd_device_prefix(const std::string& path, std::string* result) { +static bool FindVbdDevicePrefix(const std::string& path, std::string* result) { result->clear(); if (!android::base::StartsWith(path, "/devices/vbd-")) return false; @@ -405,59 +91,177 @@ static bool find_vbd_device_prefix(const std::string& path, std::string* result) return true; } -void parse_event(const char* msg, uevent* uevent) { - uevent->partition_num = -1; - uevent->major = -1; - uevent->minor = -1; - // currently ignoring SEQNUM - while(*msg) { - if(!strncmp(msg, "ACTION=", 7)) { - msg += 7; - uevent->action = msg; - } else if(!strncmp(msg, "DEVPATH=", 8)) { - msg += 8; - uevent->path = msg; - } else if(!strncmp(msg, "SUBSYSTEM=", 10)) { - msg += 10; - uevent->subsystem = msg; - } else if(!strncmp(msg, "FIRMWARE=", 9)) { - msg += 9; - uevent->firmware = msg; - } else if(!strncmp(msg, "MAJOR=", 6)) { - msg += 6; - uevent->major = atoi(msg); - } else if(!strncmp(msg, "MINOR=", 6)) { - msg += 6; - uevent->minor = atoi(msg); - } else if(!strncmp(msg, "PARTN=", 6)) { - msg += 6; - uevent->partition_num = atoi(msg); - } else if(!strncmp(msg, "PARTNAME=", 9)) { - msg += 9; - uevent->partition_name = msg; - } else if(!strncmp(msg, "DEVNAME=", 8)) { - msg += 8; - uevent->device_name = msg; +Permissions::Permissions(const std::string& name, mode_t perm, uid_t uid, gid_t gid) + : name_(name), perm_(perm), uid_(uid), gid_(gid), prefix_(false), wildcard_(false) { + // Set 'prefix_' or 'wildcard_' based on the below cases: + // + // 1) No '*' in 'name' -> Neither are set and Match() checks a given path for strict + // equality with 'name' + // + // 2) '*' only appears as the last character in 'name' -> 'prefix'_ is set to true and + // Match() checks if 'name' is a prefix of a given path. + // + // 3) '*' appears elsewhere -> 'wildcard_' is set to true and Match() uses fnmatch() + // with FNM_PATHNAME to compare 'name' to a given path. + + auto wildcard_position = name_.find('*'); + if (wildcard_position != std::string::npos) { + if (wildcard_position == name_.length() - 1) { + prefix_ = true; + name_.pop_back(); + } else { + wildcard_ = true; } - - // advance to after the next \0 - while(*msg++) - ; - } - - if (LOG_UEVENTS) { - LOG(INFO) << "event { '" << uevent->action << "', '" << uevent->path << "', '" - << uevent->subsystem << "', '" << uevent->firmware << "', " << uevent->major - << ", " << uevent->minor << " }"; } } -std::vector get_character_device_symlinks(uevent* uevent) { +bool Permissions::Match(const std::string& path) const { + if (prefix_) return android::base::StartsWith(path, name_.c_str()); + if (wildcard_) return fnmatch(name_.c_str(), path.c_str(), FNM_PATHNAME) == 0; + return path == name_; +} + +bool SysfsPermissions::MatchWithSubsystem(const std::string& path, + const std::string& subsystem) const { + std::string path_basename = android::base::Basename(path); + if (name().find(subsystem) != std::string::npos) { + if (Match("/sys/class/" + subsystem + "/" + path_basename)) return true; + if (Match("/sys/bus/" + subsystem + "/devices/" + path_basename)) return true; + } + return Match(path); +} + +void SysfsPermissions::SetPermissions(const std::string& path) const { + std::string attribute_file = path + "/" + attribute_; + LOG(VERBOSE) << "fixup " << attribute_file << " " << uid() << " " << gid() << " " << std::oct + << perm(); + + if (access(attribute_file.c_str(), F_OK) == 0) { + if (chown(attribute_file.c_str(), uid(), gid()) != 0) { + PLOG(ERROR) << "chown(" << attribute_file << ", " << uid() << ", " << gid() + << ") failed"; + } + if (chmod(attribute_file.c_str(), perm()) != 0) { + PLOG(ERROR) << "chmod(" << attribute_file << ", " << perm() << ") failed"; + } + } +} + +// Given a path that may start with a platform device, find the length of the +// platform device prefix. If it doesn't start with a platform device, return false +bool PlatformDeviceList::Find(const std::string& path, std::string* out_path) const { + out_path->clear(); + // platform_devices is searched backwards, since parents are added before their children, + // and we want to match as deep of a child as we can. + for (auto it = platform_devices_.crbegin(); it != platform_devices_.crend(); ++it) { + auto platform_device_path_length = it->length(); + if (platform_device_path_length < path.length() && + path[platform_device_path_length] == '/' && + android::base::StartsWith(path, it->c_str())) { + *out_path = *it; + return true; + } + } + return false; +} + +void DeviceHandler::FixupSysPermissions(const std::string& upath, + const std::string& subsystem) const { + // upaths omit the "/sys" that paths in this list + // contain, so we prepend it... + std::string path = "/sys" + upath; + + for (const auto& s : sysfs_permissions_) { + if (s.MatchWithSubsystem(path, subsystem)) s.SetPermissions(path); + } + + if (access(path.c_str(), F_OK) == 0) { + LOG(VERBOSE) << "restorecon_recursive: " << path; + if (selinux_android_restorecon(path.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE) != 0) { + PLOG(ERROR) << "selinux_android_restorecon(" << path << ") failed"; + } + } +} + +std::tuple DeviceHandler::GetDevicePermissions( + const std::string& path, const std::vector& links) const { + // Search the perms list in reverse so that ueventd.$hardware can override ueventd.rc. + for (auto it = dev_permissions_.crbegin(); it != dev_permissions_.crend(); ++it) { + if (it->Match(path) || std::any_of(links.cbegin(), links.cend(), + [it](const auto& link) { return it->Match(link); })) { + return {it->perm(), it->uid(), it->gid()}; + } + } + /* Default if nothing found. */ + return {0600, 0, 0}; +} + +void DeviceHandler::MakeDevice(const std::string& path, int block, int major, int minor, + const std::vector& links) const { + auto[mode, uid, gid] = GetDevicePermissions(path, links); + mode |= (block ? S_IFBLK : S_IFCHR); + + char* secontext = nullptr; + if (sehandle_) { + std::vector c_links; + for (const auto& link : links) { + c_links.emplace_back(link.c_str()); + } + c_links.emplace_back(nullptr); + if (selabel_lookup_best_match(sehandle_, &secontext, path.c_str(), &c_links[0], mode)) { + PLOG(ERROR) << "Device '" << path << "' not created; cannot find SELinux label"; + return; + } + setfscreatecon(secontext); + } + + dev_t dev = makedev(major, minor); + /* Temporarily change egid to avoid race condition setting the gid of the + * device node. Unforunately changing the euid would prevent creation of + * some device nodes, so the uid has to be set with chown() and is still + * racy. Fixing the gid race at least fixed the issue with system_server + * opening dynamic input devices under the AID_INPUT gid. */ + if (setegid(gid)) { + PLOG(ERROR) << "setegid(" << gid << ") for " << path << " device failed"; + goto out; + } + /* If the node already exists update its SELinux label to handle cases when + * it was created with the wrong context during coldboot procedure. */ + if (mknod(path.c_str(), mode, dev) && (errno == EEXIST) && secontext) { + char* fcon = nullptr; + int rc = lgetfilecon(path.c_str(), &fcon); + if (rc < 0) { + PLOG(ERROR) << "Cannot get SELinux label on '" << path << "' device"; + goto out; + } + + bool different = strcmp(fcon, secontext) != 0; + freecon(fcon); + + if (different && lsetfilecon(path.c_str(), secontext)) { + PLOG(ERROR) << "Cannot set '" << secontext << "' SELinux label on '" << path + << "' device"; + } + } + +out: + chown(path.c_str(), uid, -1); + if (setegid(AID_ROOT)) { + PLOG(FATAL) << "setegid(AID_ROOT) failed"; + } + + if (secontext) { + freecon(secontext); + setfscreatecon(nullptr); + } +} + +std::vector DeviceHandler::GetCharacterDeviceSymlinks(const Uevent& uevent) const { std::string parent_device; - if (!find_platform_device(uevent->path, &parent_device)) return {}; + if (!platform_devices_.Find(uevent.path, &parent_device)) return {}; // skip path to the parent driver - std::string path = uevent->path.substr(parent_device.length()); + std::string path = uevent.path.substr(parent_device.length()); if (!android::base::StartsWith(path, "/usb")) return {}; @@ -484,7 +288,7 @@ std::vector get_character_device_symlinks(uevent* uevent) { auto name_string = path.substr(start, length); std::vector links; - links.emplace_back("/dev/usb/" + uevent->subsystem + name_string); + links.emplace_back("/dev/usb/" + uevent.subsystem + name_string); mkdir("/dev/usb", 0755); @@ -493,7 +297,7 @@ std::vector get_character_device_symlinks(uevent* uevent) { // replaces any unacceptable characters with '_', the // length of the resulting string is equal to the input string -void sanitize_partition_name(std::string* string) { +void SanitizePartitionName(std::string* string) { const char* accept = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -508,11 +312,11 @@ void sanitize_partition_name(std::string* string) { } } -std::vector get_block_device_symlinks(uevent* uevent) { +std::vector DeviceHandler::GetBlockDeviceSymlinks(const Uevent& uevent) const { std::string device; std::string type; - if (find_platform_device(uevent->path, &device)) { + if (platform_devices_.Find(uevent.path, &device)) { // Skip /devices/platform or /devices/ if present static const std::string devices_platform_prefix = "/devices/platform/"; static const std::string devices_prefix = "/devices/"; @@ -524,9 +328,9 @@ std::vector get_block_device_symlinks(uevent* uevent) { } type = "platform"; - } else if (find_pci_device_prefix(uevent->path, &device)) { + } else if (FindPciDevicePrefix(uevent.path, &device)) { type = "pci"; - } else if (find_vbd_device_prefix(uevent->path, &device)) { + } else if (FindVbdDevicePrefix(uevent.path, &device)) { type = "vbd"; } else { return {}; @@ -538,379 +342,137 @@ std::vector get_block_device_symlinks(uevent* uevent) { auto link_path = "/dev/block/" + type + "/" + device; - if (!uevent->partition_name.empty()) { - std::string partition_name_sanitized(uevent->partition_name); - sanitize_partition_name(&partition_name_sanitized); - if (partition_name_sanitized != uevent->partition_name) { - LOG(VERBOSE) << "Linking partition '" << uevent->partition_name << "' as '" + if (!uevent.partition_name.empty()) { + std::string partition_name_sanitized(uevent.partition_name); + SanitizePartitionName(&partition_name_sanitized); + if (partition_name_sanitized != uevent.partition_name) { + LOG(VERBOSE) << "Linking partition '" << uevent.partition_name << "' as '" << partition_name_sanitized << "'"; } links.emplace_back(link_path + "/by-name/" + partition_name_sanitized); } - if (uevent->partition_num >= 0) { - links.emplace_back(link_path + "/by-num/p" + std::to_string(uevent->partition_num)); + if (uevent.partition_num >= 0) { + links.emplace_back(link_path + "/by-num/p" + std::to_string(uevent.partition_num)); } - auto last_slash = uevent->path.rfind('/'); - links.emplace_back(link_path + "/" + uevent->path.substr(last_slash + 1)); + auto last_slash = uevent.path.rfind('/'); + links.emplace_back(link_path + "/" + uevent.path.substr(last_slash + 1)); return links; } -static void make_link_init(const std::string& oldpath, const std::string& newpath) { - if (mkdir_recursive(dirname(newpath.c_str()), 0755, sehandle)) { - PLOG(ERROR) << "Failed to create directory " << dirname(newpath.c_str()); - } - - if (symlink(oldpath.c_str(), newpath.c_str()) && errno != EEXIST) { - PLOG(ERROR) << "Failed to symlink " << oldpath << " to " << newpath; - } -} - -static void remove_link(const std::string& oldpath, const std::string& newpath) { - std::string path; - if (android::base::Readlink(newpath, &path) && path == oldpath) unlink(newpath.c_str()); -} - -static void handle_device(const std::string& action, const std::string& devpath, int block, - int major, int minor, const std::vector& links) { +void DeviceHandler::HandleDevice(const std::string& action, const std::string& devpath, int block, + int major, int minor, const std::vector& links) const { if (action == "add") { - make_device(devpath, block, major, minor, links); + MakeDevice(devpath, block, major, minor, links); for (const auto& link : links) { - make_link_init(devpath, link); + if (mkdir_recursive(android::base::Dirname(link), 0755, sehandle_)) { + PLOG(ERROR) << "Failed to create directory " << android::base::Dirname(link); + } + + if (symlink(devpath.c_str(), link.c_str()) && errno != EEXIST) { + PLOG(ERROR) << "Failed to symlink " << devpath << " to " << link; + } } } if (action == "remove") { for (const auto& link : links) { - remove_link(devpath, link); + std::string link_path; + if (android::base::Readlink(link, &link_path) && link_path == devpath) { + unlink(link.c_str()); + } } unlink(devpath.c_str()); } } -void handle_platform_device_event(uevent* uevent) { - if (uevent->action == "add") { - platform_devices.emplace_back(uevent->path); - } else if (uevent->action == "remove") { - auto it = std::find(platform_devices.begin(), platform_devices.end(), uevent->path); - if (it != platform_devices.end()) platform_devices.erase(it); +void DeviceHandler::HandlePlatformDeviceEvent(const Uevent& uevent) { + if (uevent.action == "add") { + platform_devices_.Add(uevent.path); + } else if (uevent.action == "remove") { + platform_devices_.Remove(uevent.path); } } -static void handle_block_device_event(uevent* uevent) { +void DeviceHandler::HandleBlockDeviceEvent(const Uevent& uevent) const { // if it's not a /dev device, nothing to do - if (uevent->major < 0 || uevent->minor < 0) return; + if (uevent.major < 0 || uevent.minor < 0) return; const char* base = "/dev/block/"; - make_dir(base, 0755, sehandle); + make_dir(base, 0755, sehandle_); - std::string name = android::base::Basename(uevent->path); + std::string name = android::base::Basename(uevent.path); std::string devpath = base + name; std::vector links; - if (android::base::StartsWith(uevent->path, "/devices")) { - links = get_block_device_symlinks(uevent); + if (android::base::StartsWith(uevent.path, "/devices")) { + links = GetBlockDeviceSymlinks(uevent); } - handle_device(uevent->action, devpath, 1, uevent->major, uevent->minor, links); + HandleDevice(uevent.action, devpath, 1, uevent.major, uevent.minor, links); } -static void handle_generic_device_event(uevent* uevent) { +void DeviceHandler::HandleGenericDeviceEvent(const Uevent& uevent) const { // if it's not a /dev device, nothing to do - if (uevent->major < 0 || uevent->minor < 0) return; + if (uevent.major < 0 || uevent.minor < 0) return; std::string devpath; - if (android::base::StartsWith(uevent->subsystem, "usb")) { - if (uevent->subsystem == "usb") { - if (!uevent->device_name.empty()) { - devpath = "/dev/" + uevent->device_name; + if (android::base::StartsWith(uevent.subsystem, "usb")) { + if (uevent.subsystem == "usb") { + if (!uevent.device_name.empty()) { + devpath = "/dev/" + uevent.device_name; } else { // This imitates the file system that would be created // if we were using devfs instead. // Minors are broken up into groups of 128, starting at "001" - int bus_id = uevent->minor / 128 + 1; - int device_id = uevent->minor % 128 + 1; + int bus_id = uevent.minor / 128 + 1; + int device_id = uevent.minor % 128 + 1; devpath = android::base::StringPrintf("/dev/bus/usb/%03d/%03d", bus_id, device_id); } } else { // ignore other USB events return; } - } else if (auto subsystem = std::find(subsystems.begin(), subsystems.end(), uevent->subsystem); - subsystem != subsystems.end()) { + } else if (const auto subsystem = + std::find(subsystems_.cbegin(), subsystems_.cend(), uevent.subsystem); + subsystem != subsystems_.cend()) { devpath = subsystem->ParseDevPath(uevent); } else { - devpath = "/dev/" + android::base::Basename(uevent->path); + devpath = "/dev/" + android::base::Basename(uevent.path); } - mkdir_recursive(android::base::Dirname(devpath), 0755, sehandle); + mkdir_recursive(android::base::Dirname(devpath), 0755, sehandle_); - auto links = get_character_device_symlinks(uevent); + auto links = GetCharacterDeviceSymlinks(uevent); - handle_device(uevent->action, devpath, 0, uevent->major, uevent->minor, links); + HandleDevice(uevent.action, devpath, 0, uevent.major, uevent.minor, links); } -static void handle_device_event(struct uevent *uevent) -{ - if (uevent->action == "add" || uevent->action == "change" || uevent->action == "online") { - fixup_sys_permissions(uevent->path, uevent->subsystem); +void DeviceHandler::HandleDeviceEvent(const Uevent& uevent) { + if (uevent.action == "add" || uevent.action == "change" || uevent.action == "online") { + FixupSysPermissions(uevent.path, uevent.subsystem); } - if (uevent->subsystem == "block") { - handle_block_device_event(uevent); - } else if (uevent->subsystem == "platform") { - handle_platform_device_event(uevent); + if (uevent.subsystem == "block") { + HandleBlockDeviceEvent(uevent); + } else if (uevent.subsystem == "platform") { + HandlePlatformDeviceEvent(uevent); } else { - handle_generic_device_event(uevent); + HandleGenericDeviceEvent(uevent); } } -static void load_firmware(uevent* uevent, const std::string& root, - int fw_fd, size_t fw_size, - int loading_fd, int data_fd) { - // Start transfer. - android::base::WriteFully(loading_fd, "1", 1); +DeviceHandler::DeviceHandler(std::vector dev_permissions, + std::vector sysfs_permissions, + std::vector subsystems) + : dev_permissions_(std::move(dev_permissions)), + sysfs_permissions_(std::move(sysfs_permissions)), + subsystems_(std::move(subsystems)), + sehandle_(selinux_android_file_context_handle()) {} - // Copy the firmware. - int rc = sendfile(data_fd, fw_fd, nullptr, fw_size); - if (rc == -1) { - PLOG(ERROR) << "firmware: sendfile failed { '" << root << "', '" << uevent->firmware << "' }"; - } - - // Tell the firmware whether to abort or commit. - const char* response = (rc != -1) ? "0" : "-1"; - android::base::WriteFully(loading_fd, response, strlen(response)); -} - -static int is_booting() { - return access("/dev/.booting", F_OK) == 0; -} - -static void process_firmware_event(uevent* uevent) { - int booting = is_booting(); - - LOG(INFO) << "firmware: loading '" << uevent->firmware << "' for '" << uevent->path << "'"; - - std::string root = "/sys" + uevent->path; - std::string loading = root + "/loading"; - std::string data = root + "/data"; - - android::base::unique_fd loading_fd(open(loading.c_str(), O_WRONLY|O_CLOEXEC)); - if (loading_fd == -1) { - PLOG(ERROR) << "couldn't open firmware loading fd for " << uevent->firmware; - return; - } - - android::base::unique_fd data_fd(open(data.c_str(), O_WRONLY|O_CLOEXEC)); - if (data_fd == -1) { - PLOG(ERROR) << "couldn't open firmware data fd for " << uevent->firmware; - return; - } - - static const char* firmware_dirs[] = {"/etc/firmware/", "/vendor/firmware/", - "/firmware/image/"}; - -try_loading_again: - for (size_t i = 0; i < arraysize(firmware_dirs); i++) { - std::string file = firmware_dirs[i] + uevent->firmware; - android::base::unique_fd fw_fd(open(file.c_str(), O_RDONLY|O_CLOEXEC)); - struct stat sb; - if (fw_fd != -1 && fstat(fw_fd, &sb) != -1) { - load_firmware(uevent, root, fw_fd, sb.st_size, loading_fd, data_fd); - return; - } - } - - if (booting) { - // If we're not fully booted, we may be missing - // filesystems needed for firmware, wait and retry. - std::this_thread::sleep_for(100ms); - booting = is_booting(); - goto try_loading_again; - } - - LOG(ERROR) << "firmware: could not find firmware for " << uevent->firmware; - - // Write "-1" as our response to the kernel's firmware request, since we have nothing for it. - write(loading_fd, "-1", 2); -} - -static void handle_firmware_event(uevent* uevent) { - if (uevent->subsystem != "firmware" || uevent->action != "add") return; - - // Loading the firmware in a child means we can do that in parallel... - // (We ignore SIGCHLD rather than wait for our children.) - pid_t pid = fork(); - if (pid == 0) { - Timer t; - process_firmware_event(uevent); - LOG(INFO) << "loading " << uevent->path << " took " << t; - _exit(EXIT_SUCCESS); - } else if (pid == -1) { - PLOG(ERROR) << "could not fork to process firmware event for " << uevent->firmware; - } -} - -static bool inline should_stop_coldboot(coldboot_action_t act) -{ - return (act == COLDBOOT_STOP || act == COLDBOOT_FINISH); -} - -#define UEVENT_MSG_LEN 2048 - -static inline coldboot_action_t handle_device_fd_with( - std::function handle_uevent) -{ - char msg[UEVENT_MSG_LEN+2]; - int n; - while ((n = uevent_kernel_multicast_recv(device_fd, msg, UEVENT_MSG_LEN)) > 0) { - if(n >= UEVENT_MSG_LEN) /* overflow -- discard */ - continue; - - msg[n] = '\0'; - msg[n+1] = '\0'; - - uevent uevent; - parse_event(msg, &uevent); - coldboot_action_t act = handle_uevent(&uevent); - if (should_stop_coldboot(act)) - return act; - } - - return COLDBOOT_CONTINUE; -} - -coldboot_action_t handle_device_fd(coldboot_callback fn) -{ - coldboot_action_t ret = handle_device_fd_with( - [&](uevent* uevent) -> coldboot_action_t { - // default is to always create the devices - coldboot_action_t act = COLDBOOT_CREATE; - if (fn) { - act = fn(uevent); - } - - if (act == COLDBOOT_CREATE || act == COLDBOOT_STOP) { - handle_device_event(uevent); - handle_firmware_event(uevent); - } - - return act; - }); - - return ret; -} - -/* Coldboot walks parts of the /sys tree and pokes the uevent files -** to cause the kernel to regenerate device add events that happened -** before init's device manager was started -** -** We drain any pending events from the netlink socket every time -** we poke another uevent file to make sure we don't overrun the -** socket's buffer. -*/ - -static coldboot_action_t do_coldboot(DIR *d, coldboot_callback fn) -{ - struct dirent *de; - int dfd, fd; - coldboot_action_t act = COLDBOOT_CONTINUE; - - dfd = dirfd(d); - - fd = openat(dfd, "uevent", O_WRONLY); - if (fd >= 0) { - write(fd, "add\n", 4); - close(fd); - act = handle_device_fd(fn); - if (should_stop_coldboot(act)) - return act; - } - - while (!should_stop_coldboot(act) && (de = readdir(d))) { - DIR *d2; - - if(de->d_type != DT_DIR || de->d_name[0] == '.') - continue; - - fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY); - if(fd < 0) - continue; - - d2 = fdopendir(fd); - if(d2 == 0) - close(fd); - else { - act = do_coldboot(d2, fn); - closedir(d2); - } - } - - // default is always to continue looking for uevents - return act; -} - -static coldboot_action_t coldboot(const char *path, coldboot_callback fn) -{ - std::unique_ptr d(opendir(path), closedir); - if (d) { - return do_coldboot(d.get(), fn); - } - - return COLDBOOT_CONTINUE; -} - -void device_init(const char* path, coldboot_callback fn) { - if (!sehandle) { - sehandle = selinux_android_file_context_handle(); - } - // open uevent socket and selinux status only if it hasn't been - // done before - if (device_fd == -1) { - /* is 256K enough? udev uses 16MB! */ - device_fd.reset(uevent_open_socket(256 * 1024, true)); - if (device_fd == -1) { - return; - } - fcntl(device_fd, F_SETFL, O_NONBLOCK); - } - - if (access(COLDBOOT_DONE, F_OK) == 0) { - LOG(VERBOSE) << "Skipping coldboot, already done!"; - return; - } - - Timer t; - coldboot_action_t act; - if (!path) { - act = coldboot("/sys/class", fn); - if (!should_stop_coldboot(act)) { - act = coldboot("/sys/block", fn); - if (!should_stop_coldboot(act)) { - act = coldboot("/sys/devices", fn); - } - } - } else { - act = coldboot(path, fn); - } - - // If we have a callback, then do as it says. If no, then the default is - // to always create COLDBOOT_DONE file. - if (!fn || (act == COLDBOOT_FINISH)) { - close(open(COLDBOOT_DONE, O_WRONLY|O_CREAT|O_CLOEXEC, 0000)); - } - - LOG(INFO) << "Coldboot took " << t; -} - -void device_close() { - platform_devices.clear(); - device_fd.reset(); -} - -int get_device_fd() { - return device_fd; -} +DeviceHandler::DeviceHandler() + : DeviceHandler(std::vector{}, std::vector{}, + std::vector{}) {} diff --git a/init/devices.h b/init/devices.h index 647b4c4bc..50f49fc10 100644 --- a/init/devices.h +++ b/init/devices.h @@ -20,35 +20,14 @@ #include #include -#include +#include #include #include -#include "init_parser.h" +#include +#include -enum coldboot_action_t { - // coldboot continues without creating the device for the uevent - COLDBOOT_CONTINUE = 0, - // coldboot continues after creating the device for the uevent - COLDBOOT_CREATE, - // coldboot stops after creating the device for uevent but doesn't - // create the COLDBOOT_DONE file - COLDBOOT_STOP, - // same as COLDBOOT_STOP, but creates the COLDBOOT_DONE file - COLDBOOT_FINISH -}; - -struct uevent { - std::string action; - std::string path; - std::string subsystem; - std::string firmware; - std::string partition_name; - std::string device_name; - int partition_num; - int major; - int minor; -}; +#include "uevent.h" class Permissions { public: @@ -93,9 +72,15 @@ class Subsystem { // Returns the full path for a uevent of a device that is a member of this subsystem, // according to the rules parsed from ueventd.rc - std::string ParseDevPath(uevent* uevent) const; + std::string ParseDevPath(const Uevent& uevent) const { + std::string devname = devname_source_ == DevnameSource::DEVNAME_UEVENT_DEVNAME + ? uevent.device_name + : android::base::Basename(uevent.path); - bool operator==(const std::string& string_name) { return name_ == string_name; } + return dir_name_ + "/" + devname; + } + + bool operator==(const std::string& string_name) const { return name_ == string_name; } private: enum class DevnameSource { @@ -108,34 +93,54 @@ class Subsystem { DevnameSource devname_source_; }; -class SubsystemParser : public SectionParser { +class PlatformDeviceList { public: - SubsystemParser() {} - bool ParseSection(std::vector&& args, const std::string& filename, int line, - std::string* err) override; - bool ParseLineSection(std::vector&& args, int line, std::string* err) override; - void EndSection() override; + void Add(const std::string& path) { platform_devices_.emplace_back(path); } + void Remove(const std::string& path) { + auto it = std::find(platform_devices_.begin(), platform_devices_.end(), path); + if (it != platform_devices_.end()) platform_devices_.erase(it); + } + bool Find(const std::string& path, std::string* out_path) const; + auto size() const { return platform_devices_.size(); } private: - bool ParseDevName(std::vector&& args, std::string* err); - bool ParseDirName(std::vector&& args, std::string* err); - - Subsystem subsystem_; + std::vector platform_devices_; }; -bool ParsePermissionsLine(std::vector&& args, std::string* err, bool is_sysfs); -typedef std::function coldboot_callback; -extern coldboot_action_t handle_device_fd(coldboot_callback fn = nullptr); -extern void device_init(const char* path = nullptr, coldboot_callback fn = nullptr); -extern void device_close(); -int get_device_fd(); +class DeviceHandler { + public: + friend class DeviceHandlerTester; + + DeviceHandler(); + DeviceHandler(std::vector dev_permissions, + std::vector sysfs_permissions, + std::vector subsystems); + ~DeviceHandler(){}; + + void HandleDeviceEvent(const Uevent& uevent); + std::vector GetBlockDeviceSymlinks(const Uevent& uevent) const; + + private: + void FixupSysPermissions(const std::string& upath, const std::string& subsystem) const; + std::tuple GetDevicePermissions( + const std::string& path, const std::vector& links) const; + void MakeDevice(const std::string& path, int block, int major, int minor, + const std::vector& links) const; + std::vector GetCharacterDeviceSymlinks(const Uevent& uevent) const; + void HandleDevice(const std::string& action, const std::string& devpath, int block, int major, + int minor, const std::vector& links) const; + void HandlePlatformDeviceEvent(const Uevent& uevent); + void HandleBlockDeviceEvent(const Uevent& uevent) const; + void HandleGenericDeviceEvent(const Uevent& uevent) const; + + std::vector dev_permissions_; + std::vector sysfs_permissions_; + std::vector subsystems_; + PlatformDeviceList platform_devices_; + selabel_handle* sehandle_; +}; // Exposed for testing -extern std::vector platform_devices; -bool find_platform_device(const std::string& path, std::string* out_path); -std::vector get_character_device_symlinks(uevent* uevent); -std::vector get_block_device_symlinks(uevent* uevent); -void sanitize_partition_name(std::string* string); -void handle_platform_device_event(uevent* uevent); +void SanitizePartitionName(std::string* string); -#endif /* _INIT_DEVICES_H */ +#endif diff --git a/init/devices_test.cpp b/init/devices_test.cpp index 66521db6b..41b101b22 100644 --- a/init/devices_test.cpp +++ b/init/devices_test.cpp @@ -22,167 +22,180 @@ #include #include -void add_platform_device(const std::string& path) { - uevent uevent = { - .action = "add", .subsystem = "platform", .path = path, - }; - handle_platform_device_event(&uevent); -} - -void remove_platform_device(const std::string& path) { - uevent uevent = { - .action = "remove", .subsystem = "platform", .path = path, - }; - handle_platform_device_event(&uevent); -} - -template (*Function)(uevent*)> -void test_get_symlinks(const std::string& platform_device_name, uevent* uevent, - const std::vector expected_links) { - add_platform_device(platform_device_name); - auto platform_device_remover = android::base::make_scope_guard( - [&platform_device_name]() { remove_platform_device(platform_device_name); }); - - std::vector result = Function(uevent); - - auto expected_size = expected_links.size(); - ASSERT_EQ(expected_size, result.size()); - if (expected_size == 0) return; - - // Explicitly iterate so the results are visible if a failure occurs - for (unsigned int i = 0; i < expected_size; ++i) { - EXPECT_EQ(expected_links[i], result[i]); +class DeviceHandlerTester { + public: + void AddPlatformDevice(const std::string& path) { + Uevent uevent = { + .action = "add", .subsystem = "platform", .path = path, + }; + device_handler_.HandlePlatformDeviceEvent(uevent); } -} -TEST(devices, handle_platform_device_event) { - platform_devices.clear(); - add_platform_device("/devices/platform/some_device_name"); - ASSERT_EQ(1U, platform_devices.size()); - remove_platform_device("/devices/platform/some_device_name"); - ASSERT_EQ(0U, platform_devices.size()); -} + void RemovePlatformDevice(const std::string& path) { + Uevent uevent = { + .action = "remove", .subsystem = "platform", .path = path, + }; + device_handler_.HandlePlatformDeviceEvent(uevent); + } -TEST(devices, find_platform_device) { - platform_devices.clear(); - add_platform_device("/devices/platform/some_device_name"); - add_platform_device("/devices/platform/some_device_name/longer"); - add_platform_device("/devices/platform/other_device_name"); - EXPECT_EQ(3U, platform_devices.size()); + void TestGetSymlinks(const std::string& platform_device_name, const Uevent& uevent, + const std::vector expected_links, bool block) { + AddPlatformDevice(platform_device_name); + auto platform_device_remover = android::base::make_scope_guard( + [this, &platform_device_name]() { RemovePlatformDevice(platform_device_name); }); + + std::vector result; + if (block) { + result = device_handler_.GetBlockDeviceSymlinks(uevent); + } else { + result = device_handler_.GetCharacterDeviceSymlinks(uevent); + } + + auto expected_size = expected_links.size(); + ASSERT_EQ(expected_size, result.size()); + if (expected_size == 0) return; + + // Explicitly iterate so the results are visible if a failure occurs + for (unsigned int i = 0; i < expected_size; ++i) { + EXPECT_EQ(expected_links[i], result[i]); + } + } + + private: + DeviceHandler device_handler_; +}; + +TEST(device_handler, PlatformDeviceList) { + PlatformDeviceList platform_device_list; + + platform_device_list.Add("/devices/platform/some_device_name"); + platform_device_list.Add("/devices/platform/some_device_name/longer"); + platform_device_list.Add("/devices/platform/other_device_name"); + EXPECT_EQ(3U, platform_device_list.size()); std::string out_path; - EXPECT_FALSE(find_platform_device("/devices/platform/not_found", &out_path)); + EXPECT_FALSE(platform_device_list.Find("/devices/platform/not_found", &out_path)); EXPECT_EQ("", out_path); - EXPECT_FALSE( - find_platform_device("/devices/platform/some_device_name_with_same_prefix", &out_path)); + EXPECT_FALSE(platform_device_list.Find("/devices/platform/some_device_name_with_same_prefix", + &out_path)); - EXPECT_TRUE( - find_platform_device("/devices/platform/some_device_name/longer/longer_child", &out_path)); + EXPECT_TRUE(platform_device_list.Find("/devices/platform/some_device_name/longer/longer_child", + &out_path)); EXPECT_EQ("/devices/platform/some_device_name/longer", out_path); - EXPECT_TRUE(find_platform_device("/devices/platform/some_device_name/other_child", &out_path)); + EXPECT_TRUE( + platform_device_list.Find("/devices/platform/some_device_name/other_child", &out_path)); EXPECT_EQ("/devices/platform/some_device_name", out_path); } -TEST(devices, get_character_device_symlinks_success) { +TEST(device_handler, get_character_device_symlinks_success) { const char* platform_device = "/devices/platform/some_device_name"; - uevent uevent = { + Uevent uevent = { .path = "/devices/platform/some_device_name/usb/usb_device/name/tty2-1:1.0", .subsystem = "tty", }; std::vector expected_result{"/dev/usb/ttyname"}; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, false); } -TEST(devices, get_character_device_symlinks_no_pdev_match) { +TEST(device_handler, get_character_device_symlinks_no_pdev_match) { const char* platform_device = "/devices/platform/some_device_name"; - uevent uevent = { + Uevent uevent = { .path = "/device/name/tty2-1:1.0", .subsystem = "tty", }; std::vector expected_result; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, false); } -TEST(devices, get_character_device_symlinks_nothing_after_platform_device) { +TEST(device_handler, get_character_device_symlinks_nothing_after_platform_device) { const char* platform_device = "/devices/platform/some_device_name"; - uevent uevent = { + Uevent uevent = { .path = "/devices/platform/some_device_name", .subsystem = "tty", }; std::vector expected_result; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, false); } -TEST(devices, get_character_device_symlinks_no_usb_found) { +TEST(device_handler, get_character_device_symlinks_no_usb_found) { const char* platform_device = "/devices/platform/some_device_name"; - uevent uevent = { + Uevent uevent = { .path = "/devices/platform/some_device_name/bad/bad/", .subsystem = "tty", }; std::vector expected_result; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, false); } -TEST(devices, get_character_device_symlinks_no_roothub) { +TEST(device_handler, get_character_device_symlinks_no_roothub) { const char* platform_device = "/devices/platform/some_device_name"; - uevent uevent = { + Uevent uevent = { .path = "/devices/platform/some_device_name/usb/", .subsystem = "tty", }; std::vector expected_result; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, false); } -TEST(devices, get_character_device_symlinks_no_usb_device) { +TEST(device_handler, get_character_device_symlinks_no_usb_device) { const char* platform_device = "/devices/platform/some_device_name"; - uevent uevent = { + Uevent uevent = { .path = "/devices/platform/some_device_name/usb/usb_device/", .subsystem = "tty", }; std::vector expected_result; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, false); } -TEST(devices, get_character_device_symlinks_no_final_slash) { +TEST(device_handler, get_character_device_symlinks_no_final_slash) { const char* platform_device = "/devices/platform/some_device_name"; - uevent uevent = { + Uevent uevent = { .path = "/devices/platform/some_device_name/usb/usb_device/name", .subsystem = "tty", }; std::vector expected_result; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, false); } -TEST(devices, get_character_device_symlinks_no_final_name) { +TEST(device_handler, get_character_device_symlinks_no_final_name) { const char* platform_device = "/devices/platform/some_device_name"; - uevent uevent = { + Uevent uevent = { .path = "/devices/platform/some_device_name/usb/usb_device//", .subsystem = "tty", }; std::vector expected_result; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, false); } -TEST(devices, get_block_device_symlinks_success_platform) { +TEST(device_handler, get_block_device_symlinks_success_platform) { // These are actual paths from bullhead const char* platform_device = "/devices/soc.0/f9824900.sdhci"; - uevent uevent = { + Uevent uevent = { .path = "/devices/soc.0/f9824900.sdhci/mmc_host/mmc0/mmc0:0001/block/mmcblk0", .partition_name = "", .partition_num = -1, }; std::vector expected_result{"/dev/block/platform/soc.0/f9824900.sdhci/mmcblk0"}; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, true); } -TEST(devices, get_block_device_symlinks_success_platform_with_partition) { +TEST(device_handler, get_block_device_symlinks_success_platform_with_partition) { // These are actual paths from bullhead const char* platform_device = "/devices/soc.0/f9824900.sdhci"; - uevent uevent = { + Uevent uevent = { .path = "/devices/soc.0/f9824900.sdhci/mmc_host/mmc0/mmc0:0001/block/mmcblk0p1", .partition_name = "modem", .partition_num = 1, @@ -193,12 +206,13 @@ TEST(devices, get_block_device_symlinks_success_platform_with_partition) { "/dev/block/platform/soc.0/f9824900.sdhci/mmcblk0p1", }; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, true); } -TEST(devices, get_block_device_symlinks_success_platform_with_partition_only_num) { +TEST(device_handler, get_block_device_symlinks_success_platform_with_partition_only_num) { const char* platform_device = "/devices/soc.0/f9824900.sdhci"; - uevent uevent = { + Uevent uevent = { .path = "/devices/soc.0/f9824900.sdhci/mmc_host/mmc0/mmc0:0001/block/mmcblk0p1", .partition_name = "", .partition_num = 1, @@ -208,12 +222,13 @@ TEST(devices, get_block_device_symlinks_success_platform_with_partition_only_num "/dev/block/platform/soc.0/f9824900.sdhci/mmcblk0p1", }; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, true); } -TEST(devices, get_block_device_symlinks_success_platform_with_partition_only_name) { +TEST(device_handler, get_block_device_symlinks_success_platform_with_partition_only_name) { const char* platform_device = "/devices/soc.0/f9824900.sdhci"; - uevent uevent = { + Uevent uevent = { .path = "/devices/soc.0/f9824900.sdhci/mmc_host/mmc0/mmc0:0001/block/mmcblk0p1", .partition_name = "modem", .partition_num = -1, @@ -223,101 +238,107 @@ TEST(devices, get_block_device_symlinks_success_platform_with_partition_only_nam "/dev/block/platform/soc.0/f9824900.sdhci/mmcblk0p1", }; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, true); } -TEST(devices, get_block_device_symlinks_success_pci) { +TEST(device_handler, get_block_device_symlinks_success_pci) { const char* platform_device = "/devices/do/not/match"; - uevent uevent = { + Uevent uevent = { .path = "/devices/pci0000:00/0000:00:1f.2/mmcblk0", .partition_name = "", .partition_num = -1, }; std::vector expected_result{"/dev/block/pci/pci0000:00/0000:00:1f.2/mmcblk0"}; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, true); } -TEST(devices, get_block_device_symlinks_pci_bad_format) { +TEST(device_handler, get_block_device_symlinks_pci_bad_format) { const char* platform_device = "/devices/do/not/match"; - uevent uevent = { + Uevent uevent = { .path = "/devices/pci//mmcblk0", .partition_name = "", .partition_num = -1, }; std::vector expected_result{}; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, true); } -TEST(devices, get_block_device_symlinks_success_vbd) { +TEST(device_handler, get_block_device_symlinks_success_vbd) { const char* platform_device = "/devices/do/not/match"; - uevent uevent = { + Uevent uevent = { .path = "/devices/vbd-1234/mmcblk0", .partition_name = "", .partition_num = -1, }; std::vector expected_result{"/dev/block/vbd/1234/mmcblk0"}; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, true); } -TEST(devices, get_block_device_symlinks_vbd_bad_format) { +TEST(device_handler, get_block_device_symlinks_vbd_bad_format) { const char* platform_device = "/devices/do/not/match"; - uevent uevent = { + Uevent uevent = { .path = "/devices/vbd-/mmcblk0", .partition_name = "", .partition_num = -1, }; std::vector expected_result{}; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, true); } -TEST(devices, get_block_device_symlinks_no_matches) { +TEST(device_handler, get_block_device_symlinks_no_matches) { const char* platform_device = "/devices/soc.0/f9824900.sdhci"; - uevent uevent = { + Uevent uevent = { .path = "/devices/soc.0/not_the_device/mmc_host/mmc0/mmc0:0001/block/mmcblk0p1", .partition_name = "", .partition_num = -1, }; std::vector expected_result; - test_get_symlinks(platform_device, &uevent, expected_result); + DeviceHandlerTester device_handler_tester_; + device_handler_tester_.TestGetSymlinks(platform_device, uevent, expected_result, true); } -TEST(devices, sanitize_null) { - sanitize_partition_name(nullptr); +TEST(device_handler, sanitize_null) { + SanitizePartitionName(nullptr); } -TEST(devices, sanitize_empty) { +TEST(device_handler, sanitize_empty) { std::string empty; - sanitize_partition_name(&empty); + SanitizePartitionName(&empty); EXPECT_EQ(0u, empty.size()); } -TEST(devices, sanitize_allgood) { +TEST(device_handler, sanitize_allgood) { std::string good = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "_-."; std::string good_copy = good; - sanitize_partition_name(&good); + SanitizePartitionName(&good); EXPECT_EQ(good_copy, good); } -TEST(devices, sanitize_somebad) { +TEST(device_handler, sanitize_somebad) { std::string string = "abc!@#$%^&*()"; - sanitize_partition_name(&string); + SanitizePartitionName(&string); EXPECT_EQ("abc__________", string); } -TEST(devices, sanitize_allbad) { +TEST(device_handler, sanitize_allbad) { std::string string = "!@#$%^&*()"; - sanitize_partition_name(&string); + SanitizePartitionName(&string); EXPECT_EQ("__________", string); } -TEST(devices, sanitize_onebad) { +TEST(device_handler, sanitize_onebad) { std::string string = ")"; - sanitize_partition_name(&string); + SanitizePartitionName(&string); EXPECT_EQ("_", string); } -TEST(devices, DevPermissionsMatchNormal) { +TEST(device_handler, DevPermissionsMatchNormal) { // Basic from ueventd.rc // /dev/null 0666 root root Permissions permissions("/dev/null", 0666, 0, 0); @@ -329,7 +350,7 @@ TEST(devices, DevPermissionsMatchNormal) { EXPECT_EQ(0U, permissions.gid()); } -TEST(devices, DevPermissionsMatchPrefix) { +TEST(device_handler, DevPermissionsMatchPrefix) { // Prefix from ueventd.rc // /dev/dri/* 0666 root graphics Permissions permissions("/dev/dri/*", 0666, 0, 1000); @@ -342,7 +363,7 @@ TEST(devices, DevPermissionsMatchPrefix) { EXPECT_EQ(1000U, permissions.gid()); } -TEST(devices, DevPermissionsMatchWildcard) { +TEST(device_handler, DevPermissionsMatchWildcard) { // Wildcard example // /dev/device*name 0666 root graphics Permissions permissions("/dev/device*name", 0666, 0, 1000); @@ -356,7 +377,7 @@ TEST(devices, DevPermissionsMatchWildcard) { EXPECT_EQ(1000U, permissions.gid()); } -TEST(devices, DevPermissionsMatchWildcardPrefix) { +TEST(device_handler, DevPermissionsMatchWildcardPrefix) { // Wildcard+Prefix example // /dev/device*name* 0666 root graphics Permissions permissions("/dev/device*name*", 0666, 0, 1000); @@ -372,7 +393,7 @@ TEST(devices, DevPermissionsMatchWildcardPrefix) { EXPECT_EQ(1000U, permissions.gid()); } -TEST(devices, SysfsPermissionsMatchWithSubsystemNormal) { +TEST(device_handler, SysfsPermissionsMatchWithSubsystemNormal) { // /sys/devices/virtual/input/input* enable 0660 root input SysfsPermissions permissions("/sys/devices/virtual/input/input*", "enable", 0660, 0, 1001); EXPECT_TRUE(permissions.MatchWithSubsystem("/sys/devices/virtual/input/input0", "input")); @@ -382,7 +403,7 @@ TEST(devices, SysfsPermissionsMatchWithSubsystemNormal) { EXPECT_EQ(1001U, permissions.gid()); } -TEST(devices, SysfsPermissionsMatchWithSubsystemClass) { +TEST(device_handler, SysfsPermissionsMatchWithSubsystemClass) { // /sys/class/input/event* enable 0660 root input SysfsPermissions permissions("/sys/class/input/event*", "enable", 0660, 0, 1001); EXPECT_TRUE(permissions.MatchWithSubsystem( @@ -396,7 +417,7 @@ TEST(devices, SysfsPermissionsMatchWithSubsystemClass) { EXPECT_EQ(1001U, permissions.gid()); } -TEST(devices, SysfsPermissionsMatchWithSubsystemBus) { +TEST(device_handler, SysfsPermissionsMatchWithSubsystemBus) { // /sys/bus/i2c/devices/i2c-* enable 0660 root input SysfsPermissions permissions("/sys/bus/i2c/devices/i2c-*", "enable", 0660, 0, 1001); EXPECT_TRUE(permissions.MatchWithSubsystem("/sys/devices/soc.0/f9967000.i2c/i2c-5", "i2c")); diff --git a/init/firmware_handler.cpp b/init/firmware_handler.cpp new file mode 100644 index 000000000..1471aeb47 --- /dev/null +++ b/init/firmware_handler.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "firmware_handler.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "util.h" + +static void LoadFirmware(const Uevent& uevent, const std::string& root, int fw_fd, size_t fw_size, + int loading_fd, int data_fd) { + // Start transfer. + android::base::WriteFully(loading_fd, "1", 1); + + // Copy the firmware. + int rc = sendfile(data_fd, fw_fd, nullptr, fw_size); + if (rc == -1) { + PLOG(ERROR) << "firmware: sendfile failed { '" << root << "', '" << uevent.firmware + << "' }"; + } + + // Tell the firmware whether to abort or commit. + const char* response = (rc != -1) ? "0" : "-1"; + android::base::WriteFully(loading_fd, response, strlen(response)); +} + +static bool IsBooting() { + return access("/dev/.booting", F_OK) == 0; +} + +static void ProcessFirmwareEvent(const Uevent& uevent) { + int booting = IsBooting(); + + LOG(INFO) << "firmware: loading '" << uevent.firmware << "' for '" << uevent.path << "'"; + + std::string root = "/sys" + uevent.path; + std::string loading = root + "/loading"; + std::string data = root + "/data"; + + android::base::unique_fd loading_fd(open(loading.c_str(), O_WRONLY | O_CLOEXEC)); + if (loading_fd == -1) { + PLOG(ERROR) << "couldn't open firmware loading fd for " << uevent.firmware; + return; + } + + android::base::unique_fd data_fd(open(data.c_str(), O_WRONLY | O_CLOEXEC)); + if (data_fd == -1) { + PLOG(ERROR) << "couldn't open firmware data fd for " << uevent.firmware; + return; + } + + static const char* firmware_dirs[] = {"/etc/firmware/", "/vendor/firmware/", + "/firmware/image/"}; + +try_loading_again: + for (size_t i = 0; i < arraysize(firmware_dirs); i++) { + std::string file = firmware_dirs[i] + uevent.firmware; + android::base::unique_fd fw_fd(open(file.c_str(), O_RDONLY | O_CLOEXEC)); + struct stat sb; + if (fw_fd != -1 && fstat(fw_fd, &sb) != -1) { + LoadFirmware(uevent, root, fw_fd, sb.st_size, loading_fd, data_fd); + return; + } + } + + if (booting) { + // If we're not fully booted, we may be missing + // filesystems needed for firmware, wait and retry. + std::this_thread::sleep_for(100ms); + booting = IsBooting(); + goto try_loading_again; + } + + LOG(ERROR) << "firmware: could not find firmware for " << uevent.firmware; + + // Write "-1" as our response to the kernel's firmware request, since we have nothing for it. + write(loading_fd, "-1", 2); +} + +void HandleFirmwareEvent(const Uevent& uevent) { + if (uevent.subsystem != "firmware" || uevent.action != "add") return; + + // Loading the firmware in a child means we can do that in parallel... + // (We ignore SIGCHLD rather than wait for our children.) + pid_t pid = fork(); + if (pid == 0) { + Timer t; + ProcessFirmwareEvent(uevent); + LOG(INFO) << "loading " << uevent.path << " took " << t; + _exit(EXIT_SUCCESS); + } else if (pid == -1) { + PLOG(ERROR) << "could not fork to process firmware event for " << uevent.firmware; + } +} diff --git a/init/firmware_handler.h b/init/firmware_handler.h new file mode 100644 index 000000000..be9daaee5 --- /dev/null +++ b/init/firmware_handler.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _INIT_FIRMWARE_HANDLER_H +#define _INIT_FIRMWARE_HANDLER_H + +#include "uevent.h" + +void HandleFirmwareEvent(const Uevent& uevent); + +#endif diff --git a/init/init.cpp b/init/init.cpp index 8b5d15eee..4e462d7f9 100644 --- a/init/init.cpp +++ b/init/init.cpp @@ -58,7 +58,6 @@ #include "action.h" #include "bootchart.h" -#include "devices.h" #include "import_parser.h" #include "init_first_stage.h" #include "init_parser.h" diff --git a/init/init_first_stage.cpp b/init/init_first_stage.cpp index 9d2a0d100..8a7d9a2bc 100644 --- a/init/init_first_stage.cpp +++ b/init/init_first_stage.cpp @@ -31,6 +31,8 @@ #include "devices.h" #include "fs_mgr.h" #include "fs_mgr_avb.h" +#include "uevent.h" +#include "uevent_listener.h" #include "util.h" // Class Declarations @@ -51,7 +53,7 @@ class FirstStageMount { void InitVerityDevice(const std::string& verity_device); bool MountPartitions(); - virtual coldboot_action_t ColdbootCallback(uevent* uevent); + virtual RegenerationAction UeventCallback(const Uevent& uevent); // Pure virtual functions. virtual bool GetRequiredDevices() = 0; @@ -63,6 +65,8 @@ class FirstStageMount { // Eligible first stage mount candidates, only allow /system, /vendor and/or /odm. std::vector mount_fstab_recs_; std::set required_devices_partition_names_; + DeviceHandler device_handler_; + UeventListener uevent_listener_; }; class FirstStageMountVBootV1 : public FirstStageMount { @@ -83,7 +87,7 @@ class FirstStageMountVBootV2 : public FirstStageMount { ~FirstStageMountVBootV2() override = default; protected: - coldboot_action_t ColdbootCallback(uevent* uevent) override; + RegenerationAction UeventCallback(const Uevent& uevent) override; bool GetRequiredDevices() override; bool SetUpDmVerity(fstab_rec* fstab_rec) override; bool InitAvbHandle(); @@ -165,46 +169,50 @@ void FirstStageMount::InitRequiredDevices() { if (need_dm_verity_) { const std::string dm_path = "/devices/virtual/misc/device-mapper"; - device_init(("/sys" + dm_path).c_str(), [&dm_path](uevent* uevent) -> coldboot_action_t { - if (uevent->path == dm_path) return COLDBOOT_STOP; - return COLDBOOT_CONTINUE; // dm_path not found, continue to find it. - }); + uevent_listener_.RegenerateUeventsForPath("/sys" + dm_path, + [this, &dm_path](const Uevent& uevent) { + if (uevent.path == dm_path) { + device_handler_.HandleDeviceEvent(uevent); + return RegenerationAction::kStop; + } + return RegenerationAction::kContinue; + }); } - device_init(nullptr, - [this](uevent* uevent) -> coldboot_action_t { return ColdbootCallback(uevent); }); - - device_close(); + uevent_listener_.RegenerateUevents( + [this](const Uevent& uevent) { return UeventCallback(uevent); }); } -coldboot_action_t FirstStageMount::ColdbootCallback(uevent* uevent) { +RegenerationAction FirstStageMount::UeventCallback(const Uevent& uevent) { // We need platform devices to create symlinks. - if (uevent->subsystem == "platform") { - return COLDBOOT_CREATE; + if (uevent.subsystem == "platform") { + device_handler_.HandleDeviceEvent(uevent); + return RegenerationAction::kContinue; } // Ignores everything that is not a block device. - if (uevent->subsystem != "block") { - return COLDBOOT_CONTINUE; + if (uevent.subsystem != "block") { + return RegenerationAction::kContinue; } - if (!uevent->partition_name.empty()) { + if (!uevent.partition_name.empty()) { // Matches partition name to create device nodes. // Both required_devices_partition_names_ and uevent->partition_name have A/B // suffix when A/B is used. - auto iter = required_devices_partition_names_.find(uevent->partition_name); + auto iter = required_devices_partition_names_.find(uevent.partition_name); if (iter != required_devices_partition_names_.end()) { LOG(VERBOSE) << __FUNCTION__ << "(): found partition: " << *iter; required_devices_partition_names_.erase(iter); + device_handler_.HandleDeviceEvent(uevent); if (required_devices_partition_names_.empty()) { - return COLDBOOT_STOP; // Found all partitions, stop coldboot. + return RegenerationAction::kStop; } else { - return COLDBOOT_CREATE; // Creates this device and continue to find others. + return RegenerationAction::kContinue; } } } // Not found a partition or find an unneeded partition, continue to find others. - return COLDBOOT_CONTINUE; + return RegenerationAction::kContinue; } // Creates "/dev/block/dm-XX" for dm-verity by running coldboot on /sys/block/dm-XX. @@ -212,14 +220,15 @@ void FirstStageMount::InitVerityDevice(const std::string& verity_device) { const std::string device_name(basename(verity_device.c_str())); const std::string syspath = "/sys/block/" + device_name; - device_init(syspath.c_str(), [&](uevent* uevent) -> coldboot_action_t { - if (uevent->device_name == device_name) { - LOG(VERBOSE) << "Creating dm-verity device : " << verity_device; - return COLDBOOT_STOP; - } - return COLDBOOT_CONTINUE; - }); - device_close(); + uevent_listener_.RegenerateUeventsForPath( + syspath, [&device_name, &verity_device, this](const Uevent& uevent) { + if (uevent.device_name == device_name) { + LOG(VERBOSE) << "Creating dm-verity device : " << verity_device; + device_handler_.HandleDeviceEvent(uevent); + return RegenerationAction::kStop; + } + return RegenerationAction::kContinue; + }); } bool FirstStageMount::MountPartitions() { @@ -345,33 +354,32 @@ bool FirstStageMountVBootV2::GetRequiredDevices() { return true; } -coldboot_action_t FirstStageMountVBootV2::ColdbootCallback(uevent* uevent) { - // Invokes the parent function to see if any desired partition has been found. - // If yes, record the by-name symlink for creating FsManagerAvbHandle later. - coldboot_action_t parent_callback_ret = FirstStageMount::ColdbootCallback(uevent); - - // Skips the uevent if the parent function returns COLDBOOT_CONTINUE (meaning - // that the uevent was skipped) or there is no uevent->partition_name to - // create the by-name symlink. - if (parent_callback_ret != COLDBOOT_CONTINUE && !uevent->partition_name.empty()) { - // get_block_device_symlinks() will return three symlinks at most, depending on +RegenerationAction FirstStageMountVBootV2::UeventCallback(const Uevent& uevent) { + // Check if this uevent corresponds to one of the required partitions and store its symlinks if + // so, in order to create FsManagerAvbHandle later. + // Note that the parent callback removes partitions from the list of required partitions + // as it finds them, so this must happen first. + if (!uevent.partition_name.empty() && + required_devices_partition_names_.find(uevent.partition_name) != + required_devices_partition_names_.end()) { + // GetBlockDeviceSymlinks() will return three symlinks at most, depending on // the content of uevent. by-name symlink will be at [0] if uevent->partition_name // is not empty. e.g., // - /dev/block/platform/soc.0/f9824900.sdhci/by-name/modem // - /dev/block/platform/soc.0/f9824900.sdhci/by-num/p1 // - /dev/block/platform/soc.0/f9824900.sdhci/mmcblk0p1 - std::vector links = get_block_device_symlinks(uevent); + std::vector links = device_handler_.GetBlockDeviceSymlinks(uevent); if (!links.empty()) { - auto[it, inserted] = by_name_symlink_map_.emplace(uevent->partition_name, links[0]); + auto[it, inserted] = by_name_symlink_map_.emplace(uevent.partition_name, links[0]); if (!inserted) { - LOG(ERROR) << "Partition '" << uevent->partition_name + LOG(ERROR) << "Partition '" << uevent.partition_name << "' already existed in the by-name symlink map with a value of '" << it->second << "', new value '" << links[0] << "' will be ignored."; } } } - return parent_callback_ret; + return FirstStageMount::UeventCallback(uevent); } bool FirstStageMountVBootV2::SetUpDmVerity(fstab_rec* fstab_rec) { diff --git a/init/init_parser.h b/init/init_parser.h index bd8a17805..722ebb26a 100644 --- a/init/init_parser.h +++ b/init/init_parser.h @@ -66,9 +66,9 @@ class Parser { // be written. using LineCallback = std::function&&, std::string*)>; + // TODO: init is the only user of this as a singleton; remove it. static Parser& GetInstance(); - // Exposed for testing Parser(); bool ParseConfig(const std::string& path); diff --git a/init/uevent.h b/init/uevent.h new file mode 100644 index 000000000..1095665fe --- /dev/null +++ b/init/uevent.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _INIT_UEVENT_H +#define _INIT_UEVENT_H + +#include + +struct Uevent { + std::string action; + std::string path; + std::string subsystem; + std::string firmware; + std::string partition_name; + std::string device_name; + int partition_num; + int major; + int minor; +}; + +#endif diff --git a/init/uevent_listener.cpp b/init/uevent_listener.cpp new file mode 100644 index 000000000..27c5d2374 --- /dev/null +++ b/init/uevent_listener.cpp @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "uevent_listener.h" + +#include +#include +#include +#include + +#include + +#include +#include + +static void ParseEvent(const char* msg, Uevent* uevent) { + uevent->partition_num = -1; + uevent->major = -1; + uevent->minor = -1; + uevent->action.clear(); + uevent->path.clear(); + uevent->subsystem.clear(); + uevent->firmware.clear(); + uevent->partition_name.clear(); + uevent->device_name.clear(); + // currently ignoring SEQNUM + while (*msg) { + if (!strncmp(msg, "ACTION=", 7)) { + msg += 7; + uevent->action = msg; + } else if (!strncmp(msg, "DEVPATH=", 8)) { + msg += 8; + uevent->path = msg; + } else if (!strncmp(msg, "SUBSYSTEM=", 10)) { + msg += 10; + uevent->subsystem = msg; + } else if (!strncmp(msg, "FIRMWARE=", 9)) { + msg += 9; + uevent->firmware = msg; + } else if (!strncmp(msg, "MAJOR=", 6)) { + msg += 6; + uevent->major = atoi(msg); + } else if (!strncmp(msg, "MINOR=", 6)) { + msg += 6; + uevent->minor = atoi(msg); + } else if (!strncmp(msg, "PARTN=", 6)) { + msg += 6; + uevent->partition_num = atoi(msg); + } else if (!strncmp(msg, "PARTNAME=", 9)) { + msg += 9; + uevent->partition_name = msg; + } else if (!strncmp(msg, "DEVNAME=", 8)) { + msg += 8; + uevent->device_name = msg; + } + + // advance to after the next \0 + while (*msg++) + ; + } + + if (LOG_UEVENTS) { + LOG(INFO) << "event { '" << uevent->action << "', '" << uevent->path << "', '" + << uevent->subsystem << "', '" << uevent->firmware << "', " << uevent->major + << ", " << uevent->minor << " }"; + } +} + +UeventListener::UeventListener() { + // is 256K enough? udev uses 16MB! + device_fd_.reset(uevent_open_socket(256 * 1024, true)); + if (device_fd_ == -1) { + LOG(FATAL) << "Could not open uevent socket"; + } + + fcntl(device_fd_, F_SETFL, O_NONBLOCK); +} + +bool UeventListener::ReadUevent(Uevent* uevent) const { + char msg[UEVENT_MSG_LEN + 2]; + int n = uevent_kernel_multicast_recv(device_fd_, msg, UEVENT_MSG_LEN); + if (n <= 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + LOG(ERROR) << "Error reading from Uevent Fd"; + } + return false; + } + if (n >= UEVENT_MSG_LEN) { + LOG(ERROR) << "Uevent overflowed buffer, discarding"; + // Return true here even if we discard as we may have more uevents pending and we + // want to keep processing them. + return true; + } + + msg[n] = '\0'; + msg[n + 1] = '\0'; + + ParseEvent(msg, uevent); + + return true; +} + +// RegenerateUevents*() walks parts of the /sys tree and pokes the uevent files to cause the kernel +// to regenerate device add uevents that have already happened. This is particularly useful when +// starting ueventd, to regenerate all of the uevents that it had previously missed. +// +// We drain any pending events from the netlink socket every time we poke another uevent file to +// make sure we don't overrun the socket's buffer. +// + +RegenerationAction UeventListener::RegenerateUeventsForDir(DIR* d, + RegenerateCallback callback) const { + int dfd = dirfd(d); + + int fd = openat(dfd, "uevent", O_WRONLY); + if (fd >= 0) { + write(fd, "add\n", 4); + close(fd); + + Uevent uevent; + while (ReadUevent(&uevent)) { + if (callback(uevent) == RegenerationAction::kStop) return RegenerationAction::kStop; + } + } + + dirent* de; + while ((de = readdir(d)) != nullptr) { + if (de->d_type != DT_DIR || de->d_name[0] == '.') continue; + + fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY); + if (fd < 0) continue; + + std::unique_ptr d2(fdopendir(fd), closedir); + if (d2 == 0) { + close(fd); + } else { + if (RegenerateUeventsForDir(d2.get(), callback) == RegenerationAction::kStop) { + return RegenerationAction::kStop; + } + } + } + + // default is always to continue looking for uevents + return RegenerationAction::kContinue; +} + +RegenerationAction UeventListener::RegenerateUeventsForPath(const std::string& path, + RegenerateCallback callback) const { + std::unique_ptr d(opendir(path.c_str()), closedir); + if (!d) return RegenerationAction::kContinue; + + return RegenerateUeventsForDir(d.get(), callback); +} + +static const char* kRegenerationPaths[] = {"/sys/class", "/sys/block", "/sys/devices"}; + +void UeventListener::RegenerateUevents(RegenerateCallback callback) const { + for (const auto path : kRegenerationPaths) { + if (RegenerateUeventsForPath(path, callback) == RegenerationAction::kStop) return; + } +} + +void UeventListener::DoPolling(PollCallback callback) const { + pollfd ufd; + ufd.events = POLLIN; + ufd.fd = device_fd_; + + while (true) { + ufd.revents = 0; + int nr = poll(&ufd, 1, -1); + if (nr <= 0) { + continue; + } + if (ufd.revents & POLLIN) { + // We're non-blocking, so if we receive a poll event keep processing until there + // we have exhausted all uevent messages. + Uevent uevent; + while (ReadUevent(&uevent)) { + callback(uevent); + } + } + } +} diff --git a/init/uevent_listener.h b/init/uevent_listener.h new file mode 100644 index 000000000..ba31aaa71 --- /dev/null +++ b/init/uevent_listener.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _INIT_UEVENT_LISTENER_H +#define _INIT_UEVENT_LISTENER_H + +#include + +#include + +#include + +#include "uevent.h" + +#define UEVENT_MSG_LEN 2048 + +enum class RegenerationAction { + kStop = 0, // Stop regenerating uevents as we've handled the one(s) we're interested in. + kContinue, // Continue regenerating uevents as we haven't seen the one(s) we're interested in. +}; + +using RegenerateCallback = std::function; +using PollCallback = std::function; + +class UeventListener { + public: + UeventListener(); + + void RegenerateUevents(RegenerateCallback callback) const; + RegenerationAction RegenerateUeventsForPath(const std::string& path, + RegenerateCallback callback) const; + void DoPolling(PollCallback callback) const; + + private: + bool ReadUevent(Uevent* uevent) const; + RegenerationAction RegenerateUeventsForDir(DIR* d, RegenerateCallback callback) const; + + android::base::unique_fd device_fd_; +}; + +#endif diff --git a/init/ueventd.cpp b/init/ueventd.cpp index 8c0c574b6..bd21a3e95 100644 --- a/init/ueventd.cpp +++ b/init/ueventd.cpp @@ -18,7 +18,6 @@ #include #include -#include #include #include #include @@ -30,9 +29,44 @@ #include #include "devices.h" +#include "firmware_handler.h" #include "log.h" +#include "uevent_listener.h" +#include "ueventd_parser.h" #include "util.h" +DeviceHandler CreateDeviceHandler() { + Parser parser; + + std::vector subsystems; + parser.AddSectionParser("subsystem", std::make_unique(&subsystems)); + + using namespace std::placeholders; + std::vector sysfs_permissions; + std::vector dev_permissions; + parser.AddSingleLineParser( + "/sys/", std::bind(ParsePermissionsLine, _1, _2, &sysfs_permissions, nullptr)); + parser.AddSingleLineParser("/dev/", + std::bind(ParsePermissionsLine, _1, _2, nullptr, &dev_permissions)); + + parser.ParseConfig("/ueventd.rc"); + parser.ParseConfig("/vendor/ueventd.rc"); + parser.ParseConfig("/odm/ueventd.rc"); + + /* + * keep the current product name base configuration so + * we remain backwards compatible and allow it to override + * everything + * TODO: cleanup platform ueventd.rc to remove vendor specific + * device node entries (b/34968103) + */ + std::string hardware = android::base::GetProperty("ro.hardware", ""); + parser.ParseConfig("/ueventd." + hardware + ".rc"); + + return DeviceHandler(std::move(dev_permissions), std::move(sysfs_permissions), + std::move(subsystems)); +} + int ueventd_main(int argc, char **argv) { /* @@ -57,41 +91,26 @@ int ueventd_main(int argc, char **argv) cb.func_log = selinux_klog_callback; selinux_set_callback(SELINUX_CB_LOG, cb); - Parser& parser = Parser::GetInstance(); - parser.AddSectionParser("subsystem", std::make_unique()); - using namespace std::placeholders; - parser.AddSingleLineParser("/sys/", std::bind(ParsePermissionsLine, _1, _2, true)); - parser.AddSingleLineParser("/dev/", std::bind(ParsePermissionsLine, _1, _2, false)); - parser.ParseConfig("/ueventd.rc"); - parser.ParseConfig("/vendor/ueventd.rc"); - parser.ParseConfig("/odm/ueventd.rc"); + DeviceHandler device_handler = CreateDeviceHandler(); + UeventListener uevent_listener; - /* - * keep the current product name base configuration so - * we remain backwards compatible and allow it to override - * everything - * TODO: cleanup platform ueventd.rc to remove vendor specific - * device node entries (b/34968103) - */ - std::string hardware = android::base::GetProperty("ro.hardware", ""); - parser.ParseConfig("/ueventd." + hardware + ".rc"); + if (access(COLDBOOT_DONE, F_OK) != 0) { + Timer t; - device_init(); + uevent_listener.RegenerateUevents([&device_handler](const Uevent& uevent) { + HandleFirmwareEvent(uevent); + device_handler.HandleDeviceEvent(uevent); + return RegenerationAction::kContinue; + }); - pollfd ufd; - ufd.events = POLLIN; - ufd.fd = get_device_fd(); - - while (true) { - ufd.revents = 0; - int nr = poll(&ufd, 1, -1); - if (nr <= 0) { - continue; - } - if (ufd.revents & POLLIN) { - handle_device_fd(); - } + close(open(COLDBOOT_DONE, O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); + LOG(INFO) << "Coldboot took " << t; } + uevent_listener.DoPolling([&device_handler](const Uevent& uevent) { + HandleFirmwareEvent(uevent); + device_handler.HandleDeviceEvent(uevent); + }); + return 0; } diff --git a/init/ueventd_parser.cpp b/init/ueventd_parser.cpp new file mode 100644 index 000000000..7156e766f --- /dev/null +++ b/init/ueventd_parser.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ueventd_parser.h" + +#include +#include + +#include "keyword_map.h" + +bool ParsePermissionsLine(std::vector&& args, std::string* err, + std::vector* out_sysfs_permissions, + std::vector* out_dev_permissions) { + bool is_sysfs = out_sysfs_permissions != nullptr; + if (is_sysfs && args.size() != 5) { + *err = "/sys/ lines must have 5 entries"; + return false; + } + + if (!is_sysfs && args.size() != 4) { + *err = "/dev/ lines must have 4 entries"; + return false; + } + + auto it = args.begin(); + const std::string& name = *it++; + + std::string sysfs_attribute; + if (is_sysfs) sysfs_attribute = *it++; + + // args is now common to both sys and dev entries and contains: + std::string& perm_string = *it++; + char* end_pointer = 0; + mode_t perm = strtol(perm_string.c_str(), &end_pointer, 8); + if (end_pointer == nullptr || *end_pointer != '\0') { + *err = "invalid mode '" + perm_string + "'"; + return false; + } + + std::string& uid_string = *it++; + passwd* pwd = getpwnam(uid_string.c_str()); + if (!pwd) { + *err = "invalid uid '" + uid_string + "'"; + return false; + } + uid_t uid = pwd->pw_uid; + + std::string& gid_string = *it++; + struct group* grp = getgrnam(gid_string.c_str()); + if (!grp) { + *err = "invalid gid '" + gid_string + "'"; + return false; + } + gid_t gid = grp->gr_gid; + + if (is_sysfs) { + out_sysfs_permissions->emplace_back(name, sysfs_attribute, perm, uid, gid); + } else { + out_dev_permissions->emplace_back(name, perm, uid, gid); + } + return true; +} + +bool SubsystemParser::ParseSection(std::vector&& args, const std::string& filename, + int line, std::string* err) { + if (args.size() != 2) { + *err = "subsystems must have exactly one name"; + return false; + } + + if (std::find(subsystems_->begin(), subsystems_->end(), args[1]) != subsystems_->end()) { + *err = "ignoring duplicate subsystem entry"; + return false; + } + + subsystem_.name_ = args[1]; + + return true; +} + +bool SubsystemParser::ParseDevName(std::vector&& args, std::string* err) { + if (args[1] == "uevent_devname") { + subsystem_.devname_source_ = Subsystem::DevnameSource::DEVNAME_UEVENT_DEVNAME; + return true; + } + if (args[1] == "uevent_devpath") { + subsystem_.devname_source_ = Subsystem::DevnameSource::DEVNAME_UEVENT_DEVPATH; + return true; + } + + *err = "invalid devname '" + args[1] + "'"; + return false; +} + +bool SubsystemParser::ParseDirName(std::vector&& args, std::string* err) { + if (args[1].front() != '/') { + *err = "dirname '" + args[1] + " ' does not start with '/'"; + return false; + } + + subsystem_.dir_name_ = args[1]; + return true; +} + +bool SubsystemParser::ParseLineSection(std::vector&& args, int line, std::string* err) { + using OptionParser = + bool (SubsystemParser::*)(std::vector && args, std::string * err); + static class OptionParserMap : public KeywordMap { + private: + const Map& map() const override { + // clang-format off + static const Map option_parsers = { + {"devname", {1, 1, &SubsystemParser::ParseDevName}}, + {"dirname", {1, 1, &SubsystemParser::ParseDirName}}, + }; + // clang-format on + return option_parsers; + } + } parser_map; + + auto parser = parser_map.FindFunction(args, err); + + if (!parser) { + return false; + } + + return (this->*parser)(std::move(args), err); +} + +void SubsystemParser::EndSection() { + subsystems_->emplace_back(std::move(subsystem_)); +} diff --git a/init/ueventd_parser.h b/init/ueventd_parser.h new file mode 100644 index 000000000..c1ce97692 --- /dev/null +++ b/init/ueventd_parser.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _INIT_UEVENTD_PARSER_H +#define _INIT_UEVENTD_PARSER_H + +#include +#include + +#include "devices.h" +#include "init_parser.h" + +class SubsystemParser : public SectionParser { + public: + SubsystemParser(std::vector* subsystems) : subsystems_(subsystems) {} + bool ParseSection(std::vector&& args, const std::string& filename, int line, + std::string* err) override; + bool ParseLineSection(std::vector&& args, int line, std::string* err) override; + void EndSection() override; + + private: + bool ParseDevName(std::vector&& args, std::string* err); + bool ParseDirName(std::vector&& args, std::string* err); + + Subsystem subsystem_; + std::vector* subsystems_; +}; + +bool ParsePermissionsLine(std::vector&& args, std::string* err, + std::vector* out_sysfs_permissions, + std::vector* out_dev_permissions); + +#endif