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:
commit
ec76b5cb4e
10 changed files with 200 additions and 25 deletions
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue