From 67dee626e0185096bbaf73042f1a891ce436f714 Mon Sep 17 00:00:00 2001 From: Tom Cherry Date: Thu, 27 Jul 2017 12:54:48 -0700 Subject: [PATCH] init: remove Parser singleton and related cleanup * Remove the Parser singleton (Hooray!) * Rename parser.* to tokenizer.* as this is actually a tokenizer * Rename init_parser.* to parser.* as this is a generic parser * Move contents of init_parser_test.cpp to service_test.cpp as this actually is a test of the parsing in MakeExecOneshotService() and nothing related to (init_)parser.cpp Test: boot bullhead Test: bool sailfish Test: init unit tests Change-Id: I4fe39e6483f58ebd3ce5ee715a45dbba0acf5d91 --- init/Android.bp | 3 +- init/action.h | 2 +- init/builtins.cpp | 20 ++- init/import_parser.h | 4 +- init/init.cpp | 54 +++++---- init/init.h | 8 ++ init/init_parser.cpp | 168 -------------------------- init/init_parser.h | 102 ---------------- init/init_parser_test.cpp | 150 ----------------------- init/init_test.cpp | 2 +- init/parser.cpp | 247 +++++++++++++++++++++----------------- init/parser.h | 76 ++++++++++-- init/service.h | 2 +- init/service_test.cpp | 120 ++++++++++++++++++ init/tokenizer.cpp | 124 +++++++++++++++++++ init/tokenizer.h | 40 ++++++ init/ueventd_parser.h | 2 +- 17 files changed, 542 insertions(+), 582 deletions(-) delete mode 100644 init/init_parser.cpp delete mode 100644 init/init_parser.h delete mode 100644 init/init_parser_test.cpp create mode 100644 init/tokenizer.cpp create mode 100644 init/tokenizer.h diff --git a/init/Android.bp b/init/Android.bp index 82945981e..6e95baa4b 100644 --- a/init/Android.bp +++ b/init/Android.bp @@ -67,10 +67,10 @@ cc_library_static { "devices.cpp", "firmware_handler.cpp", "import_parser.cpp", - "init_parser.cpp", "log.cpp", "parser.cpp", "service.cpp", + "tokenizer.cpp", "uevent_listener.cpp", "ueventd_parser.cpp", "util.cpp", @@ -153,7 +153,6 @@ cc_test { defaults: ["init_defaults"], srcs: [ "devices_test.cpp", - "init_parser_test.cpp", "init_test.cpp", "property_service_test.cpp", "service_test.cpp", diff --git a/init/action.h b/init/action.h index ad15f3f42..50cae7171 100644 --- a/init/action.h +++ b/init/action.h @@ -24,8 +24,8 @@ #include #include "builtins.h" -#include "init_parser.h" #include "keyword_map.h" +#include "parser.h" namespace android { namespace init { diff --git a/init/builtins.cpp b/init/builtins.cpp index dfd7b73d2..dec6f40e6 100644 --- a/init/builtins.cpp +++ b/init/builtins.cpp @@ -57,7 +57,7 @@ #include "action.h" #include "bootchart.h" #include "init.h" -#include "init_parser.h" +#include "parser.h" #include "property_service.h" #include "reboot.h" #include "service.h" @@ -388,21 +388,15 @@ exit_success: * start_index: index of the first path in the args list */ static void import_late(const std::vector& args, size_t start_index, size_t end_index) { - Parser& parser = Parser::GetInstance(); + auto& action_manager = ActionManager::GetInstance(); + auto& service_manager = ServiceManager::GetInstance(); + Parser parser = CreateParser(action_manager, service_manager); if (end_index <= start_index) { // Fallbacks for partitions on which early mount isn't enabled. - if (!parser.is_system_etc_init_loaded()) { - parser.ParseConfig("/system/etc/init"); - parser.set_is_system_etc_init_loaded(true); - } - if (!parser.is_vendor_etc_init_loaded()) { - parser.ParseConfig("/vendor/etc/init"); - parser.set_is_vendor_etc_init_loaded(true); - } - if (!parser.is_odm_etc_init_loaded()) { - parser.ParseConfig("/odm/etc/init"); - parser.set_is_odm_etc_init_loaded(true); + for (const auto& path : late_import_paths) { + parser.ParseConfig(path); } + late_import_paths.clear(); } else { for (size_t i = start_index; i < end_index; ++i) { parser.ParseConfig(args[i]); diff --git a/init/import_parser.h b/init/import_parser.h index b774c57c0..0d04e0e27 100644 --- a/init/import_parser.h +++ b/init/import_parser.h @@ -17,11 +17,11 @@ #ifndef _INIT_IMPORT_PARSER_H #define _INIT_IMPORT_PARSER_H -#include "init_parser.h" - #include #include +#include "parser.h" + namespace android { namespace init { diff --git a/init/init.cpp b/init/init.cpp index 7fbc3b7c7..f063c3f53 100644 --- a/init/init.cpp +++ b/init/init.cpp @@ -55,16 +55,13 @@ #include #include -#include "action.h" #include "bootchart.h" #include "import_parser.h" #include "init_first_stage.h" -#include "init_parser.h" #include "keychords.h" #include "log.h" #include "property_service.h" #include "reboot.h" -#include "service.h" #include "signal_handler.h" #include "ueventd.h" #include "util.h" @@ -98,11 +95,43 @@ static std::string wait_prop_name; static std::string wait_prop_value; static bool shutting_down; +std::vector late_import_paths; + void DumpState() { ServiceManager::GetInstance().DumpState(); ActionManager::GetInstance().DumpState(); } +Parser CreateParser(ActionManager& action_manager, ServiceManager& service_manager) { + Parser parser; + + parser.AddSectionParser("service", std::make_unique(&service_manager)); + parser.AddSectionParser("on", std::make_unique(&action_manager)); + parser.AddSectionParser("import", std::make_unique(&parser)); + + return parser; +} + +static void LoadBootScripts(ActionManager& action_manager, ServiceManager& service_manager) { + Parser parser = CreateParser(action_manager, service_manager); + + std::string bootscript = GetProperty("ro.boot.init_rc", ""); + if (bootscript.empty()) { + parser.ParseConfig("/init.rc"); + if (!parser.ParseConfig("/system/etc/init")) { + late_import_paths.emplace_back("/system/etc/init"); + } + if (!parser.ParseConfig("/vendor/etc/init")) { + late_import_paths.emplace_back("/vendor/etc/init"); + } + if (!parser.ParseConfig("/odm/etc/init")) { + late_import_paths.emplace_back("/odm/etc/init"); + } + } else { + parser.ParseConfig(bootscript); + } +} + void register_epoll_handler(int fd, void (*fn)()) { epoll_event ev; ev.events = EPOLLIN; @@ -1102,25 +1131,8 @@ int main(int argc, char** argv) { ActionManager& am = ActionManager::GetInstance(); ServiceManager& sm = ServiceManager::GetInstance(); - Parser& parser = Parser::GetInstance(); - parser.AddSectionParser("service", std::make_unique(&sm)); - parser.AddSectionParser("on", std::make_unique(&am)); - parser.AddSectionParser("import", std::make_unique(&parser)); - std::string bootscript = GetProperty("ro.boot.init_rc", ""); - if (bootscript.empty()) { - parser.ParseConfig("/init.rc"); - parser.set_is_system_etc_init_loaded( - parser.ParseConfig("/system/etc/init")); - parser.set_is_vendor_etc_init_loaded( - parser.ParseConfig("/vendor/etc/init")); - parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init")); - } else { - parser.ParseConfig(bootscript); - parser.set_is_system_etc_init_loaded(true); - parser.set_is_vendor_etc_init_loaded(true); - parser.set_is_odm_etc_init_loaded(true); - } + LoadBootScripts(am, sm); // Turning this on and letting the INFO logging be discarded adds 0.2s to // Nexus 9 boot time, so it's disabled by default. diff --git a/init/init.h b/init/init.h index aaab523ee..0a77bd211 100644 --- a/init/init.h +++ b/init/init.h @@ -21,6 +21,10 @@ #include +#include "action.h" +#include "parser.h" +#include "service.h" + namespace android { namespace init { @@ -32,6 +36,10 @@ extern std::string default_console; extern struct selabel_handle *sehandle; extern struct selabel_handle *sehandle_prop; +extern std::vector late_import_paths; + +Parser CreateParser(ActionManager& action_manager, ServiceManager& service_manager); + void handle_control_message(const std::string& msg, const std::string& arg); void property_changed(const std::string& name, const std::string& value); diff --git a/init/init_parser.cpp b/init/init_parser.cpp deleted file mode 100644 index 9f7089bcd..000000000 --- a/init/init_parser.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2010 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 "init_parser.h" - -#include - -#include -#include -#include -#include - -#include "parser.h" -#include "util.h" - -namespace android { -namespace init { - -Parser::Parser() { -} - -Parser& Parser::GetInstance() { - static Parser instance; - return instance; -} - -void Parser::AddSectionParser(const std::string& name, - std::unique_ptr parser) { - section_parsers_[name] = std::move(parser); -} - -void Parser::AddSingleLineParser(const std::string& prefix, LineCallback callback) { - line_callbacks_.emplace_back(prefix, callback); -} - -void Parser::ParseData(const std::string& filename, const std::string& data) { - //TODO: Use a parser with const input and remove this copy - std::vector data_copy(data.begin(), data.end()); - data_copy.push_back('\0'); - - parse_state state; - state.line = 0; - state.ptr = &data_copy[0]; - state.nexttoken = 0; - - SectionParser* section_parser = nullptr; - std::vector args; - - for (;;) { - switch (next_token(&state)) { - case T_EOF: - if (section_parser) { - section_parser->EndSection(); - } - return; - case T_NEWLINE: - state.line++; - if (args.empty()) { - break; - } - // If we have a line matching a prefix we recognize, call its callback and unset any - // current section parsers. This is meant for /sys/ and /dev/ line entries for uevent. - for (const auto& [prefix, callback] : line_callbacks_) { - if (android::base::StartsWith(args[0], prefix.c_str())) { - if (section_parser) section_parser->EndSection(); - - std::string ret_err; - if (!callback(std::move(args), &ret_err)) { - LOG(ERROR) << filename << ": " << state.line << ": " << ret_err; - } - section_parser = nullptr; - break; - } - } - if (section_parsers_.count(args[0])) { - if (section_parser) { - section_parser->EndSection(); - } - section_parser = section_parsers_[args[0]].get(); - std::string ret_err; - if (!section_parser->ParseSection(std::move(args), filename, state.line, &ret_err)) { - LOG(ERROR) << filename << ": " << state.line << ": " << ret_err; - section_parser = nullptr; - } - } else if (section_parser) { - std::string ret_err; - if (!section_parser->ParseLineSection(std::move(args), state.line, &ret_err)) { - LOG(ERROR) << filename << ": " << state.line << ": " << ret_err; - } - } - args.clear(); - break; - case T_TEXT: - args.emplace_back(state.text); - break; - } - } -} - -bool Parser::ParseConfigFile(const std::string& path) { - LOG(INFO) << "Parsing file " << path << "..."; - android::base::Timer t; - std::string data; - std::string err; - if (!ReadFile(path, &data, &err)) { - LOG(ERROR) << err; - return false; - } - - data.push_back('\n'); // TODO: fix parse_config. - ParseData(path, data); - for (const auto& [section_name, section_parser] : section_parsers_) { - section_parser->EndFile(); - } - - LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".)"; - return true; -} - -bool Parser::ParseConfigDir(const std::string& path) { - LOG(INFO) << "Parsing directory " << path << "..."; - std::unique_ptr config_dir(opendir(path.c_str()), closedir); - if (!config_dir) { - PLOG(ERROR) << "Could not import directory '" << path << "'"; - return false; - } - dirent* current_file; - std::vector files; - while ((current_file = readdir(config_dir.get()))) { - // Ignore directories and only process regular files. - if (current_file->d_type == DT_REG) { - std::string current_path = - android::base::StringPrintf("%s/%s", path.c_str(), current_file->d_name); - files.emplace_back(current_path); - } - } - // Sort first so we load files in a consistent order (bug 31996208) - std::sort(files.begin(), files.end()); - for (const auto& file : files) { - if (!ParseConfigFile(file)) { - LOG(ERROR) << "could not import file '" << file << "'"; - } - } - return true; -} - -bool Parser::ParseConfig(const std::string& path) { - if (is_dir(path.c_str())) { - return ParseConfigDir(path); - } - return ParseConfigFile(path); -} - -} // namespace init -} // namespace android diff --git a/init/init_parser.h b/init/init_parser.h deleted file mode 100644 index c07a69967..000000000 --- a/init/init_parser.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2010 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. - */ - -#ifndef _INIT_INIT_PARSER_H_ -#define _INIT_INIT_PARSER_H_ - -#include -#include -#include -#include - -// SectionParser is an interface that can parse a given 'section' in init. -// -// You can implement up to 4 functions below, with ParseSection() being mandatory. -// The first two function return bool with false indicating a failure and has a std::string* err -// parameter into which an error string can be written. It will be reported along with the -// filename and line number of where the error occurred. -// -// 1) bool ParseSection(std::vector&& args, const std::string& filename, -// int line, std::string* err) -// This function is called when a section is first encountered. -// -// 2) bool ParseLineSection(std::vector&& args, int line, std::string* err) -// This function is called on each subsequent line until the next section is encountered. -// -// 3) bool EndSection() -// This function is called either when a new section is found or at the end of the file. -// It indicates that parsing of the current section is complete and any relevant objects should -// be committed. -// -// 4) bool EndFile() -// This function is called at the end of the file. -// It indicates that the parsing has completed and any relevant objects should be committed. - -namespace android { -namespace init { - -class SectionParser { - public: - virtual ~SectionParser() {} - virtual bool ParseSection(std::vector&& args, const std::string& filename, - int line, std::string* err) = 0; - virtual bool ParseLineSection(std::vector&&, int, std::string*) { return true; }; - virtual void EndSection(){}; - virtual void EndFile(){}; -}; - -class Parser { - public: - // LineCallback is the type for callbacks that can parse a line starting with a given prefix. - // - // They take the form of bool Callback(std::vector&& args, std::string* err) - // - // Similar to ParseSection() and ParseLineSection(), this function returns bool with false - // indicating a failure and has an std::string* err parameter into which an error string can - // be written. - using LineCallback = std::function&&, std::string*)>; - - // TODO: init is the only user of this as a singleton; remove it. - static Parser& GetInstance(); - - Parser(); - - bool ParseConfig(const std::string& path); - void AddSectionParser(const std::string& name, std::unique_ptr parser); - void AddSingleLineParser(const std::string& prefix, LineCallback callback); - void set_is_system_etc_init_loaded(bool loaded) { is_system_etc_init_loaded_ = loaded; } - void set_is_vendor_etc_init_loaded(bool loaded) { is_vendor_etc_init_loaded_ = loaded; } - void set_is_odm_etc_init_loaded(bool loaded) { is_odm_etc_init_loaded_ = loaded; } - bool is_system_etc_init_loaded() { return is_system_etc_init_loaded_; } - bool is_vendor_etc_init_loaded() { return is_vendor_etc_init_loaded_; } - bool is_odm_etc_init_loaded() { return is_odm_etc_init_loaded_; } - - private: - void ParseData(const std::string& filename, const std::string& data); - bool ParseConfigFile(const std::string& path); - bool ParseConfigDir(const std::string& path); - - std::map> section_parsers_; - std::vector> line_callbacks_; - bool is_system_etc_init_loaded_ = false; - bool is_vendor_etc_init_loaded_ = false; - bool is_odm_etc_init_loaded_ = false; -}; - -} // namespace init -} // namespace android - -#endif diff --git a/init/init_parser_test.cpp b/init/init_parser_test.cpp deleted file mode 100644 index 95f269a0e..000000000 --- a/init/init_parser_test.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2015 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 "init_parser.h" - -#include -#include - -#include - -#include "init.h" -#include "service.h" -#include "util.h" - -namespace android { -namespace init { - -TEST(init_parser, make_exec_oneshot_service_invalid_syntax) { - ServiceManager& sm = ServiceManager::GetInstance(); - std::vector args; - // Nothing. - ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args)); - - // No arguments to 'exec'. - args.push_back("exec"); - ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args)); - - // No command in "exec --". - args.push_back("--"); - ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args)); -} - -TEST(init_parser, make_exec_oneshot_service_too_many_supplementary_gids) { - ServiceManager& sm = ServiceManager::GetInstance(); - std::vector args; - args.push_back("exec"); - args.push_back("seclabel"); - args.push_back("root"); // uid. - args.push_back("root"); // gid. - for (int i = 0; i < NR_SVC_SUPP_GIDS; ++i) { - args.push_back("root"); // Supplementary gid. - } - args.push_back("--"); - args.push_back("/system/bin/id"); - ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args)); -} - -static void Test_make_exec_oneshot_service(bool dash_dash, bool seclabel, bool uid, - bool gid, bool supplementary_gids) { - ServiceManager& sm = ServiceManager::GetInstance(); - std::vector args; - args.push_back("exec"); - if (seclabel) { - args.push_back("u:r:su:s0"); // seclabel - if (uid) { - args.push_back("log"); // uid - if (gid) { - args.push_back("shell"); // gid - if (supplementary_gids) { - args.push_back("system"); // supplementary gid 0 - args.push_back("adb"); // supplementary gid 1 - } - } - } - } - if (dash_dash) { - args.push_back("--"); - } - args.push_back("/system/bin/toybox"); - args.push_back("id"); - Service* svc = sm.MakeExecOneshotService(args); - ASSERT_NE(nullptr, svc); - - if (seclabel) { - ASSERT_EQ("u:r:su:s0", svc->seclabel()); - } else { - ASSERT_EQ("", svc->seclabel()); - } - if (uid) { - uid_t decoded_uid; - std::string err; - ASSERT_TRUE(DecodeUid("log", &decoded_uid, &err)); - ASSERT_EQ(decoded_uid, svc->uid()); - } else { - ASSERT_EQ(0U, svc->uid()); - } - if (gid) { - uid_t decoded_uid; - std::string err; - ASSERT_TRUE(DecodeUid("shell", &decoded_uid, &err)); - ASSERT_EQ(decoded_uid, svc->gid()); - } else { - ASSERT_EQ(0U, svc->gid()); - } - if (supplementary_gids) { - ASSERT_EQ(2U, svc->supp_gids().size()); - uid_t decoded_uid; - std::string err; - ASSERT_TRUE(DecodeUid("system", &decoded_uid, &err)); - ASSERT_EQ(decoded_uid, svc->supp_gids()[0]); - ASSERT_TRUE(DecodeUid("adb", &decoded_uid, &err)); - ASSERT_EQ(decoded_uid, svc->supp_gids()[1]); - } else { - ASSERT_EQ(0U, svc->supp_gids().size()); - } - - ASSERT_EQ(static_cast(2), svc->args().size()); - ASSERT_EQ("/system/bin/toybox", svc->args()[0]); - ASSERT_EQ("id", svc->args()[1]); -} - -TEST(init_parser, make_exec_oneshot_service_with_everything) { - Test_make_exec_oneshot_service(true, true, true, true, true); -} - -TEST(init_parser, make_exec_oneshot_service_with_seclabel_uid_gid) { - Test_make_exec_oneshot_service(true, true, true, true, false); -} - -TEST(init_parser, make_exec_oneshot_service_with_seclabel_uid) { - Test_make_exec_oneshot_service(true, true, true, false, false); -} - -TEST(init_parser, make_exec_oneshot_service_with_seclabel) { - Test_make_exec_oneshot_service(true, true, false, false, false); -} - -TEST(init_parser, make_exec_oneshot_service_with_just_command) { - Test_make_exec_oneshot_service(true, false, false, false, false); -} - -TEST(init_parser, make_exec_oneshot_service_with_just_command_no_dash) { - Test_make_exec_oneshot_service(false, false, false, false, false); -} - -} // namespace init -} // namespace android diff --git a/init/init_test.cpp b/init/init_test.cpp index 0a4071b98..20622901c 100644 --- a/init/init_test.cpp +++ b/init/init_test.cpp @@ -23,8 +23,8 @@ #include "action.h" #include "builtins.h" #include "import_parser.h" -#include "init_parser.h" #include "keyword_map.h" +#include "parser.h" #include "util.h" namespace android { diff --git a/init/parser.cpp b/init/parser.cpp index c0fa6d9e6..c6f4f459b 100644 --- a/init/parser.cpp +++ b/init/parser.cpp @@ -1,123 +1,156 @@ +/* + * Copyright (C) 2010 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 "parser.h" +#include + +#include +#include +#include +#include + +#include "tokenizer.h" +#include "util.h" + namespace android { namespace init { -int next_token(struct parse_state *state) -{ - char *x = state->ptr; - char *s; +Parser::Parser() {} - if (state->nexttoken) { - int t = state->nexttoken; - state->nexttoken = 0; - return t; - } +void Parser::AddSectionParser(const std::string& name, std::unique_ptr parser) { + section_parsers_[name] = std::move(parser); +} + +void Parser::AddSingleLineParser(const std::string& prefix, LineCallback callback) { + line_callbacks_.emplace_back(prefix, callback); +} + +void Parser::ParseData(const std::string& filename, const std::string& data) { + // TODO: Use a parser with const input and remove this copy + std::vector data_copy(data.begin(), data.end()); + data_copy.push_back('\0'); + + parse_state state; + state.line = 0; + state.ptr = &data_copy[0]; + state.nexttoken = 0; + + SectionParser* section_parser = nullptr; + std::vector args; for (;;) { - switch (*x) { - case 0: - state->ptr = x; - return T_EOF; - case '\n': - x++; - state->ptr = x; - return T_NEWLINE; - case ' ': - case '\t': - case '\r': - x++; - continue; - case '#': - while (*x && (*x != '\n')) x++; - if (*x == '\n') { - state->ptr = x+1; - return T_NEWLINE; - } else { - state->ptr = x; - return T_EOF; - } - default: - goto text; + switch (next_token(&state)) { + case T_EOF: + if (section_parser) section_parser->EndSection(); + return; + case T_NEWLINE: + state.line++; + if (args.empty()) break; + // If we have a line matching a prefix we recognize, call its callback and unset any + // current section parsers. This is meant for /sys/ and /dev/ line entries for + // uevent. + for (const auto& [prefix, callback] : line_callbacks_) { + if (android::base::StartsWith(args[0], prefix.c_str())) { + if (section_parser) section_parser->EndSection(); + + std::string ret_err; + if (!callback(std::move(args), &ret_err)) { + LOG(ERROR) << filename << ": " << state.line << ": " << ret_err; + } + section_parser = nullptr; + break; + } + } + if (section_parsers_.count(args[0])) { + if (section_parser) section_parser->EndSection(); + section_parser = section_parsers_[args[0]].get(); + std::string ret_err; + if (!section_parser->ParseSection(std::move(args), filename, state.line, + &ret_err)) { + LOG(ERROR) << filename << ": " << state.line << ": " << ret_err; + section_parser = nullptr; + } + } else if (section_parser) { + std::string ret_err; + if (!section_parser->ParseLineSection(std::move(args), state.line, &ret_err)) { + LOG(ERROR) << filename << ": " << state.line << ": " << ret_err; + } + } + args.clear(); + break; + case T_TEXT: + args.emplace_back(state.text); + break; } } +} + +bool Parser::ParseConfigFile(const std::string& path) { + LOG(INFO) << "Parsing file " << path << "..."; + android::base::Timer t; + std::string data; + std::string err; + if (!ReadFile(path, &data, &err)) { + LOG(ERROR) << err; + return false; + } -textdone: - state->ptr = x; - *s = 0; - return T_TEXT; -text: - state->text = s = x; -textresume: - for (;;) { - switch (*x) { - case 0: - goto textdone; - case ' ': - case '\t': - case '\r': - x++; - goto textdone; - case '\n': - state->nexttoken = T_NEWLINE; - x++; - goto textdone; - case '"': - x++; - for (;;) { - switch (*x) { - case 0: - /* unterminated quoted thing */ - state->ptr = x; - return T_EOF; - case '"': - x++; - goto textresume; - default: - *s++ = *x++; - } - } - break; - case '\\': - x++; - switch (*x) { - case 0: - goto textdone; - case 'n': - *s++ = '\n'; - break; - case 'r': - *s++ = '\r'; - break; - case 't': - *s++ = '\t'; - break; - case '\\': - *s++ = '\\'; - break; - case '\r': - /* \ -> line continuation */ - if (x[1] != '\n') { - x++; - continue; - } - case '\n': - /* \ -> line continuation */ - state->line++; - x++; - /* eat any extra whitespace */ - while((*x == ' ') || (*x == '\t')) x++; - continue; - default: - /* unknown escape -- just copy */ - *s++ = *x++; - } - continue; - default: - *s++ = *x++; + data.push_back('\n'); // TODO: fix parse_config. + ParseData(path, data); + for (const auto& [section_name, section_parser] : section_parsers_) { + section_parser->EndFile(); + } + + LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".)"; + return true; +} + +bool Parser::ParseConfigDir(const std::string& path) { + LOG(INFO) << "Parsing directory " << path << "..."; + std::unique_ptr config_dir(opendir(path.c_str()), closedir); + if (!config_dir) { + PLOG(ERROR) << "Could not import directory '" << path << "'"; + return false; + } + dirent* current_file; + std::vector files; + while ((current_file = readdir(config_dir.get()))) { + // Ignore directories and only process regular files. + if (current_file->d_type == DT_REG) { + std::string current_path = + android::base::StringPrintf("%s/%s", path.c_str(), current_file->d_name); + files.emplace_back(current_path); } } - return T_EOF; + // Sort first so we load files in a consistent order (bug 31996208) + std::sort(files.begin(), files.end()); + for (const auto& file : files) { + if (!ParseConfigFile(file)) { + LOG(ERROR) << "could not import file '" << file << "'"; + } + } + return true; +} + +bool Parser::ParseConfig(const std::string& path) { + if (is_dir(path.c_str())) { + return ParseConfigDir(path); + } + return ParseConfigFile(path); } } // namespace init diff --git a/init/parser.h b/init/parser.h index 86e4c5729..fd65ad6ff 100644 --- a/init/parser.h +++ b/init/parser.h @@ -14,27 +14,77 @@ * limitations under the License. */ -#ifndef PARSER_H_ -#define PARSER_H_ +#ifndef _INIT_PARSER_H_ +#define _INIT_PARSER_H_ -#define T_EOF 0 -#define T_TEXT 1 -#define T_NEWLINE 2 +#include +#include +#include +#include + +// SectionParser is an interface that can parse a given 'section' in init. +// +// You can implement up to 4 functions below, with ParseSection() being mandatory. +// The first two function return bool with false indicating a failure and has a std::string* err +// parameter into which an error string can be written. It will be reported along with the +// filename and line number of where the error occurred. +// +// 1) bool ParseSection(std::vector&& args, const std::string& filename, +// int line, std::string* err) +// This function is called when a section is first encountered. +// +// 2) bool ParseLineSection(std::vector&& args, int line, std::string* err) +// This function is called on each subsequent line until the next section is encountered. +// +// 3) bool EndSection() +// This function is called either when a new section is found or at the end of the file. +// It indicates that parsing of the current section is complete and any relevant objects should +// be committed. +// +// 4) bool EndFile() +// This function is called at the end of the file. +// It indicates that the parsing has completed and any relevant objects should be committed. namespace android { namespace init { -struct parse_state -{ - char *ptr; - char *text; - int line; - int nexttoken; +class SectionParser { + public: + virtual ~SectionParser() {} + virtual bool ParseSection(std::vector&& args, const std::string& filename, + int line, std::string* err) = 0; + virtual bool ParseLineSection(std::vector&&, int, std::string*) { return true; }; + virtual void EndSection(){}; + virtual void EndFile(){}; }; -int next_token(struct parse_state *state); +class Parser { + public: + // LineCallback is the type for callbacks that can parse a line starting with a given prefix. + // + // They take the form of bool Callback(std::vector&& args, std::string* err) + // + // Similar to ParseSection() and ParseLineSection(), this function returns bool with false + // indicating a failure and has an std::string* err parameter into which an error string can + // be written. + using LineCallback = std::function&&, std::string*)>; + + Parser(); + + bool ParseConfig(const std::string& path); + void AddSectionParser(const std::string& name, std::unique_ptr parser); + void AddSingleLineParser(const std::string& prefix, LineCallback callback); + + private: + void ParseData(const std::string& filename, const std::string& data); + bool ParseConfigFile(const std::string& path); + bool ParseConfigDir(const std::string& path); + + std::map> section_parsers_; + std::vector> line_callbacks_; +}; } // namespace init } // namespace android -#endif /* PARSER_H_ */ +#endif diff --git a/init/service.h b/init/service.h index 62a3299e1..10e19d9d6 100644 --- a/init/service.h +++ b/init/service.h @@ -30,8 +30,8 @@ #include "action.h" #include "capabilities.h" #include "descriptors.h" -#include "init_parser.h" #include "keyword_map.h" +#include "parser.h" #define SVC_DISABLED 0x001 // do not autostart with class #define SVC_ONESHOT 0x002 // do not restart on exit diff --git a/init/service_test.cpp b/init/service_test.cpp index 44f28a37f..123c8a5d6 100644 --- a/init/service_test.cpp +++ b/init/service_test.cpp @@ -23,6 +23,8 @@ #include +#include "util.h" + namespace android { namespace init { @@ -71,5 +73,123 @@ TEST(service, pod_initialized) { EXPECT_FALSE(service_in_old_memory->process_cgroup_empty()); } +TEST(service, make_exec_oneshot_service_invalid_syntax) { + ServiceManager& sm = ServiceManager::GetInstance(); + std::vector args; + // Nothing. + ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args)); + + // No arguments to 'exec'. + args.push_back("exec"); + ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args)); + + // No command in "exec --". + args.push_back("--"); + ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args)); +} + +TEST(service, make_exec_oneshot_service_too_many_supplementary_gids) { + ServiceManager& sm = ServiceManager::GetInstance(); + std::vector args; + args.push_back("exec"); + args.push_back("seclabel"); + args.push_back("root"); // uid. + args.push_back("root"); // gid. + for (int i = 0; i < NR_SVC_SUPP_GIDS; ++i) { + args.push_back("root"); // Supplementary gid. + } + args.push_back("--"); + args.push_back("/system/bin/id"); + ASSERT_EQ(nullptr, sm.MakeExecOneshotService(args)); +} + +static void Test_make_exec_oneshot_service(bool dash_dash, bool seclabel, bool uid, bool gid, + bool supplementary_gids) { + ServiceManager& sm = ServiceManager::GetInstance(); + std::vector args; + args.push_back("exec"); + if (seclabel) { + args.push_back("u:r:su:s0"); // seclabel + if (uid) { + args.push_back("log"); // uid + if (gid) { + args.push_back("shell"); // gid + if (supplementary_gids) { + args.push_back("system"); // supplementary gid 0 + args.push_back("adb"); // supplementary gid 1 + } + } + } + } + if (dash_dash) { + args.push_back("--"); + } + args.push_back("/system/bin/toybox"); + args.push_back("id"); + Service* svc = sm.MakeExecOneshotService(args); + ASSERT_NE(nullptr, svc); + + if (seclabel) { + ASSERT_EQ("u:r:su:s0", svc->seclabel()); + } else { + ASSERT_EQ("", svc->seclabel()); + } + if (uid) { + uid_t decoded_uid; + std::string err; + ASSERT_TRUE(DecodeUid("log", &decoded_uid, &err)); + ASSERT_EQ(decoded_uid, svc->uid()); + } else { + ASSERT_EQ(0U, svc->uid()); + } + if (gid) { + uid_t decoded_uid; + std::string err; + ASSERT_TRUE(DecodeUid("shell", &decoded_uid, &err)); + ASSERT_EQ(decoded_uid, svc->gid()); + } else { + ASSERT_EQ(0U, svc->gid()); + } + if (supplementary_gids) { + ASSERT_EQ(2U, svc->supp_gids().size()); + uid_t decoded_uid; + std::string err; + ASSERT_TRUE(DecodeUid("system", &decoded_uid, &err)); + ASSERT_EQ(decoded_uid, svc->supp_gids()[0]); + ASSERT_TRUE(DecodeUid("adb", &decoded_uid, &err)); + ASSERT_EQ(decoded_uid, svc->supp_gids()[1]); + } else { + ASSERT_EQ(0U, svc->supp_gids().size()); + } + + ASSERT_EQ(static_cast(2), svc->args().size()); + ASSERT_EQ("/system/bin/toybox", svc->args()[0]); + ASSERT_EQ("id", svc->args()[1]); +} + +TEST(service, make_exec_oneshot_service_with_everything) { + Test_make_exec_oneshot_service(true, true, true, true, true); +} + +TEST(service, make_exec_oneshot_service_with_seclabel_uid_gid) { + Test_make_exec_oneshot_service(true, true, true, true, false); +} + +TEST(service, make_exec_oneshot_service_with_seclabel_uid) { + Test_make_exec_oneshot_service(true, true, true, false, false); +} + +TEST(service, make_exec_oneshot_service_with_seclabel) { + Test_make_exec_oneshot_service(true, true, false, false, false); +} + +TEST(service, make_exec_oneshot_service_with_just_command) { + Test_make_exec_oneshot_service(true, false, false, false, false); +} + +TEST(service, make_exec_oneshot_service_with_just_command_no_dash) { + Test_make_exec_oneshot_service(false, false, false, false, false); +} + } // namespace init } // namespace android diff --git a/init/tokenizer.cpp b/init/tokenizer.cpp new file mode 100644 index 000000000..f8d9b6be7 --- /dev/null +++ b/init/tokenizer.cpp @@ -0,0 +1,124 @@ +#include "tokenizer.h" + +namespace android { +namespace init { + +int next_token(struct parse_state *state) +{ + char *x = state->ptr; + char *s; + + if (state->nexttoken) { + int t = state->nexttoken; + state->nexttoken = 0; + return t; + } + + for (;;) { + switch (*x) { + case 0: + state->ptr = x; + return T_EOF; + case '\n': + x++; + state->ptr = x; + return T_NEWLINE; + case ' ': + case '\t': + case '\r': + x++; + continue; + case '#': + while (*x && (*x != '\n')) x++; + if (*x == '\n') { + state->ptr = x+1; + return T_NEWLINE; + } else { + state->ptr = x; + return T_EOF; + } + default: + goto text; + } + } + +textdone: + state->ptr = x; + *s = 0; + return T_TEXT; +text: + state->text = s = x; +textresume: + for (;;) { + switch (*x) { + case 0: + goto textdone; + case ' ': + case '\t': + case '\r': + x++; + goto textdone; + case '\n': + state->nexttoken = T_NEWLINE; + x++; + goto textdone; + case '"': + x++; + for (;;) { + switch (*x) { + case 0: + /* unterminated quoted thing */ + state->ptr = x; + return T_EOF; + case '"': + x++; + goto textresume; + default: + *s++ = *x++; + } + } + break; + case '\\': + x++; + switch (*x) { + case 0: + goto textdone; + case 'n': + *s++ = '\n'; + break; + case 'r': + *s++ = '\r'; + break; + case 't': + *s++ = '\t'; + break; + case '\\': + *s++ = '\\'; + break; + case '\r': + /* \ -> line continuation */ + if (x[1] != '\n') { + x++; + continue; + } + case '\n': + /* \ -> line continuation */ + state->line++; + x++; + /* eat any extra whitespace */ + while((*x == ' ') || (*x == '\t')) x++; + continue; + default: + /* unknown escape -- just copy */ + *s++ = *x++; + } + continue; + default: + *s++ = *x++; + } + } + return T_EOF; +} + +} // namespace init +} // namespace android diff --git a/init/tokenizer.h b/init/tokenizer.h new file mode 100644 index 000000000..72c08ef08 --- /dev/null +++ b/init/tokenizer.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2010 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. + */ + +#ifndef _INIT_TOKENIZER_H_ +#define _INIT_TOKENIZER_H_ + +#define T_EOF 0 +#define T_TEXT 1 +#define T_NEWLINE 2 + +namespace android { +namespace init { + +struct parse_state +{ + char *ptr; + char *text; + int line; + int nexttoken; +}; + +int next_token(struct parse_state *state); + +} // namespace init +} // namespace android + +#endif diff --git a/init/ueventd_parser.h b/init/ueventd_parser.h index 592df639c..51d83ef00 100644 --- a/init/ueventd_parser.h +++ b/init/ueventd_parser.h @@ -21,7 +21,7 @@ #include #include "devices.h" -#include "init_parser.h" +#include "parser.h" namespace android { namespace init {