Merge "ueventd: allow using external firmware handlers"
diff --git a/init/Android.bp b/init/Android.bp
index 9c4b5b9..9714014 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -224,6 +224,7 @@
 
     srcs: [
         "devices_test.cpp",
+        "firmware_handler_test.cpp",
         "init_test.cpp",
         "keychords_test.cpp",
         "persistent_properties_test.cpp",
diff --git a/init/README.ueventd.md b/init/README.ueventd.md
index 7e90e0c..053ebf8 100644
--- a/init/README.ueventd.md
+++ b/init/README.ueventd.md
@@ -82,7 +82,7 @@
 
 ## Firmware loading
 ----------------
-Ueventd automatically serves firmware requests by searching through a list of firmware directories
+Ueventd by default serves firmware requests by searching through a list of firmware directories
 for a file matching the uevent `FIRMWARE`. It then forks a process to serve this firmware to the
 kernel.
 
@@ -100,6 +100,26 @@
 Ueventd will wait until after `post-fs` in init, to keep retrying before believing the firmwares are
 not present.
 
+The exact firmware file to be served can be customized by running an external program by a
+`external_firmware_handler` line in a ueventd.rc file. This line takes the format of
+
+    external_firmware_handler <devpath> <user name to run as> <path to external program>
+For example
+
+    external_firmware_handler /devices/leds/red/firmware/coeffs.bin system /vendor/bin/led_coeffs.bin
+Will launch `/vendor/bin/led_coeffs.bin` as the system user instead of serving the default firmware
+for `/devices/leds/red/firmware/coeffs.bin`.
+
+Ueventd will provide the uevent `DEVPATH` and `FIRMWARE` to this external program on the environment
+via environment variables with the same names. Ueventd will use the string written to stdout as the
+new name of the firmware to load. It will still look for the new firmware in the list of firmware
+directories stated above. It will also reject file names with `..` in them, to prevent leaving these
+directories. If stdout cannot be read, or the program returns with any exit code other than
+`EXIT_SUCCESS`, or the program crashes, the default firmware from the uevent will be loaded.
+
+Ueventd will additionally log all messages sent to stderr from the external program to the serial
+console after the external program has exited.
+
 ## Coldboot
 --------
 Ueventd must create devices in `/dev` for all devices that have already sent their uevents before
diff --git a/init/firmware_handler.cpp b/init/firmware_handler.cpp
index c067f6f..1dce2d5 100644
--- a/init/firmware_handler.cpp
+++ b/init/firmware_handler.cpp
@@ -17,6 +17,10 @@
 #include "firmware_handler.h"
 
 #include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
 #include <sys/sendfile.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -26,25 +30,29 @@
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 
+using android::base::ReadFdToString;
+using android::base::Socketpair;
+using android::base::Split;
 using android::base::Timer;
+using android::base::Trim;
 using android::base::unique_fd;
 using android::base::WriteFully;
 
 namespace android {
 namespace init {
 
-static void LoadFirmware(const Uevent& uevent, const std::string& root, int fw_fd, size_t fw_size,
-                         int loading_fd, int data_fd) {
+static void LoadFirmware(const std::string& firmware, const std::string& root, int fw_fd,
+                         size_t fw_size, int loading_fd, int data_fd) {
     // Start transfer.
     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
-                    << "' }";
+        PLOG(ERROR) << "firmware: sendfile failed { '" << root << "', '" << firmware << "' }";
     }
 
     // Tell the firmware whether to abort or commit.
@@ -56,36 +64,151 @@
     return access("/dev/.booting", F_OK) == 0;
 }
 
-FirmwareHandler::FirmwareHandler(std::vector<std::string> firmware_directories)
-    : firmware_directories_(std::move(firmware_directories)) {}
+FirmwareHandler::FirmwareHandler(std::vector<std::string> firmware_directories,
+                                 std::vector<ExternalFirmwareHandler> external_firmware_handlers)
+    : firmware_directories_(std::move(firmware_directories)),
+      external_firmware_handlers_(std::move(external_firmware_handlers)) {}
 
-void FirmwareHandler::ProcessFirmwareEvent(const Uevent& uevent) {
-    int booting = IsBooting();
+Result<std::string> FirmwareHandler::RunExternalHandler(const std::string& handler, uid_t uid,
+                                                        const Uevent& uevent) const {
+    unique_fd child_stdout;
+    unique_fd parent_stdout;
+    if (!Socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, &child_stdout, &parent_stdout)) {
+        return ErrnoError() << "Socketpair() for stdout failed";
+    }
 
+    unique_fd child_stderr;
+    unique_fd parent_stderr;
+    if (!Socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, &child_stderr, &parent_stderr)) {
+        return ErrnoError() << "Socketpair() for stderr failed";
+    }
+
+    signal(SIGCHLD, SIG_DFL);
+
+    auto pid = fork();
+    if (pid < 0) {
+        return ErrnoError() << "fork() failed";
+    }
+
+    if (pid == 0) {
+        setenv("FIRMWARE", uevent.firmware.c_str(), 1);
+        setenv("DEVPATH", uevent.path.c_str(), 1);
+        parent_stdout.reset();
+        parent_stderr.reset();
+        close(STDOUT_FILENO);
+        close(STDERR_FILENO);
+        dup2(child_stdout.get(), STDOUT_FILENO);
+        dup2(child_stderr.get(), STDERR_FILENO);
+
+        auto args = Split(handler, " ");
+        std::vector<char*> c_args;
+        for (auto& arg : args) {
+            c_args.emplace_back(arg.data());
+        }
+        c_args.emplace_back(nullptr);
+
+        if (setuid(uid) != 0) {
+            fprintf(stderr, "setuid() failed: %s", strerror(errno));
+            _exit(EXIT_FAILURE);
+        }
+
+        execv(c_args[0], c_args.data());
+        fprintf(stderr, "exec() failed: %s", strerror(errno));
+        _exit(EXIT_FAILURE);
+    }
+
+    child_stdout.reset();
+    child_stderr.reset();
+
+    int status;
+    pid_t waited_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
+    if (waited_pid == -1) {
+        return ErrnoError() << "waitpid() failed";
+    }
+
+    std::string stdout_content;
+    if (!ReadFdToString(parent_stdout.get(), &stdout_content)) {
+        return ErrnoError() << "ReadFdToString() for stdout failed";
+    }
+
+    std::string stderr_content;
+    if (ReadFdToString(parent_stderr.get(), &stderr_content)) {
+        auto messages = Split(stderr_content, "\n");
+        for (const auto& message : messages) {
+            if (!message.empty()) {
+                LOG(ERROR) << "External Firmware Handler: " << message;
+            }
+        }
+    } else {
+        LOG(ERROR) << "ReadFdToString() for stderr failed";
+    }
+
+    if (WIFEXITED(status)) {
+        if (WEXITSTATUS(status) == EXIT_SUCCESS) {
+            return Trim(stdout_content);
+        } else {
+            return Error() << "exited with status " << WEXITSTATUS(status);
+        }
+    } else if (WIFSIGNALED(status)) {
+        return Error() << "killed by signal " << WTERMSIG(status);
+    }
+
+    return Error() << "unexpected exit status " << status;
+}
+
+std::string FirmwareHandler::GetFirmwarePath(const Uevent& uevent) const {
+    for (const auto& external_handler : external_firmware_handlers_) {
+        if (external_handler.devpath == uevent.path) {
+            LOG(INFO) << "Launching external firmware handler '" << external_handler.handler_path
+                      << "' for devpath: '" << uevent.path << "' firmware: '" << uevent.firmware
+                      << "'";
+
+            auto result =
+                    RunExternalHandler(external_handler.handler_path, external_handler.uid, uevent);
+            if (!result) {
+                LOG(ERROR) << "Using default firmware; External firmware handler failed: "
+                           << result.error();
+                return uevent.firmware;
+            }
+            if (result->find("..") != std::string::npos) {
+                LOG(ERROR) << "Using default firmware; External firmware handler provided an "
+                              "invalid path, '"
+                           << *result << "'";
+                return uevent.firmware;
+            }
+            LOG(INFO) << "Loading firmware '" << *result << "' in place of '" << uevent.firmware
+                      << "'";
+            return *result;
+        }
+    }
     LOG(INFO) << "firmware: loading '" << uevent.firmware << "' for '" << uevent.path << "'";
+    return uevent.firmware;
+}
 
-    std::string root = "/sys" + uevent.path;
+void FirmwareHandler::ProcessFirmwareEvent(const std::string& root,
+                                           const std::string& firmware) const {
     std::string loading = root + "/loading";
     std::string data = root + "/data";
 
     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;
+        PLOG(ERROR) << "couldn't open firmware loading fd for " << firmware;
         return;
     }
 
     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;
+        PLOG(ERROR) << "couldn't open firmware data fd for " << firmware;
         return;
     }
 
     std::vector<std::string> attempted_paths_and_errors;
 
+    int booting = IsBooting();
 try_loading_again:
     attempted_paths_and_errors.clear();
     for (const auto& firmware_directory : firmware_directories_) {
-        std::string file = firmware_directory + uevent.firmware;
+        std::string file = firmware_directory + firmware;
         unique_fd fw_fd(open(file.c_str(), O_RDONLY | O_CLOEXEC));
         if (fw_fd == -1) {
             attempted_paths_and_errors.emplace_back("firmware: attempted " + file +
@@ -98,7 +221,7 @@
                                                     ", fstat failed: " + strerror(errno));
             continue;
         }
-        LoadFirmware(uevent, root, fw_fd, sb.st_size, loading_fd, data_fd);
+        LoadFirmware(firmware, root, fw_fd, sb.st_size, loading_fd, data_fd);
         return;
     }
 
@@ -110,7 +233,7 @@
         goto try_loading_again;
     }
 
