Merge changes from topics "action-in-apex-config", "apex-ready-event", "subcontext-for-vendor-apex"

* changes:
  Use subcontext for APEX configs from /{vendor, odm}
  add apex-ready event after post-fs-data
  APEX configs support 'on' as well
This commit is contained in:
Jooyung Han 2022-05-13 01:47:40 +00:00 committed by Gerrit Code Review
commit ec76b5cb4e
10 changed files with 200 additions and 25 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

@ -85,6 +85,10 @@
#include "system/core/init/property_service.pb.h"
#include "util.h"
#ifndef RECOVERY
#include "com_android_apex.h"
#endif // RECOVERY
using namespace std::chrono_literals;
using namespace std::string_literals;
@ -293,13 +297,59 @@ Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
return parser;
}
// parser that only accepts new services
Parser CreateServiceOnlyParser(ServiceList& service_list, bool from_apex) {
Parser parser;
#ifndef RECOVERY
template <typename T>
struct LibXmlErrorHandler {
T handler_;
template <typename Handler>
LibXmlErrorHandler(Handler&& handler) : handler_(std::move(handler)) {
xmlSetGenericErrorFunc(nullptr, &ErrorHandler);
}
~LibXmlErrorHandler() { xmlSetGenericErrorFunc(nullptr, nullptr); }
static void ErrorHandler(void*, const char* msg, ...) {
va_list args;
va_start(args, msg);
char* formatted;
if (vasprintf(&formatted, msg, args) >= 0) {
LOG(ERROR) << formatted;
}
free(formatted);
va_end(args);
}
};
template <typename Handler>
LibXmlErrorHandler(Handler&&) -> LibXmlErrorHandler<Handler>;
#endif // RECOVERY
// Returns a Parser that accepts scripts from APEX modules. It supports `service` and `on`.
Parser CreateApexConfigParser(ActionManager& action_manager, ServiceList& service_list) {
Parser parser;
auto subcontext = GetSubcontext();
#ifndef RECOVERY
if (subcontext) {
const auto apex_info_list_file = "/apex/apex-info-list.xml";
auto error_handler = LibXmlErrorHandler([&](const auto& error_message) {
LOG(ERROR) << "Failed to read " << apex_info_list_file << ":" << error_message;
});
const auto apex_info_list = com::android::apex::readApexInfoList(apex_info_list_file);
if (apex_info_list.has_value()) {
std::vector<std::string> subcontext_apexes;
for (const auto& info : apex_info_list->getApexInfo()) {
if (info.hasPreinstalledModulePath() &&
subcontext->PathMatchesSubcontext(info.getPreinstalledModulePath())) {
subcontext_apexes.push_back(info.getModuleName());
}
}
subcontext->SetApexList(std::move(subcontext_apexes));
}
}
#endif // RECOVERY
parser.AddSectionParser("service",
std::make_unique<ServiceParser>(&service_list, subcontext, std::nullopt,
/*from_apex=*/true));
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager, subcontext));
parser.AddSectionParser(
"service", std::make_unique<ServiceParser>(&service_list, GetSubcontext(), std::nullopt,
from_apex));
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";

View file

@ -250,7 +250,14 @@ void Subcontext::Restart() {
Fork();
}
bool Subcontext::PathMatchesSubcontext(const std::string& path) {
bool Subcontext::PathMatchesSubcontext(const std::string& path) const {
static const std::string kApexDir = "/apex/";
if (StartsWith(path, kApexDir)) {
auto begin = kApexDir.size();
auto end = path.find('/', begin);
auto apex_name = path.substr(begin, end - begin);
return std::find(apex_list_.begin(), apex_list_.end(), apex_name) != apex_list_.end();
}
for (const auto& prefix : path_prefixes_) {
if (StartsWith(path, prefix)) {
return true;
@ -259,6 +266,10 @@ bool Subcontext::PathMatchesSubcontext(const std::string& path) {
return false;
}
void Subcontext::SetApexList(std::vector<std::string>&& apex_list) {
apex_list_ = std::move(apex_list);
}
Result<SubcontextReply> Subcontext::TransmitMessage(const SubcontextCommand& subcontext_command) {
if (auto result = SendMessage(socket_, subcontext_command); !result.ok()) {
Restart();

View file

@ -46,7 +46,8 @@ class Subcontext {
Result<void> Execute(const std::vector<std::string>& args);
Result<std::vector<std::string>> ExpandArgs(const std::vector<std::string>& args);
void Restart();
bool PathMatchesSubcontext(const std::string& path);
bool PathMatchesSubcontext(const std::string& path) const;
void SetApexList(std::vector<std::string>&& apex_list);
const std::string& context() const { return context_; }
pid_t pid() const { return pid_; }
@ -56,6 +57,7 @@ class Subcontext {
Result<SubcontextReply> TransmitMessage(const SubcontextCommand& subcontext_command);
std::vector<std::string> path_prefixes_;
std::vector<std::string> apex_list_;
std::string context_;
pid_t pid_;
android::base::unique_fd socket_;

View file

@ -534,6 +534,10 @@ on late-init
# /data, which in turn can only be loaded when system properties are present.
trigger post-fs-data
# APEXes are ready to use. apex-ready is a public trigger similar to apexd.status=ready which
# is a system-private property.
trigger apex-ready
# Should be before netd, but after apex, properties and logging is available.
trigger load_bpf_programs
@ -1290,6 +1294,7 @@ on userspace-reboot-fs-remount
on userspace-reboot-resume
trigger userspace-reboot-fs-remount
trigger post-fs-data
trigger apex-ready
trigger zygote-start
trigger early-boot
trigger boot