ueventd: Break devices.cpp into discrete classes
devices.cpp handles too many things for creating one class. This
change breaks it up into various files and classes.
* Parsing is moved to ueventd_parser.cpp
* Reading from the uevent socket and Cold booting is moved to a
UeventListener class, in uevent_listener.cpp
* Firmware handling is moved to firmware_handler.cpp
* The remaining contents form a DeviceHandler class within devices.cpp
Bug: 33785894
Test: boot bullhead x40, observe no major differences in /dev and /sys
Test: boot sailfish x40, observe no major differences in /dev and /sys
Test: init unit tests
Change-Id: I846a2e5995fbb344c7a8e349065c18a934fa6aba
diff --git a/init/Android.bp b/init/Android.bp
index 80d5c42..af1e9d3 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -66,11 +66,14 @@
"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 e0351a3..40f9740 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 <dirent.h>
#include <errno.h>
-#include <fcntl.h>
#include <fnmatch.h>
-#include <grp.h>
-#include <libgen.h>
-#include <linux/netlink.h>
-#include <pwd.h>
-#include <stddef.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/sendfile.h>
-#include <sys/socket.h>
#include <sys/sysmacros.h>
-#include <sys/time.h>
-#include <sys/un.h>
-#include <sys/wait.h>
#include <unistd.h>
-#include <algorithm>
#include <memory>
-#include <thread>
-#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <cutils/uevent.h>
#include <private/android_filesystem_config.h>
#include <selinux/android.h>
-#include <selinux/label.h>
#include <selinux/selinux.h>
-#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<Permissions> dev_permissions;
-std::vector<SysfsPermissions> sysfs_permissions;
-
-bool ParsePermissionsLine(std::vector<std::string>&& 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: <perm> <uid> <gid>
- 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<Subsystem> 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<std::string>&& 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<std::string>&& 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<std::string>&& 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<std::string>&& args, int line, std::string* err) {
- using OptionParser =
- bool (SubsystemParser::*)(std::vector<std::string> && args, std::string * err);
- static class OptionParserMap : public KeywordMap<OptionParser> {
- 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<mode_t, uid_t, gid_t> get_device_permissions(
- const std::string& path, const std::vector<std::string>& 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<std::string>& 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<const char*> 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<std::string> 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 @@
* 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 @@
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<std::string> 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<mode_t, uid_t, gid_t> DeviceHandler::GetDevicePermissions(
+ const std::string& path, const std::vector<std::string>& 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<std::string>& links) const {
+ auto[mode, uid, gid] = GetDevicePermissions(path, links);
+ mode |= (block ? S_IFBLK : S_IFCHR);
+
+ char* secontext = nullptr;
+ if (sehandle_) {
+ std::vector<const char*> 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<std::string> 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 @@
auto name_string = path.substr(start, length);
std::vector<std::string> 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 @@
// 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 @@
}
}
-std::vector<std::string> get_block_device_symlinks(uevent* uevent) {
+std::vector<std::string> 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 @@
}
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 @@
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<std::string>& links) {
+void DeviceHandler::HandleDevice(const std::string& action, const std::string& devpath, int block,
+ int major, int minor, const std::vector<std::string>& 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<std::string> 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<Permissions> dev_permissions,
+ std::vector<SysfsPermissions> sysfs_permissions,
+ std::vector<Subsystem> 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<coldboot_action_t(uevent* uevent)> 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<DIR, decltype(&closedir)> 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<Permissions>{}, std::vector<SysfsPermissions>{},
+ std::vector<Subsystem>{}) {}
diff --git a/init/devices.h b/init/devices.h
index 647b4c4..50f49fc 100644
--- a/init/devices.h
+++ b/init/devices.h
@@ -20,35 +20,14 @@
#include <sys/stat.h>
#include <sys/types.h>
-#include <functional>
+#include <algorithm>
#include <string>
#include <vector>
-#include "init_parser.h"
+#include <android-base/file.h>
+#include <selinux/label.h>
-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 @@
// 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 @@
DevnameSource devname_source_;
};
-class SubsystemParser : public SectionParser {
+class PlatformDeviceList {
public:
- SubsystemParser() {}
- bool ParseSection(std::vector<std::string>&& args, const std::string& filename, int line,
- std::string* err) override;
- bool ParseLineSection(std::vector<std::string>&& 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<std::string>&& args, std::string* err);
- bool ParseDirName(std::vector<std::string>&& args, std::string* err);
-
- Subsystem subsystem_;
+ std::vector<std::string> platform_devices_;
};
-bool ParsePermissionsLine(std::vector<std::string>&& args, std::string* err, bool is_sysfs);
-typedef std::function<coldboot_action_t(struct uevent* uevent)> 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<Permissions> dev_permissions,
+ std::vector<SysfsPermissions> sysfs_permissions,
+ std::vector<Subsystem> subsystems);
+ ~DeviceHandler(){};
+
+ void HandleDeviceEvent(const Uevent& uevent);
+ std::vector<std::string> GetBlockDeviceSymlinks(const Uevent& uevent) const;
+
+ private:
+ void FixupSysPermissions(const std::string& upath, const std::string& subsystem) const;
+ std::tuple<mode_t, uid_t, gid_t> GetDevicePermissions(
+ const std::string& path, const std::vector<std::string>& links) const;
+ void MakeDevice(const std::string& path, int block, int major, int minor,
+ const std::vector<std::string>& links) const;
+ std::vector<std::string> GetCharacterDeviceSymlinks(const Uevent& uevent) const;
+ void HandleDevice(const std::string& action, const std::string& devpath, int block, int major,
+ int minor, const std::vector<std::string>& links) const;
+ void HandlePlatformDeviceEvent(const Uevent& uevent);
+ void HandleBlockDeviceEvent(const Uevent& uevent) const;
+ void HandleGenericDeviceEvent(const Uevent& uevent) const;
+
+ std::vector<Permissions> dev_permissions_;
+ std::vector<SysfsPermissions> sysfs_permissions_;
+ std::vector<Subsystem> subsystems_;
+ PlatformDeviceList platform_devices_;
+ selabel_handle* sehandle_;
+};
// Exposed for testing
-extern std::vector<std::string> platform_devices;
-bool find_platform_device(const std::string& path, std::string* out_path);
-std::vector<std::string> get_character_device_symlinks(uevent* uevent);
-std::vector<std::string> 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 66521db..41b101b 100644
--- a/init/devices_test.cpp
+++ b/init/devices_test.cpp
@@ -22,167 +22,180 @@
#include <android-base/scopeguard.h>
#include <gtest/gtest.h>
-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 <std::vector<std::string> (*Function)(uevent*)>
-void test_get_symlinks(const std::string& platform_device_name, uevent* uevent,
- const std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> expected_result{"/dev/usb/ttyname"};
- test_get_symlinks<get_character_device_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<std::string> expected_result;
- test_get_symlinks<get_character_device_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<std::string> expected_result;
- test_get_symlinks<get_character_device_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<std::string> expected_result;
- test_get_symlinks<get_character_device_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<std::string> expected_result;
- test_get_symlinks<get_character_device_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<std::string> expected_result;
- test_get_symlinks<get_character_device_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<std::string> expected_result;
- test_get_symlinks<get_character_device_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<std::string> expected_result;
- test_get_symlinks<get_character_device_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<std::string> expected_result{"/dev/block/platform/soc.0/f9824900.sdhci/mmcblk0"};
- test_get_symlinks<get_block_device_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 @@
"/dev/block/platform/soc.0/f9824900.sdhci/mmcblk0p1",
};
- test_get_symlinks<get_block_device_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 @@
"/dev/block/platform/soc.0/f9824900.sdhci/mmcblk0p1",
};
- test_get_symlinks<get_block_device_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 @@
"/dev/block/platform/soc.0/f9824900.sdhci/mmcblk0p1",
};
- test_get_symlinks<get_block_device_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<std::string> expected_result{"/dev/block/pci/pci0000:00/0000:00:1f.2/mmcblk0"};
- test_get_symlinks<get_block_device_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<std::string> expected_result{};
- test_get_symlinks<get_block_device_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<std::string> expected_result{"/dev/block/vbd/1234/mmcblk0"};
- test_get_symlinks<get_block_device_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<std::string> expected_result{};
- test_get_symlinks<get_block_device_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<std::string> expected_result;
- test_get_symlinks<get_block_device_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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 0000000..1471aeb
--- /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 <fcntl.h>
+#include <sys/sendfile.h>
+#include <unistd.h>
+
+#include <string>
+#include <thread>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+
+#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 0000000..be9daae
--- /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 8b5d15e..4e462d7 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 9d2a0d1..8a7d9a2 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 @@
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 @@
// Eligible first stage mount candidates, only allow /system, /vendor and/or /odm.
std::vector<fstab_rec*> mount_fstab_recs_;
std::set<std::string> required_devices_partition_names_;
+ DeviceHandler device_handler_;
+ UeventListener uevent_listener_;
};
class FirstStageMountVBootV1 : public FirstStageMount {
@@ -83,7 +87,7 @@
~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 @@
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 @@
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 @@
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<std::string> links = get_block_device_symlinks(uevent);
+ std::vector<std::string> 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 bd8a178..722ebb2 100644
--- a/init/init_parser.h
+++ b/init/init_parser.h
@@ -66,9 +66,9 @@
// be written.
using LineCallback = std::function<bool(std::vector<std::string>&&, 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 0000000..1095665
--- /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 <string>
+
+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 0000000..27c5d23
--- /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 <fcntl.h>
+#include <poll.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <memory>
+
+#include <android-base/logging.h>
+#include <cutils/uevent.h>
+
+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<DIR, decltype(&closedir)> 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<DIR, decltype(&closedir)> 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 0000000..ba31aaa
--- /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 <dirent.h>
+
+#include <functional>
+
+#include <android-base/unique_fd.h>
+
+#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<RegenerationAction(const Uevent&)>;
+using PollCallback = std::function<void(const Uevent&)>;
+
+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 8c0c574..bd21a3e 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -18,7 +18,6 @@
#include <ctype.h>
#include <fcntl.h>
-#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
@@ -30,9 +29,44 @@
#include <selinux/selinux.h>
#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<Subsystem> subsystems;
+ parser.AddSectionParser("subsystem", std::make_unique<SubsystemParser>(&subsystems));
+
+ using namespace std::placeholders;
+ std::vector<SysfsPermissions> sysfs_permissions;
+ std::vector<Permissions> 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 @@
cb.func_log = selinux_klog_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
- Parser& parser = Parser::GetInstance();
- parser.AddSectionParser("subsystem", std::make_unique<SubsystemParser>());
- 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 0000000..7156e76
--- /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 <grp.h>
+#include <pwd.h>
+
+#include "keyword_map.h"
+
+bool ParsePermissionsLine(std::vector<std::string>&& args, std::string* err,
+ std::vector<SysfsPermissions>* out_sysfs_permissions,
+ std::vector<Permissions>* 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: <perm> <uid> <gid>
+ 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<std::string>&& 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<std::string>&& 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<std::string>&& 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<std::string>&& args, int line, std::string* err) {
+ using OptionParser =
+ bool (SubsystemParser::*)(std::vector<std::string> && args, std::string * err);
+ static class OptionParserMap : public KeywordMap<OptionParser> {
+ 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 0000000..c1ce976
--- /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 <string>
+#include <vector>
+
+#include "devices.h"
+#include "init_parser.h"
+
+class SubsystemParser : public SectionParser {
+ public:
+ SubsystemParser(std::vector<Subsystem>* subsystems) : subsystems_(subsystems) {}
+ bool ParseSection(std::vector<std::string>&& args, const std::string& filename, int line,
+ std::string* err) override;
+ bool ParseLineSection(std::vector<std::string>&& args, int line, std::string* err) override;
+ void EndSection() override;
+
+ private:
+ bool ParseDevName(std::vector<std::string>&& args, std::string* err);
+ bool ParseDirName(std::vector<std::string>&& args, std::string* err);
+
+ Subsystem subsystem_;
+ std::vector<Subsystem>* subsystems_;
+};
+
+bool ParsePermissionsLine(std::vector<std::string>&& args, std::string* err,
+ std::vector<SysfsPermissions>* out_sysfs_permissions,
+ std::vector<Permissions>* out_dev_permissions);
+
+#endif