-    LOG(ERROR) << "firmware: could not find firmware for " << uevent.firmware;
+    LOG(ERROR) << "firmware: could not find firmware for " << firmware;
     for (const auto& message : attempted_paths_and_errors) {
         LOG(ERROR) << message;
     }
@@ -129,7 +252,8 @@
     }
     if (pid == 0) {
         Timer t;
-        ProcessFirmwareEvent(uevent);
+        auto firmware = GetFirmwarePath(uevent);
+        ProcessFirmwareEvent("/sys" + uevent.path, firmware);
         LOG(INFO) << "loading " << uevent.path << " took " << t;
         _exit(EXIT_SUCCESS);
     }
diff --git a/init/firmware_handler.h b/init/firmware_handler.h
index 3996096..b4138f1 100644
--- a/init/firmware_handler.h
+++ b/init/firmware_handler.h
@@ -14,32 +14,48 @@
  * limitations under the License.
  */
 
-#ifndef _INIT_FIRMWARE_HANDLER_H
-#define _INIT_FIRMWARE_HANDLER_H
+#pragma once
+
+#include <pwd.h>
 
 #include <string>
 #include <vector>
 
+#include "result.h"
 #include "uevent.h"
 #include "uevent_handler.h"
 
 namespace android {
 namespace init {
 
+struct ExternalFirmwareHandler {
+    ExternalFirmwareHandler(std::string devpath, uid_t uid, std::string handler_path)
+        : devpath(std::move(devpath)), uid(uid), handler_path(std::move(handler_path)) {}
+    std::string devpath;
+    uid_t uid;
+    std::string handler_path;
+};
+
 class FirmwareHandler : public UeventHandler {
   public:
-    explicit FirmwareHandler(std::vector<std::string> firmware_directories);
+    FirmwareHandler(std::vector<std::string> firmware_directories,
+                    std::vector<ExternalFirmwareHandler> external_firmware_handlers);
     virtual ~FirmwareHandler() = default;
 
     void HandleUevent(const Uevent& uevent) override;
 
   private:
-    void ProcessFirmwareEvent(const Uevent& uevent);
+    friend void FirmwareTestWithExternalHandler(const std::string& test_name,
+                                                bool expect_new_firmware);
+
+    Result<std::string> RunExternalHandler(const std::string& handler, uid_t uid,
+                                           const Uevent& uevent) const;
+    std::string GetFirmwarePath(const Uevent& uevent) const;
+    void ProcessFirmwareEvent(const std::string& root, const std::string& firmware) const;
 
     std::vector<std::string> firmware_directories_;
+    std::vector<ExternalFirmwareHandler> external_firmware_handlers_;
 };
 
 }  // namespace init
 }  // namespace android
