From badb7de1a23563dbc7b72f1c1d241c9085d5e4ed Mon Sep 17 00:00:00 2001 From: Jooyung Han Date: Tue, 10 May 2022 03:16:51 +0900 Subject: [PATCH] APEX configs support 'on' as well APEX configs have supported only 'service' definitions. For those services relying on 'on' trigger actions, we had to have separate config files installed in read-only partitions (e.g. /system/etc/init). This was suboptimal because even though APEXes are updatable, read-only partitions are not. Now, 'on' is supported in APEX configs. Putting 'on' trigger actions near to service definitions makes APEX more self-contained. 'on' trigger actions loaded from APEX configs are not sticky. So, events happens before loading APEX configs can't trigger actions. For example, 'post-fs-data' is where APEX configs are loaded for now, so 'on post-fs-data' in APEX configs can't be triggerd. Bug: 202731768 Test: atest CtsInitTestCases Change-Id: I5a01d9c7c57b07955b829d6cc157e7f0c91166f9 --- init/action.h | 3 ++ init/action_manager.h | 4 ++ init/builtins.cpp | 3 +- init/init.cpp | 8 +-- init/init.h | 2 +- init/init_test.cpp | 118 +++++++++++++++++++++++++++++++++++++----- init/reboot.cpp | 11 +++- 7 files changed, 129 insertions(+), 20 deletions(-) diff --git a/init/action.h b/init/action.h index 1534bf987..eddc384cd 100644 --- a/init/action.h +++ b/init/action.h @@ -22,6 +22,8 @@ #include #include +#include + #include "builtins.h" #include "keyword_map.h" #include "result.h" @@ -79,6 +81,7 @@ class Action { static void set_function_map(const BuiltinFunctionMap* function_map) { function_map_ = function_map; } + bool IsFromApex() const { return base::StartsWith(filename_, "/apex/"); } private: void ExecuteCommand(const Command& command) const; diff --git a/init/action_manager.h b/init/action_manager.h index b6f93d9b5..2746a7c4a 100644 --- a/init/action_manager.h +++ b/init/action_manager.h @@ -37,6 +37,10 @@ class ActionManager { size_t CheckAllCommands(); void AddAction(std::unique_ptr action); + template + void RemoveActionIf(UnaryPredicate predicate) { + actions_.erase(std::remove_if(actions_.begin(), actions_.end(), predicate), actions_.end()); + } void QueueEventTrigger(const std::string& trigger); void QueuePropertyChange(const std::string& name, const std::string& value); void QueueAllPropertyActions(); diff --git a/init/builtins.cpp b/init/builtins.cpp index 01db4f5da..9e1d93c6b 100644 --- a/init/builtins.cpp +++ b/init/builtins.cpp @@ -1288,7 +1288,8 @@ static Result parse_apex_configs() { return Error() << "glob pattern '" << glob_pattern << "' failed"; } std::vector configs; - Parser parser = CreateServiceOnlyParser(ServiceList::GetInstance(), true); + Parser parser = + CreateApexConfigParser(ActionManager::GetInstance(), ServiceList::GetInstance()); for (size_t i = 0; i < glob_result.gl_pathc; i++) { std::string path = glob_result.gl_pathv[i]; // Filter-out /apex/@ paths. The paths are bind-mounted to diff --git a/init/init.cpp b/init/init.cpp index f8330bc5b..fd8ee0fe8 100644 --- a/init/init.cpp +++ b/init/init.cpp @@ -293,13 +293,15 @@ Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) { return parser; } -// parser that only accepts new services -Parser CreateServiceOnlyParser(ServiceList& service_list, bool from_apex) { +// Returns a Parser that accepts scripts from APEX modules. It supports `service` and `on`. +Parser CreateApexConfigParser(ActionManager& action_manager, ServiceList& service_list) { Parser parser; parser.AddSectionParser( "service", std::make_unique(&service_list, GetSubcontext(), std::nullopt, - from_apex)); + /*from_apex=*/true)); + parser.AddSectionParser("on", std::make_unique(&action_manager, GetSubcontext())); + return parser; } diff --git a/init/init.h b/init/init.h index 4f686cb5b..522053549 100644 --- a/init/init.h +++ b/init/init.h @@ -29,7 +29,7 @@ namespace android { namespace init { Parser CreateParser(ActionManager& action_manager, ServiceList& service_list); -Parser CreateServiceOnlyParser(ServiceList& service_list, bool from_apex); +Parser CreateApexConfigParser(ActionManager& action_manager, ServiceList& service_list); bool start_waiting_for_property(const char *name, const char *value); diff --git a/init/init_test.cpp b/init/init_test.cpp index 8c19d5f5a..0dc6ff640 100644 --- a/init/init_test.cpp +++ b/init/init_test.cpp @@ -42,34 +42,34 @@ namespace init { using ActionManagerCommand = std::function; void TestInit(const std::string& init_script_file, const BuiltinFunctionMap& test_function_map, - const std::vector& commands, ServiceList* service_list) { - ActionManager am; - + const std::vector& commands, ActionManager* action_manager, + ServiceList* service_list) { Action::set_function_map(&test_function_map); Parser parser; parser.AddSectionParser("service", std::make_unique(service_list, nullptr, std::nullopt)); - parser.AddSectionParser("on", std::make_unique(&am, nullptr)); + parser.AddSectionParser("on", std::make_unique(action_manager, nullptr)); parser.AddSectionParser("import", std::make_unique(&parser)); ASSERT_TRUE(parser.ParseConfig(init_script_file)); for (const auto& command : commands) { - command(am); + command(*action_manager); } - while (am.HasMoreCommands()) { - am.ExecuteOneCommand(); + while (action_manager->HasMoreCommands()) { + action_manager->ExecuteOneCommand(); } } void TestInitText(const std::string& init_script, const BuiltinFunctionMap& test_function_map, - const std::vector& commands, ServiceList* service_list) { + const std::vector& commands, ActionManager* action_manager, + ServiceList* service_list) { TemporaryFile tf; ASSERT_TRUE(tf.fd != -1); ASSERT_TRUE(android::base::WriteStringToFd(init_script, tf.fd)); - TestInit(tf.path, test_function_map, commands, service_list); + TestInit(tf.path, test_function_map, commands, action_manager, service_list); } TEST(init, SimpleEventTrigger) { @@ -91,8 +91,9 @@ pass_test ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); }; std::vector commands{trigger_boot}; + ActionManager action_manager; ServiceList service_list; - TestInitText(init_script, test_function_map, commands, &service_list); + TestInitText(init_script, test_function_map, commands, &action_manager, &service_list); EXPECT_TRUE(expect_true); } @@ -154,8 +155,9 @@ execute_third ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); }; std::vector commands{trigger_boot}; + ActionManager action_manager; ServiceList service_list; - TestInitText(init_script, test_function_map, commands, &service_list); + TestInitText(init_script, test_function_map, commands, &action_manager, &service_list); EXPECT_EQ(3, num_executed); } @@ -170,8 +172,9 @@ service A something )init"; + ActionManager action_manager; ServiceList service_list; - TestInitText(init_script, BuiltinFunctionMap(), {}, &service_list); + TestInitText(init_script, BuiltinFunctionMap(), {}, &action_manager, &service_list); ASSERT_EQ(1, std::distance(service_list.begin(), service_list.end())); auto service = service_list.begin()->get(); @@ -237,13 +240,100 @@ TEST(init, EventTriggerOrderMultipleFiles) { ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); }; std::vector commands{trigger_boot}; + ActionManager action_manager; ServiceList service_list; - - TestInit(start.path, test_function_map, commands, &service_list); + TestInit(start.path, test_function_map, commands, &action_manager, &service_list); EXPECT_EQ(6, num_executed); } +BuiltinFunctionMap GetTestFunctionMapForLazyLoad(int& num_executed, ActionManager& action_manager) { + auto execute_command = [&num_executed](const BuiltinArguments& args) { + EXPECT_EQ(2U, args.size()); + EXPECT_EQ(++num_executed, std::stoi(args[1])); + return Result{}; + }; + auto load_command = [&action_manager](const BuiltinArguments& args) -> Result { + EXPECT_EQ(2U, args.size()); + Parser parser; + parser.AddSectionParser("on", std::make_unique(&action_manager, nullptr)); + if (!parser.ParseConfig(args[1])) { + return Error() << "Failed to load"; + } + return Result{}; + }; + auto trigger_command = [&action_manager](const BuiltinArguments& args) { + EXPECT_EQ(2U, args.size()); + LOG(INFO) << "Queue event trigger: " << args[1]; + action_manager.QueueEventTrigger(args[1]); + return Result{}; + }; + BuiltinFunctionMap test_function_map = { + {"execute", {1, 1, {false, execute_command}}}, + {"load", {1, 1, {false, load_command}}}, + {"trigger", {1, 1, {false, trigger_command}}}, + }; + return test_function_map; +} + +TEST(init, LazilyLoadedActionsCantBeTriggeredByTheSameTrigger) { + // "start" script loads "lazy" script. Even though "lazy" scripts + // defines "on boot" action, it's not executed by the current "boot" + // event because it's already processed. + TemporaryFile lazy; + ASSERT_TRUE(lazy.fd != -1); + ASSERT_TRUE(android::base::WriteStringToFd("on boot\nexecute 2", lazy.fd)); + + TemporaryFile start; + // clang-format off + std::string start_script = "on boot\n" + "load " + std::string(lazy.path) + "\n" + "execute 1"; + // clang-format on + ASSERT_TRUE(android::base::WriteStringToFd(start_script, start.fd)); + + int num_executed = 0; + ActionManager action_manager; + ServiceList service_list; + BuiltinFunctionMap test_function_map = + GetTestFunctionMapForLazyLoad(num_executed, action_manager); + + ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); }; + std::vector commands{trigger_boot}; + TestInit(start.path, test_function_map, commands, &action_manager, &service_list); + + EXPECT_EQ(1, num_executed); +} + +TEST(init, LazilyLoadedActionsCanBeTriggeredByTheNextTrigger) { + // "start" script loads "lazy" script and then triggers "next" event + // which executes "on next" action loaded by the previous command. + TemporaryFile lazy; + ASSERT_TRUE(lazy.fd != -1); + ASSERT_TRUE(android::base::WriteStringToFd("on next\nexecute 2", lazy.fd)); + + TemporaryFile start; + // clang-format off + std::string start_script = "on boot\n" + "load " + std::string(lazy.path) + "\n" + "execute 1\n" + "trigger next"; + // clang-format on + ASSERT_TRUE(android::base::WriteStringToFd(start_script, start.fd)); + + int num_executed = 0; + ActionManager action_manager; + ServiceList service_list; + BuiltinFunctionMap test_function_map = + GetTestFunctionMapForLazyLoad(num_executed, action_manager); + + ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); }; + std::vector commands{trigger_boot}; + TestInit(start.path, test_function_map, commands, &action_manager, &service_list); + + EXPECT_EQ(2, num_executed); +} + TEST(init, RejectsCriticalAndOneshotService) { if (GetIntProperty("ro.product.first_api_level", 10000) < 30) { GTEST_SKIP() << "Test only valid for devices launching with R or later"; diff --git a/init/reboot.cpp b/init/reboot.cpp index 41cf748d8..4e4bfd875 100644 --- a/init/reboot.cpp +++ b/init/reboot.cpp @@ -892,7 +892,16 @@ static Result DoUserspaceReboot() { sub_reason = "ns_switch"; return Error() << "Failed to switch to bootstrap namespace"; } - // Remove services that were defined in an APEX. + ActionManager::GetInstance().RemoveActionIf([](const auto& action) -> bool { + if (action->IsFromApex()) { + std::string trigger_name = action->BuildTriggersString(); + LOG(INFO) << "Removing action (" << trigger_name << ") from (" << action->filename() + << ":" << action->line() << ")"; + return true; + } + return false; + }); + // Remove services that were defined in an APEX ServiceList::GetInstance().RemoveServiceIf([](const std::unique_ptr& s) -> bool { if (s->is_from_apex()) { LOG(INFO) << "Removing service '" << s->name() << "' because it's defined in an APEX";