diff --git a/init/Android.mk b/init/Android.mk index bfdc664b5..e9d2f3be6 100644 --- a/init/Android.mk +++ b/init/Android.mk @@ -144,6 +144,7 @@ LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_SRC_FILES := \ devices_test.cpp \ init_parser_test.cpp \ + init_test.cpp \ property_service_test.cpp \ util_test.cpp \ @@ -155,7 +156,7 @@ LOCAL_SHARED_LIBRARIES += \ LOCAL_STATIC_LIBRARIES := libinit LOCAL_SANITIZE := integer LOCAL_CLANG := true -LOCAL_CPPFLAGS := -Wall -Wextra -Werror +LOCAL_CPPFLAGS := -Wall -Wextra -Werror -std=gnu++1z include $(BUILD_NATIVE_TEST) diff --git a/init/action.cpp b/init/action.cpp index c1289684d..8d49e2ad4 100644 --- a/init/action.cpp +++ b/init/action.cpp @@ -363,7 +363,7 @@ void ActionManager::DumpState() const { } } -bool ActionParser::ParseSection(const std::vector& args, const std::string& filename, +bool ActionParser::ParseSection(std::vector&& args, const std::string& filename, int line, std::string* err) { std::vector triggers(args.begin() + 1, args.end()); if (triggers.size() < 1) { @@ -380,13 +380,12 @@ bool ActionParser::ParseSection(const std::vector& args, const std: return true; } -bool ActionParser::ParseLineSection(const std::vector& args, int line, - std::string* err) { - return action_ ? action_->AddCommand(args, line, err) : false; +bool ActionParser::ParseLineSection(std::vector&& args, int line, std::string* err) { + return action_ ? action_->AddCommand(std::move(args), line, err) : false; } void ActionParser::EndSection() { if (action_ && action_->NumCommands() > 0) { - ActionManager::GetInstance().AddAction(std::move(action_)); + action_manager_->AddAction(std::move(action_)); } } diff --git a/init/action.h b/init/action.h index 25e5a3e22..c528a7cc2 100644 --- a/init/action.h +++ b/init/action.h @@ -88,9 +88,12 @@ public: }; class ActionManager { -public: + public: static ActionManager& GetInstance(); + // Exposed for testing + ActionManager(); + void AddAction(std::unique_ptr action); void QueueEventTrigger(const std::string& trigger); void QueuePropertyTrigger(const std::string& name, const std::string& value); @@ -100,9 +103,7 @@ public: bool HasMoreCommands() const; void DumpState() const; -private: - ActionManager(); - + private: ActionManager(ActionManager const&) = delete; void operator=(ActionManager const&) = delete; @@ -114,16 +115,15 @@ private: class ActionParser : public SectionParser { public: - ActionParser() : action_(nullptr) { - } - bool ParseSection(const std::vector& args, const std::string& filename, int line, + ActionParser(ActionManager* action_manager) + : action_manager_(action_manager), action_(nullptr) {} + bool ParseSection(std::vector&& args, const std::string& filename, int line, std::string* err) override; - bool ParseLineSection(const std::vector& args, int line, std::string* err) override; + bool ParseLineSection(std::vector&& args, int line, std::string* err) override; void EndSection() override; - void EndFile(const std::string&) override { - } private: + ActionManager* action_manager_; std::unique_ptr action_; }; diff --git a/init/builtins.cpp b/init/builtins.cpp index f687b6ca3..04cf4dca4 100644 --- a/init/builtins.cpp +++ b/init/builtins.cpp @@ -390,7 +390,7 @@ static void import_late(const std::vector& args, size_t start_index // Turning this on and letting the INFO logging be discarded adds 0.2s to // Nexus 9 boot time, so it's disabled by default. - if (false) parser.DumpState(); + if (false) DumpState(); } /* mount_fstab @@ -855,7 +855,7 @@ static int do_init_user0(const std::vector& args) { return e4crypt_do_init_user0(); } -BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const { +const BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const { constexpr std::size_t kMax = std::numeric_limits::max(); // clang-format off static const Map builtin_functions = { diff --git a/init/builtins.h b/init/builtins.h index 53f4a714f..e1f0567ce 100644 --- a/init/builtins.h +++ b/init/builtins.h @@ -17,19 +17,20 @@ #ifndef _INIT_BUILTINS_H #define _INIT_BUILTINS_H +#include #include #include #include #include "keyword_map.h" -using BuiltinFunction = int (*) (const std::vector& args); +using BuiltinFunction = std::function&)>; class BuiltinFunctionMap : public KeywordMap { -public: - BuiltinFunctionMap() { - } -private: - Map& map() const override; + public: + BuiltinFunctionMap() {} + + private: + const Map& map() const override; }; #endif diff --git a/init/import_parser.cpp b/init/import_parser.cpp index f66b2bae6..99275e5e7 100644 --- a/init/import_parser.cpp +++ b/init/import_parser.cpp @@ -20,7 +20,7 @@ #include "util.h" -bool ImportParser::ParseSection(const std::vector& args, const std::string& filename, +bool ImportParser::ParseSection(std::vector&& args, const std::string& filename, int line, std::string* err) { if (args.size() != 2) { *err = "single argument needed for import\n"; @@ -35,16 +35,18 @@ bool ImportParser::ParseSection(const std::vector& args, const std: } LOG(INFO) << "Added '" << conf_file << "' to import list"; - imports_.emplace_back(std::move(conf_file)); + if (filename_.empty()) filename_ = filename; + imports_.emplace_back(std::move(conf_file), line); return true; } -void ImportParser::EndFile(const std::string& filename) { +void ImportParser::EndFile() { auto current_imports = std::move(imports_); imports_.clear(); - for (const auto& s : current_imports) { - if (!Parser::GetInstance().ParseConfig(s)) { - PLOG(ERROR) << "could not import file '" << s << "' from '" << filename << "'"; + for (const auto& [import, line_num] : current_imports) { + if (!parser_->ParseConfig(import)) { + PLOG(ERROR) << filename_ << ": " << line_num << ": Could not import file '" << import + << "'"; } } } diff --git a/init/import_parser.h b/init/import_parser.h index e15d55515..45cbfadf0 100644 --- a/init/import_parser.h +++ b/init/import_parser.h @@ -24,20 +24,17 @@ class ImportParser : public SectionParser { public: - ImportParser() { - } - bool ParseSection(const std::vector& args, const std::string& filename, int line, + ImportParser(Parser* parser) : parser_(parser) {} + bool ParseSection(std::vector&& args, const std::string& filename, int line, std::string* err) override; - bool ParseLineSection(const std::vector& args, int line, - std::string* err) override { - return true; - } - void EndSection() override { - } - void EndFile(const std::string& filename) override; + void EndFile() override; private: - std::vector imports_; + Parser* parser_; + // Store filename for later error reporting. + std::string filename_; + // Vector of imports and their line numbers for later error reporting. + std::vector> imports_; }; #endif diff --git a/init/init.cpp b/init/init.cpp index a8573772e..09373de4f 100644 --- a/init/init.cpp +++ b/init/init.cpp @@ -96,6 +96,11 @@ static std::unique_ptr waiting_for_prop(nullptr); static std::string wait_prop_name; static std::string wait_prop_value; +void DumpState() { + ServiceManager::GetInstance().DumpState(); + ActionManager::GetInstance().DumpState(); +} + void register_epoll_handler(int fd, void (*fn)()) { epoll_event ev; ev.events = EPOLLIN; @@ -1429,10 +1434,13 @@ int main(int argc, char** argv) { const BuiltinFunctionMap function_map; Action::set_function_map(&function_map); + ActionManager& am = ActionManager::GetInstance(); + ServiceManager& sm = ServiceManager::GetInstance(); Parser& parser = Parser::GetInstance(); - parser.AddSectionParser("service",std::make_unique()); - parser.AddSectionParser("on", std::make_unique()); - parser.AddSectionParser("import", std::make_unique()); + + 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"); @@ -1450,9 +1458,7 @@ int main(int argc, char** argv) { // Turning this on and letting the INFO logging be discarded adds 0.2s to // Nexus 9 boot time, so it's disabled by default. - if (false) parser.DumpState(); - - ActionManager& am = ActionManager::GetInstance(); + if (false) DumpState(); am.QueueEventTrigger("early-init"); @@ -1487,10 +1493,10 @@ int main(int argc, char** argv) { // By default, sleep until something happens. int epoll_timeout_ms = -1; - if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) { + if (!(waiting_for_prop || sm.IsWaitingForExec())) { am.ExecuteOneCommand(); } - if (!(waiting_for_prop || ServiceManager::GetInstance().IsWaitingForExec())) { + if (!(waiting_for_prop || sm.IsWaitingForExec())) { restart_processes(); // If there's a process that needs restarting, wake up in time for that. diff --git a/init/init.h b/init/init.h index 1da335030..6add75fba 100644 --- a/init/init.h +++ b/init/init.h @@ -34,4 +34,6 @@ int add_environment(const char* key, const char* val); bool start_waiting_for_property(const char *name, const char *value); +void DumpState(); + #endif /* _INIT_INIT_H */ diff --git a/init/init_parser.cpp b/init/init_parser.cpp index b425497b1..5c7af79ee 100644 --- a/init/init_parser.cpp +++ b/init/init_parser.cpp @@ -17,14 +17,12 @@ #include "init_parser.h" #include -#include #include #include -#include "action.h" #include "parser.h" -#include "service.h" +#include "util.h" Parser::Parser() { } @@ -71,13 +69,14 @@ void Parser::ParseData(const std::string& filename, const std::string& data) { } section_parser = section_parsers_[args[0]].get(); std::string ret_err; - if (!section_parser->ParseSection(args, state.filename, state.line, &ret_err)) { + if (!section_parser->ParseSection(std::move(args), state.filename, state.line, + &ret_err)) { parse_error(&state, "%s\n", ret_err.c_str()); section_parser = nullptr; } } else if (section_parser) { std::string ret_err; - if (!section_parser->ParseLineSection(args, state.line, &ret_err)) { + if (!section_parser->ParseLineSection(std::move(args), state.line, &ret_err)) { parse_error(&state, "%s\n", ret_err.c_str()); } } @@ -100,8 +99,8 @@ bool Parser::ParseConfigFile(const std::string& path) { data.push_back('\n'); // TODO: fix parse_config. ParseData(path, data); - for (const auto& sp : section_parsers_) { - sp.second->EndFile(path); + for (const auto& [section_name, section_parser] : section_parsers_) { + section_parser->EndFile(); } LOG(VERBOSE) << "(Parsing " << path << " took " << t << ".)"; @@ -141,8 +140,3 @@ bool Parser::ParseConfig(const std::string& path) { } return ParseConfigFile(path); } - -void Parser::DumpState() const { - ServiceManager::GetInstance().DumpState(); - ActionManager::GetInstance().DumpState(); -} diff --git a/init/init_parser.h b/init/init_parser.h index 64f0cacbe..fe70d7d50 100644 --- a/init/init_parser.h +++ b/init/init_parser.h @@ -22,25 +22,50 @@ #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. + class SectionParser { -public: - virtual ~SectionParser() { - } - virtual bool ParseSection(const std::vector& args, const std::string& filename, + public: + virtual ~SectionParser() {} + virtual bool ParseSection(std::vector&& args, const std::string& filename, int line, std::string* err) = 0; - virtual bool ParseLineSection(const std::vector& args, int line, - std::string* err) = 0; - virtual void EndSection() = 0; - virtual void EndFile(const std::string& filename) = 0; + virtual bool ParseLineSection(std::vector&&, int, std::string*) { return true; }; + virtual void EndSection(){}; + virtual void EndFile(){}; }; class Parser { -public: + public: static Parser& GetInstance(); - void DumpState() const; + + // Exposed for testing + Parser(); + bool ParseConfig(const std::string& path); void AddSectionParser(const std::string& name, std::unique_ptr parser); + void set_is_system_etc_init_loaded(bool loaded) { is_system_etc_init_loaded_ = loaded; } @@ -54,9 +79,7 @@ public: 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: - Parser(); - + private: void ParseData(const std::string& filename, const std::string& data); bool ParseConfigFile(const std::string& path); bool ParseConfigDir(const std::string& path); diff --git a/init/init_test.cpp b/init/init_test.cpp new file mode 100644 index 000000000..3da14b5ab --- /dev/null +++ b/init/init_test.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2017 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 + +#include +#include +#include + +#include "action.h" +#include "builtins.h" +#include "import_parser.h" +#include "init_parser.h" +#include "keyword_map.h" +#include "util.h" + +class TestFunctionMap : public KeywordMap { + public: + // Helper for argument-less functions + using BuiltinFunctionNoArgs = std::function; + void Add(const std::string& name, const BuiltinFunctionNoArgs function) { + Add(name, 0, 0, [function](const std::vector&) { + function(); + return 0; + }); + } + + void Add(const std::string& name, std::size_t min_parameters, std::size_t max_parameters, + const BuiltinFunction function) { + builtin_functions_[name] = make_tuple(min_parameters, max_parameters, function); + } + + private: + Map builtin_functions_ = {}; + + const Map& map() const override { return builtin_functions_; } +}; + +using ActionManagerCommand = std::function; + +void TestInit(const std::string& init_script_file, const TestFunctionMap& test_function_map, + const std::vector& commands) { + ActionManager am; + + Action::set_function_map(&test_function_map); + + Parser parser; + parser.AddSectionParser("on", std::make_unique(&am)); + parser.AddSectionParser("import", std::make_unique(&parser)); + + ASSERT_TRUE(parser.ParseConfig(init_script_file)); + + for (const auto& command : commands) { + command(am); + } + + while (am.HasMoreCommands()) { + am.ExecuteOneCommand(); + } +} + +void TestInitText(const std::string& init_script, const TestFunctionMap& test_function_map, + const std::vector& commands) { + TemporaryFile tf; + ASSERT_TRUE(tf.fd != -1); + ASSERT_TRUE(android::base::WriteStringToFd(init_script, tf.fd)); + TestInit(tf.path, test_function_map, commands); +} + +TEST(init, SimpleEventTrigger) { + bool expect_true = false; + std::string init_script = + R"init( +on boot +pass_test +)init"; + + TestFunctionMap test_function_map; + test_function_map.Add("pass_test", [&expect_true]() { expect_true = true; }); + + ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); }; + std::vector commands{trigger_boot}; + + TestInitText(init_script, test_function_map, commands); + + EXPECT_TRUE(expect_true); +} + +TEST(init, EventTriggerOrder) { + std::string init_script = + R"init( +on boot +execute_first + +on boot && property:ro.hardware=* +execute_second + +on boot +execute_third + +)init"; + + int num_executed = 0; + TestFunctionMap test_function_map; + test_function_map.Add("execute_first", [&num_executed]() { EXPECT_EQ(0, num_executed++); }); + test_function_map.Add("execute_second", [&num_executed]() { EXPECT_EQ(1, num_executed++); }); + test_function_map.Add("execute_third", [&num_executed]() { EXPECT_EQ(2, num_executed++); }); + + ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); }; + std::vector commands{trigger_boot}; + + TestInitText(init_script, test_function_map, commands); +} + +TEST(init, EventTriggerOrderMultipleFiles) { + // 6 total files, which should have their triggers executed in the following order: + // 1: start - original script parsed + // 2: first_import - immediately imported by first_script + // 3: dir_a - file named 'a.rc' in dir; dir is imported after first_import + // 4: a_import - file imported by dir_a + // 5: dir_b - file named 'b.rc' in dir + // 6: last_import - imported after dir is imported + + TemporaryFile first_import; + ASSERT_TRUE(first_import.fd != -1); + ASSERT_TRUE(android::base::WriteStringToFd("on boot\nexecute 2", first_import.fd)); + + TemporaryFile dir_a_import; + ASSERT_TRUE(dir_a_import.fd != -1); + ASSERT_TRUE(android::base::WriteStringToFd("on boot\nexecute 4", dir_a_import.fd)); + + TemporaryFile last_import; + ASSERT_TRUE(last_import.fd != -1); + ASSERT_TRUE(android::base::WriteStringToFd("on boot\nexecute 6", last_import.fd)); + + TemporaryDir dir; + // clang-format off + std::string dir_a_script = "import " + std::string(dir_a_import.path) + "\n" + "on boot\n" + "execute 3"; + // clang-format on + // write_file() ensures the right mode is set + ASSERT_TRUE(write_file(std::string(dir.path) + "/a.rc", dir_a_script)); + + ASSERT_TRUE(write_file(std::string(dir.path) + "/b.rc", "on boot\nexecute 5")); + + // clang-format off + std::string start_script = "import " + std::string(first_import.path) + "\n" + "import " + std::string(dir.path) + "\n" + "import " + std::string(last_import.path) + "\n" + "on boot\n" + "execute 1"; + // clang-format on + TemporaryFile start; + ASSERT_TRUE(android::base::WriteStringToFd(start_script, start.fd)); + + int num_executed = 0; + auto execute_command = [&num_executed](const std::vector& args) { + EXPECT_EQ(2U, args.size()); + EXPECT_EQ(++num_executed, std::stoi(args[1])); + return 0; + }; + + TestFunctionMap test_function_map; + test_function_map.Add("execute", 1, 1, execute_command); + + ActionManagerCommand trigger_boot = [](ActionManager& am) { am.QueueEventTrigger("boot"); }; + std::vector commands{trigger_boot}; + + TestInit(start.path, test_function_map, commands); + + EXPECT_EQ(6, num_executed); +} diff --git a/init/keyword_map.h b/init/keyword_map.h index 693d82a11..2b91260ad 100644 --- a/init/keyword_map.h +++ b/init/keyword_map.h @@ -24,9 +24,9 @@ template class KeywordMap { -public: + public: using FunctionInfo = std::tuple; - using Map = const std::map; + using Map = std::map; virtual ~KeywordMap() { } @@ -68,10 +68,10 @@ public: return std::get(function_info); } -private: -//Map of keyword -> -//(minimum number of arguments, maximum number of arguments, function pointer) - virtual Map& map() const = 0; + private: + // Map of keyword -> + // (minimum number of arguments, maximum number of arguments, function pointer) + virtual const Map& map() const = 0; }; #endif diff --git a/init/service.cpp b/init/service.cpp index c0745e340..ab404a165 100644 --- a/init/service.cpp +++ b/init/service.cpp @@ -534,14 +534,14 @@ bool Service::ParseWritepid(const std::vector& args, std::string* e } class Service::OptionParserMap : public KeywordMap { -public: - OptionParserMap() { - } -private: - Map& map() const override; + public: + OptionParserMap() {} + + private: + const Map& map() const override; }; -Service::OptionParserMap::Map& Service::OptionParserMap::map() const { +const Service::OptionParserMap::Map& Service::OptionParserMap::map() const { constexpr std::size_t kMax = std::numeric_limits::max(); // clang-format off static const Map option_parsers = { @@ -877,11 +877,6 @@ ServiceManager& ServiceManager::GetInstance() { } void ServiceManager::AddService(std::unique_ptr service) { - Service* old_service = FindServiceByName(service->name()); - if (old_service) { - LOG(ERROR) << "ignored duplicate definition of service '" << service->name() << "'"; - return; - } services_.emplace_back(std::move(service)); } @@ -1095,7 +1090,7 @@ void ServiceManager::ReapAnyOutstandingChildren() { } } -bool ServiceParser::ParseSection(const std::vector& args, const std::string& filename, +bool ServiceParser::ParseSection(std::vector&& args, const std::string& filename, int line, std::string* err) { if (args.size() < 3) { *err = "services must have a name and a program"; @@ -1108,19 +1103,24 @@ bool ServiceParser::ParseSection(const std::vector& args, const std return false; } + Service* old_service = service_manager_->FindServiceByName(name); + if (old_service) { + *err = "ignored duplicate definition of service '" + name + "'"; + return false; + } + std::vector str_args(args.begin() + 2, args.end()); service_ = std::make_unique(name, str_args); return true; } -bool ServiceParser::ParseLineSection(const std::vector& args, int line, - std::string* err) { - return service_ ? service_->ParseLine(args, err) : false; +bool ServiceParser::ParseLineSection(std::vector&& args, int line, std::string* err) { + return service_ ? service_->ParseLine(std::move(args), err) : false; } void ServiceParser::EndSection() { if (service_) { - ServiceManager::GetInstance().AddService(std::move(service_)); + service_manager_->AddService(std::move(service_)); } } diff --git a/init/service.h b/init/service.h index 6fe0258bc..634fe4e63 100644 --- a/init/service.h +++ b/init/service.h @@ -213,16 +213,17 @@ private: class ServiceParser : public SectionParser { public: - ServiceParser() : service_(nullptr) {} - bool ParseSection(const std::vector& args, const std::string& filename, int line, + ServiceParser(ServiceManager* service_manager) + : service_manager_(service_manager), service_(nullptr) {} + bool ParseSection(std::vector&& args, const std::string& filename, int line, std::string* err) override; - bool ParseLineSection(const std::vector& args, int line, std::string* err) override; + bool ParseLineSection(std::vector&& args, int line, std::string* err) override; void EndSection() override; - void EndFile(const std::string&) override {} private: bool IsValidName(const std::string& name) const; + ServiceManager* service_manager_; std::unique_ptr service_; };