-
-#endif
diff --git a/init/firmware_handler_test.cpp b/init/firmware_handler_test.cpp
new file mode 100644
index 0000000..7bb603c
--- /dev/null
+++ b/init/firmware_handler_test.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2019 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 <stdlib.h>
+#include <iostream>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+
+#include "uevent.h"
+
+using android::base::GetExecutablePath;
+using namespace std::literals;
+
+namespace android {
+namespace init {
+
+void FirmwareTestWithExternalHandler(const std::string& test_name, bool expect_new_firmware) {
+    auto test_path = GetExecutablePath() + " firmware " + test_name;
+    auto external_firmware_handler = ExternalFirmwareHandler(
+            "/devices/led/firmware/test_firmware001.bin", getuid(), test_path);
+
+    auto firmware_handler = FirmwareHandler({"/test"}, {external_firmware_handler});
+
+    auto uevent = Uevent{
+            .path = "/devices/led/firmware/test_firmware001.bin",
+            .firmware = "test_firmware001.bin",
+    };
+
+    if (expect_new_firmware) {
+        EXPECT_EQ("other_firmware001.bin", firmware_handler.GetFirmwarePath(uevent));
+    } else {
+        EXPECT_EQ("test_firmware001.bin", firmware_handler.GetFirmwarePath(uevent));
+    }
+
+    // Always test the base case that the handler isn't invoked if the devpath doesn't match.
+    auto uevent_different_path = Uevent{
+            .path = "/devices/led/not/mine",
+            .firmware = "test_firmware001.bin",
+    };
+    EXPECT_EQ("test_firmware001.bin", firmware_handler.GetFirmwarePath(uevent_different_path));
+}
+
+TEST(firmware_handler, HandleChange) {
+    FirmwareTestWithExternalHandler("HandleChange", true);
+}
+
+int HandleChange(int argc, char** argv) {
+    // Assert that the environment is set up correctly.
+    if (getenv("DEVPATH") != "/devices/led/firmware/test_firmware001.bin"s) {
+        std::cerr << "$DEVPATH not set correctly" << std::endl;
+        return EXIT_FAILURE;
+    }
+    if (getenv("FIRMWARE") != "test_firmware001.bin"s) {
+        std::cerr << "$FIRMWARE not set correctly" << std::endl;
+        return EXIT_FAILURE;
+    }
+    std::cout << "other_firmware001.bin" << std::endl;
+    return 0;
+}
+
+TEST(firmware_handler, HandleAbort) {
+    FirmwareTestWithExternalHandler("HandleAbort", false);
+}
+
+int HandleAbort(int argc, char** argv) {
+    abort();
+    return 0;
+}
+
+TEST(firmware_handler, HandleFailure) {
+    FirmwareTestWithExternalHandler("HandleFailure", false);
+}
+
+int HandleFailure(int argc, char** argv) {
+    std::cerr << "Failed" << std::endl;
+    return EXIT_FAILURE;
+}
+
+TEST(firmware_handler, HandleBadPath) {
+    FirmwareTestWithExternalHandler("HandleBadPath", false);
+}
+
+int HandleBadPath(int argc, char** argv) {
+    std::cout << "../firmware.bin";
+    return 0;
+}
+
+}  // namespace init
+}  // namespace android
+
+// init_test.cpp contains the main entry point for all init tests.
+int FirmwareTestChildMain(int argc, char** argv) {
+    if (argc < 3) {
+        return 1;
+    }
+
+#define RunTest(testname)                           \
+    if (argv[2] == std::string(#testname)) {        \
+        return android::init::testname(argc, argv); \
+    }
+
+    RunTest(HandleChange);
+    RunTest(HandleAbort);
+    RunTest(HandleFailure);
+    RunTest(HandleBadPath);
+
+#undef RunTest
+    return 1;
+}
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 0411214..315d584 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -221,3 +221,19 @@
 
 }  // namespace init
 }  // namespace android
