From ac474ff7a0745308dd8f67b66d405f2f78bd9413 Mon Sep 17 00:00:00 2001 From: Grzegorz Jaszczyk Date: Sun, 13 Oct 2024 14:55:38 +0000 Subject: [PATCH 1/2] firmware_handler: extract part responsible for running ext program to lib As a preparation for upcoming commit, extract part which is responsible for external program execution to libmodprobe so it can be later re-used not only for firmware handler but also for dynamic module options handler within libmodprobe. RunExternalHandler is moved entirely to separate library with two changes: - Setting env needed previously by firmware handler was made generic and now external handler can get envs_map containing pairs of env and its value which needs to be set. - "Firmware" was removed from one of the log since now it can be used in different context (s/External Firmware Handler/External Handler/) Bug: 335619610 Test: `atest CtsInitTestCases` passed, especially: [105/129] ueventd_parser#ExternalFirmwareHandlers: PASSED (0ms) [106/129] ueventd_parser#ExternalFirmwareHandlersDuplicate: PASSED (0ms) Change-Id: Ie07cee763278f224bd3c0acfbe06c44eb36d0a81 --- init/firmware_handler.cpp | 104 ++-------------- init/firmware_handler.h | 2 - libmodprobe/Android.bp | 2 + libmodprobe/exthandler.cpp | 131 ++++++++++++++++++++ libmodprobe/include/exthandler/exthandler.h | 23 ++++ 5 files changed, 164 insertions(+), 98 deletions(-) create mode 100644 libmodprobe/exthandler.cpp create mode 100644 libmodprobe/include/exthandler/exthandler.h diff --git a/init/firmware_handler.cpp b/init/firmware_handler.cpp index 01957eff0..dcfda52d6 100644 --- a/init/firmware_handler.cpp +++ b/init/firmware_handler.cpp @@ -38,6 +38,8 @@ #include #include +#include "exthandler/exthandler.h" + using android::base::ReadFdToString; using android::base::Socketpair; using android::base::Split; @@ -136,100 +138,6 @@ FirmwareHandler::FirmwareHandler(std::vector firmware_directories, : firmware_directories_(std::move(firmware_directories)), external_firmware_handlers_(std::move(external_firmware_handlers)) {} -Result FirmwareHandler::RunExternalHandler(const std::string& handler, uid_t uid, - gid_t gid, 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 c_args; - for (auto& arg : args) { - c_args.emplace_back(arg.data()); - } - c_args.emplace_back(nullptr); - - if (gid != 0) { - if (setgid(gid) != 0) { - fprintf(stderr, "setgid() failed: %s", strerror(errno)); - _exit(EXIT_FAILURE); - } - } - - 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.match(uevent.path)) { @@ -237,11 +145,15 @@ std::string FirmwareHandler::GetFirmwarePath(const Uevent& uevent) const { << "' for devpath: '" << uevent.path << "' firmware: '" << uevent.firmware << "'"; + std::unordered_map envs_map; + envs_map["FIRMWARE"] = uevent.firmware; + envs_map["DEVPATH"] = uevent.path; + auto result = RunExternalHandler(external_handler.handler_path, external_handler.uid, - external_handler.gid, uevent); + external_handler.gid, envs_map); if (!result.ok() && NeedsRerunExternalHandler()) { auto res = RunExternalHandler(external_handler.handler_path, external_handler.uid, - external_handler.gid, uevent); + external_handler.gid, envs_map); result = std::move(res); } if (!result.ok()) { diff --git a/init/firmware_handler.h b/init/firmware_handler.h index fceb392db..e5d353809 100644 --- a/init/firmware_handler.h +++ b/init/firmware_handler.h @@ -54,8 +54,6 @@ class FirmwareHandler : public UeventHandler { friend void FirmwareTestWithExternalHandler(const std::string& test_name, bool expect_new_firmware); - Result RunExternalHandler(const std::string& handler, uid_t uid, gid_t gid, - const Uevent& uevent) const; std::string GetFirmwarePath(const Uevent& uevent) const; void ProcessFirmwareEvent(const std::string& path, const std::string& firmware) const; bool ForEachFirmwareDirectory(std::function handler) const; diff --git a/libmodprobe/Android.bp b/libmodprobe/Android.bp index 12906cc39..78b4c83e3 100644 --- a/libmodprobe/Android.bp +++ b/libmodprobe/Android.bp @@ -13,6 +13,7 @@ cc_library_static { vendor_ramdisk_available: true, host_supported: true, srcs: [ + "exthandler.cpp", "libmodprobe.cpp", "libmodprobe_ext.cpp", ], @@ -30,6 +31,7 @@ cc_test { ], local_include_dirs: ["include/"], srcs: [ + "exthandler.cpp", "libmodprobe_test.cpp", "libmodprobe.cpp", "libmodprobe_ext_test.cpp", diff --git a/libmodprobe/exthandler.cpp b/libmodprobe/exthandler.cpp new file mode 100644 index 000000000..f48c25976 --- /dev/null +++ b/libmodprobe/exthandler.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using android::base::ErrnoError; +using android::base::Error; +using android::base::ReadFdToString; +using android::base::Result; +using android::base::Split; +using android::base::Trim; +using android::base::unique_fd; + +Result RunExternalHandler(const std::string& handler, uid_t uid, gid_t gid, + std::unordered_map& envs_map) { + 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) { + for (auto it = envs_map.begin(); it != envs_map.end(); ++it) { + setenv(it->first.c_str(), it->second.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 c_args; + for (auto& arg : args) { + c_args.emplace_back(arg.data()); + } + c_args.emplace_back(nullptr); + + if (gid != 0) { + if (setgid(gid) != 0) { + fprintf(stderr, "setgid() failed: %s", strerror(errno)); + _exit(EXIT_FAILURE); + } + } + + 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 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; +} diff --git a/libmodprobe/include/exthandler/exthandler.h b/libmodprobe/include/exthandler/exthandler.h new file mode 100644 index 000000000..232aa95a4 --- /dev/null +++ b/libmodprobe/include/exthandler/exthandler.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 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. + */ + +#pragma once +#include +#include + +android::base::Result RunExternalHandler( + const std::string& handler, uid_t uid, gid_t gid, + std::unordered_map& envs_map); From 3063d84e9c0162ba74670f4eb6ef276ceb03c644 Mon Sep 17 00:00:00 2001 From: Grzegorz Jaszczyk Date: Thu, 6 Jun 2024 15:27:08 +0000 Subject: [PATCH 2/2] libmodprobe: add support for dynamic module options Hitherto libmodprobe was supporting modules.options which allows passing extra module options. This commit extends that support and beside `options`, modules.options can now contain `dyn_options` entries. Syntax example: dyn_options snd_sof_pci system "/vendor/bin/mod_options --sof-fw" This allows to determine extra module options, with help of external program, at runtime which can be useful for unibuilds, supporting various device flavors and therefore requiring different module options. Using external program to customize modules parameters are inspired by Android's external firmware handler: https://android.googlesource.com/platform/system/core/+/master/init/README.ueventd.md#firmware-loading and the same RunExternalHandler is reused for both dynamic module handling and firmware handling. Bug: 335619610 Test: Test audio on systems using snd_sof_pci module and verify correctness of custom module parameters. Change-Id: Ibd1a942d4ea022519676435ed525cd7c3e720d31 --- libmodprobe/include/modprobe/modprobe.h | 1 + libmodprobe/libmodprobe.cpp | 61 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h index d7a90c488..7b691b13a 100644 --- a/libmodprobe/include/modprobe/modprobe.h +++ b/libmodprobe/include/modprobe/modprobe.h @@ -59,6 +59,7 @@ class Modprobe { bool ParseSoftdepCallback(const std::vector& args); bool ParseLoadCallback(const std::vector& args); bool ParseOptionsCallback(const std::vector& args); + bool ParseDynOptionsCallback(const std::vector& args); bool ParseBlocklistCallback(const std::vector& args); void ParseKernelCmdlineOptions(); void ParseCfg(const std::string& cfg, std::function&)> f); diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp index 8cc0b9b2e..bdd114c4b 100644 --- a/libmodprobe/libmodprobe.cpp +++ b/libmodprobe/libmodprobe.cpp @@ -17,8 +17,11 @@ #include #include +#include +#include #include #include +#include #include #include @@ -30,9 +33,12 @@ #include #include #include +#include #include #include +#include "exthandler/exthandler.h" + std::string Modprobe::MakeCanonical(const std::string& module_path) { auto start = module_path.find_last_of('/'); if (start == std::string::npos) { @@ -164,6 +170,10 @@ bool Modprobe::ParseOptionsCallback(const std::vector& args) { auto it = args.begin(); const std::string& type = *it++; + if (type == "dyn_options") { + return ParseDynOptionsCallback(std::vector(it, args.end())); + } + if (type != "options") { LOG(ERROR) << "non-options line encountered in modules.options"; return false; @@ -197,6 +207,57 @@ bool Modprobe::ParseOptionsCallback(const std::vector& args) { return true; } +bool Modprobe::ParseDynOptionsCallback(const std::vector& args) { + auto it = args.begin(); + int arg_size = 3; + + if (args.size() < arg_size) { + LOG(ERROR) << "dyn_options lines in modules.options must have at least" << arg_size + << " entries, not " << args.size(); + return false; + } + + const std::string& module = *it++; + + const std::string& canonical_name = MakeCanonical(module); + if (canonical_name.empty()) { + return false; + } + + const std::string& pwnam = *it++; + passwd* pwd = getpwnam(pwnam.c_str()); + if (!pwd) { + LOG(ERROR) << "invalid handler uid'" << pwnam << "'"; + return false; + } + + std::string handler_with_args = + android::base::Join(std::vector(it, args.end()), ' '); + handler_with_args.erase(std::remove(handler_with_args.begin(), handler_with_args.end(), '\"'), + handler_with_args.end()); + + LOG(DEBUG) << "Launching external module options handler: '" << handler_with_args + << " for module: " << module; + + // There is no need to set envs for external module options handler - pass + // empty map. + std::unordered_map envs_map; + auto result = RunExternalHandler(handler_with_args, pwd->pw_uid, 0, envs_map); + if (!result.ok()) { + LOG(ERROR) << "External module handler failed: " << result.error(); + return false; + } + + LOG(INFO) << "Dynamic options for module: " << module << " are '" << *result << "'"; + + auto [unused, inserted] = this->module_options_.emplace(canonical_name, *result); + if (!inserted) { + LOG(ERROR) << "multiple options lines present for module " << module; + return false; + } + return true; +} + bool Modprobe::ParseBlocklistCallback(const std::vector& args) { auto it = args.begin(); const std::string& type = *it++;