ueventd: allow using external firmware handlers

Userspace may want to load a different firmware than the one that the
kernel requests in some cases, therefore this change adds the ability
to ueventd to run an external handler that will determine the name of
the file that should actually be loaded.

Bug: 138352500
Test: unit tests
Change-Id: Ic5da37268fd78109f83ae52d1b903bf7322a5ee5
This commit is contained in:
Tom Cherry 2019-08-07 16:02:28 -07:00
parent 537e4af235
commit dcb3d15611
11 changed files with 427 additions and 39 deletions

View file

@ -224,6 +224,7 @@ cc_test {
srcs: [
"devices_test.cpp",
"firmware_handler_test.cpp",
"init_test.cpp",
"keychords_test.cpp",
"persistent_properties_test.cpp",

View file

@ -82,7 +82,7 @@ Note that `*` matches as a wildcard and can be used anywhere in a path.
## 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 @@ entries.
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

View file

@ -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 @@ static bool IsBooting() {
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 @@ try_loading_again:
", 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 @@ try_loading_again:
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 @@ void FirmwareHandler::HandleUevent(const Uevent& uevent) {
}
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);
}

View file

@ -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

View file

@ -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;
}

View file

@ -221,3 +221,19 @@ TEST(init, EventTriggerOrderMultipleFiles) {
} // 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();
}

View file

@ -224,12 +224,8 @@ BuiltinFunctionMap BuildTestFunctionMap() {
} // 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);
}

View file

@ -296,7 +296,8 @@ int ueventd_main(int argc, char** argv) {
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"};

View file

@ -88,6 +88,31 @@ Result<void> ParseFirmwareDirectoriesLine(std::vector<std::string>&& args,
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 @@ UeventdConfiguration ParseConfig(const std::vector<std::string>& configs) {
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));

View file

@ -20,6 +20,7 @@
#include <vector>
#include "devices.h"
#include "firmware_handler.h"
namespace android {
namespace init {
@ -29,6 +30,7 @@ struct UeventdConfiguration {
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;

View file

@ -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 @@ subsystem test_devpath_dirname
{"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 @@ TEST(ueventd_parser, Permissions) {
{"/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 @@ firmware_directories /more
"/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 8k
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 @@ parallel_restorecon enabled
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 @@ modalias_handling enabled
parallel_restorecon disabled
)";
TestUeventdFile(ueventd_file2, {{}, {}, {}, {}, true, 0, false});
TestUeventdFile(ueventd_file2, {{}, {}, {}, {}, {}, true, 0, false});
}
TEST(ueventd_parser, AllTogether) {
@ -196,6 +243,8 @@ subsystem test_devpath_dirname
/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 @@ parallel_restorecon enabled
"/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 @@ modalias_handling blah
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, {});