+
+int SubcontextTestChildMain(int, char**);
+int FirmwareTestChildMain(int, char**);
+
+int main(int argc, char** argv) {
+    if (argc > 1 && !strcmp(argv[1], "subcontext")) {
+        return SubcontextTestChildMain(argc, argv);
+    }
+
+    if (argc > 1 && !strcmp(argv[1], "firmware")) {
+        return FirmwareTestChildMain(argc, argv);
+    }
+
+    testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/init/subcontext_test.cpp b/init/subcontext_test.cpp
index dcbff82..7565eb6 100644
--- a/init/subcontext_test.cpp
+++ b/init/subcontext_test.cpp
@@ -224,12 +224,8 @@
 }  // namespace init
 }  // namespace android
 
-int main(int argc, char** argv) {
-    if (argc > 1 && !strcmp(basename(argv[1]), "subcontext")) {
-        auto test_function_map = android::init::BuildTestFunctionMap();
-        return android::init::SubcontextMain(argc, argv, &test_function_map);
-    }
-
-    testing::InitGoogleTest(&argc, argv);
-    return RUN_ALL_TESTS();
+// init_test.cpp contains the main entry point for all init tests.
+int SubcontextTestChildMain(int argc, char** argv) {
+    auto test_function_map = android::init::BuildTestFunctionMap();
+    return android::init::SubcontextMain(argc, argv, &test_function_map);
 }
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 6741e2a..59f91ee 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -296,7 +296,8 @@
             std::move(ueventd_configuration.sysfs_permissions),
             std::move(ueventd_configuration.subsystems), android::fs_mgr::GetBootDevices(), true));
     uevent_handlers.emplace_back(std::make_unique<FirmwareHandler>(
-            std::move(ueventd_configuration.firmware_directories)));
+            std::move(ueventd_configuration.firmware_directories),
+            std::move(ueventd_configuration.external_firmware_handlers)));
 
     if (ueventd_configuration.enable_modalias_handling) {
         std::vector<std::string> base_paths = {"/odm/lib/modules", "/vendor/lib/modules"};
diff --git a/init/ueventd_parser.cpp b/init/ueventd_parser.cpp
index 1ca1715..a74b247 100644
--- a/init/ueventd_parser.cpp
+++ b/init/ueventd_parser.cpp
@@ -88,6 +88,31 @@
     return {};
 }
 
