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
This commit is contained in:
Jooyung Han 2022-05-10 03:16:51 +09:00
parent dbe14f2f73
commit badb7de1a2
7 changed files with 129 additions and 20 deletions

View file

@ -22,6 +22,8 @@
#include <variant>
#include <vector>
#include <android-base/strings.h>
#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;

View file

@ -37,6 +37,10 @@ class ActionManager {
size_t CheckAllCommands();
void AddAction(std::unique_ptr<Action> action);
template <class UnaryPredicate>
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();

View file

@ -1288,7 +1288,8 @@ static Result<void> parse_apex_configs() {
return Error() << "glob pattern '" << glob_pattern << "' failed";
}
std::vector<std::string> 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/<name>@<ver> paths. The paths are bind-mounted to

View file

@ -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<ServiceParser>(&service_list, GetSubcontext(), std::nullopt,
from_apex));
/*from_apex=*/true));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, GetSubcontext()));
return parser;
}

View file

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

View file

@ -42,34 +42,34 @@ namespace init {
using ActionManagerCommand = std::function<void(ActionManager&)>;
void TestInit(const std::string& init_script_file, const BuiltinFunctionMap& test_function_map,
const std::vector<ActionManagerCommand>& commands, ServiceList* service_list) {
ActionManager am;
const std::vector<ActionManagerCommand>& commands, ActionManager* action_manager,
ServiceList* service_list) {
Action::set_function_map(&test_function_map);
Parser parser;
parser.AddSectionParser("service",
std::make_unique<ServiceParser>(service_list, nullptr, std::nullopt));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, nullptr));
parser.AddSectionParser("on", std::make_unique<ActionParser>(action_manager, nullptr));
parser.AddSectionParser("import", std::make_unique<ImportParser>(&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<ActionManagerCommand>& commands, ServiceList* service_list) {
const std::vector<ActionManagerCommand>& 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<ActionManagerCommand> 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<ActionManagerCommand> 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<ActionManagerCommand> 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<void>{};
};
auto load_command = [&action_manager](const BuiltinArguments& args) -> Result<void> {
EXPECT_EQ(2U, args.size());
Parser parser;
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, nullptr));
if (!parser.ParseConfig(args[1])) {
return Error() << "Failed to load";
}
return Result<void>{};
};
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<void>{};
};
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<ActionManagerCommand> 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<ActionManagerCommand> 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";

View file

@ -892,7 +892,16 @@ static Result<void> 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<Service>& s) -> bool {
if (s->is_from_apex()) {
LOG(INFO) << "Removing service '" << s->name() << "' because it's defined in an APEX";