+Result<void> ParseExternalFirmwareHandlerLine(
+        std::vector<std::string>&& args,
+        std::vector<ExternalFirmwareHandler>* external_firmware_handlers) {
+    if (args.size() != 4) {
+        return Error() << "external_firmware_handler lines must have exactly 3 parameters";
+    }
+
+    if (std::find_if(external_firmware_handlers->begin(), external_firmware_handlers->end(),
+                     [&args](const auto& other) { return other.devpath == args[2]; }) !=
+        external_firmware_handlers->end()) {
+        return Error() << "found a previous external_firmware_handler with the same devpath, '"
+                       << args[2] << "'";
+    }
+
+    passwd* pwd = getpwnam(args[2].c_str());
+    if (!pwd) {
+        return ErrnoError() << "invalid handler uid'" << args[2] << "'";
+    }
+
+    ExternalFirmwareHandler handler(std::move(args[1]), pwd->pw_uid, std::move(args[3]));
+    external_firmware_handlers->emplace_back(std::move(handler));
+
+    return {};
+}
+
 Result<void> ParseEnabledDisabledLine(std::vector<std::string>&& args, bool* feature) {
     if (args.size() != 2) {
         return Error() << args[0] << " lines take exactly one parameter";
@@ -211,6 +236,9 @@
     parser.AddSingleLineParser("firmware_directories",
                                std::bind(ParseFirmwareDirectoriesLine, _1,
                                          &ueventd_configuration.firmware_directories));
+    parser.AddSingleLineParser("external_firmware_handler",
+                               std::bind(ParseExternalFirmwareHandlerLine, _1,
+                                         &ueventd_configuration.external_firmware_handlers));
     parser.AddSingleLineParser("modalias_handling",
                                std::bind(ParseEnabledDisabledLine, _1,
                                          &ueventd_configuration.enable_modalias_handling));
diff --git a/init/ueventd_parser.h b/init/ueventd_parser.h
index b54dba8..eaafa5a 100644
--- a/init/ueventd_parser.h
+++ b/init/ueventd_parser.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "devices.h"
+#include "firmware_handler.h"
 
 namespace android {
 namespace init {
@@ -29,6 +30,7 @@
     std::vector<SysfsPermissions> sysfs_permissions;
     std::vector<Permissions> dev_permissions;
     std::vector<std::string> firmware_directories;
+    std::vector<ExternalFirmwareHandler> external_firmware_handlers;
     bool enable_modalias_handling = false;
     size_t uevent_socket_rcvbuf_size = 0;
     bool enable_parallel_restorecon = false;
diff --git a/init/ueventd_parser_test.cpp b/init/ueventd_parser_test.cpp
index 885e79d..172ba0b 100644
--- a/init/ueventd_parser_test.cpp
+++ b/init/ueventd_parser_test.cpp
@@ -20,6 +20,8 @@
 #include <gtest/gtest.h>
 #include <private/android_filesystem_config.h>
 
+#include "firmware_handler.h"
+
 namespace android {
 namespace init {
 
@@ -93,7 +95,7 @@
             {"test_devname2", Subsystem::DEVNAME_UEVENT_DEVNAME, "/dev"},
             {"test_devpath_dirname", Subsystem::DEVNAME_UEVENT_DEVPATH, "/dev/graphics"}};
 
-    TestUeventdFile(ueventd_file, {subsystems, {}, {}, {}});
+    TestUeventdFile(ueventd_file, {subsystems, {}, {}, {}, {}});
 }
 
 TEST(ueventd_parser, Permissions) {
@@ -119,7 +121,7 @@
             {"/sys/devices/virtual/*/input", "poll_delay", 0660, AID_ROOT, AID_INPUT},
     };
 
-    TestUeventdFile(ueventd_file, {{}, sysfs_permissions, permissions, {}});
+    TestUeventdFile(ueventd_file, {{}, sysfs_permissions, permissions, {}, {}});
 }
 
 TEST(ueventd_parser, FirmwareDirectories) {
@@ -135,7 +137,52 @@
             "/more",
     };
 
-    TestUeventdFile(ueventd_file, {{}, {}, {}, firmware_directories});
+    TestUeventdFile(ueventd_file, {{}, {}, {}, firmware_directories, {}});
+}
+
+TEST(ueventd_parser, ExternalFirmwareHandlers) {
+    auto ueventd_file = R"(
+external_firmware_handler devpath root handler_path
+external_firmware_handler /devices/path/firmware/something001.bin system /vendor/bin/firmware_handler.sh
+external_firmware_handler /devices/path/firmware/something001.bin radio "/vendor/bin/firmware_handler.sh --has --arguments"
+)";
+
+    auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
+            {
+                    "devpath",
+                    AID_ROOT,
+                    "handler_path",
+            },
+            {
+                    "/devices/path/firmware/something001.bin",
+                    AID_SYSTEM,
+                    "/vendor/bin/firmware_handler.sh",
+            },
+            {
+                    "/devices/path/firmware/something001.bin",
+                    AID_RADIO,
+                    "/vendor/bin/firmware_handler.sh --has --arguments",
+            },
+    };
+
+    TestUeventdFile(ueventd_file, {{}, {}, {}, {}, external_firmware_handlers});
+}
+
+TEST(ueventd_parser, ExternalFirmwareHandlersDuplicate) {
+    auto ueventd_file = R"(
+external_firmware_handler devpath root handler_path
+external_firmware_handler devpath root handler_path2
+)";
+
+    auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
+            {
+                    "devpath",
+                    AID_ROOT,
+                    "handler_path",
+            },
+    };
+
+    TestUeventdFile(ueventd_file, {{}, {}, {}, {}, external_firmware_handlers});
 }
 
 TEST(ueventd_parser, UeventSocketRcvbufSize) {
@@ -144,7 +191,7 @@
 uevent_socket_rcvbuf_size 8M
 )";
 
-    TestUeventdFile(ueventd_file, {{}, {}, {}, {}, false, 8 * 1024 * 1024});
+    TestUeventdFile(ueventd_file, {{}, {}, {}, {}, {}, false, 8 * 1024 * 1024});
 }
 
 TEST(ueventd_parser, EnabledDisabledLines) {
@@ -154,7 +201,7 @@
 modalias_handling disabled
 )";
 
-    TestUeventdFile(ueventd_file, {{}, {}, {}, {}, false, 0, true});
+    TestUeventdFile(ueventd_file, {{}, {}, {}, {}, {}, false, 0, true});
 
     auto ueventd_file2 = R"(
 parallel_restorecon enabled
@@ -162,7 +209,7 @@
 parallel_restorecon disabled
 )";
 
-    TestUeventdFile(ueventd_file2, {{}, {}, {}, {}, true, 0, false});
+    TestUeventdFile(ueventd_file2, {{}, {}, {}, {}, {}, true, 0, false});
 }
 
 TEST(ueventd_parser, AllTogether) {
@@ -196,6 +243,8 @@
 /sys/devices/virtual/*/input   poll_delay  0660  root   input
 firmware_directories /more
 
+external_firmware_handler /devices/path/firmware/firmware001.bin root /vendor/bin/touch.sh
+
 uevent_socket_rcvbuf_size 6M
 modalias_handling enabled
 parallel_restorecon enabled
@@ -228,10 +277,15 @@
             "/more",
     };
 
+    auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
+            {"/devices/path/firmware/firmware001.bin", AID_ROOT, "/vendor/bin/touch.sh"},
+    };
+
     size_t uevent_socket_rcvbuf_size = 6 * 1024 * 1024;
 
-    TestUeventdFile(ueventd_file, {subsystems, sysfs_permissions, permissions, firmware_directories,
-                                   true, uevent_socket_rcvbuf_size, true});
+    TestUeventdFile(ueventd_file,
+                    {subsystems, sysfs_permissions, permissions, firmware_directories,
+                     external_firmware_handlers, true, uevent_socket_rcvbuf_size, true});
 }
 
 // All of these lines are ill-formed, so test that there is 0 output.
@@ -257,6 +311,11 @@
 parallel_restorecon
 parallel_restorecon enabled enabled
 parallel_restorecon blah
+
+external_firmware_handler
+external_firmware_handler blah blah
+external_firmware_handler blah blah blah blah
+
 )";
 
     TestUeventdFile(ueventd_file, {});