diff --git a/init/README.md b/init/README.md index 4b21e32ea..5c07ac1ed 100644 --- a/init/README.md +++ b/init/README.md @@ -332,6 +332,13 @@ runs the service. This is particularly useful for creating a periodic service combined with the restart_period option described above. +`updatable` +> Mark that the service can be overridden (via the 'override' option) later in + the boot sequence by APEXes. When a service with updatable option is started + before APEXes are all activated, the execution is delayed until the activation + is finished. A service that is not marked as updatable cannot be overridden by + APEXes. + `user ` > Change to 'username' before exec'ing this service. Currently defaults to root. (??? probably should default to nobody) diff --git a/init/builtins.cpp b/init/builtins.cpp index 8660d2a99..5676f9206 100644 --- a/init/builtins.cpp +++ b/init/builtins.cpp @@ -1079,6 +1079,7 @@ static Result do_parse_apex_configs(const BuiltinArguments& args) { } success &= parser.ParseConfigFile(c); } + ServiceList::GetInstance().MarkServicesUpdate(); if (success) { return Success(); } else { diff --git a/init/service.cpp b/init/service.cpp index 1bda7ecfb..5aa376450 100644 --- a/init/service.cpp +++ b/init/service.cpp @@ -765,6 +765,11 @@ Result Service::ParseWritepid(std::vector&& args) { return Success(); } +Result Service::ParseUpdatable(std::vector&& args) { + updatable_ = true; + return Success(); +} + class Service::OptionParserMap : public KeywordMap { public: OptionParserMap() {} @@ -817,6 +822,7 @@ const Service::OptionParserMap::Map& Service::OptionParserMap::map() const { {"socket", {3, 6, &Service::ParseSocket}}, {"timeout_period", {1, 1, &Service::ParseTimeoutPeriod}}, + {"updatable", {0, 0, &Service::ParseUpdatable}}, {"user", {1, 1, &Service::ParseUser}}, {"writepid", {1, kMax, &Service::ParseWritepid}}, }; @@ -834,6 +840,13 @@ Result Service::ParseLine(std::vector&& args) { } Result Service::ExecStart() { + if (is_updatable() && !ServiceList::GetInstance().IsServicesUpdated()) { + // Don't delay the service for ExecStart() as the semantic is that + // the caller might depend on the side effect of the execution. + return Error() << "Cannot start an updatable service '" << name_ + << "' before configs from APEXes are all loaded"; + } + flags_ |= SVC_ONESHOT; if (auto result = Start(); !result) { @@ -851,6 +864,13 @@ Result Service::ExecStart() { } Result Service::Start() { + if (is_updatable() && !ServiceList::GetInstance().IsServicesUpdated()) { + ServiceList::GetInstance().DelayService(*this); + return Error() << "Cannot start an updatable service '" << name_ + << "' before configs from APEXes are all loaded. " + << "Queued for execution."; + } + bool disabled = (flags_ & (SVC_DISABLED | SVC_RESET)); // Starting a service removes it from the disabled or reset state and // immediately takes it out of the restarting state if it was in there. @@ -1280,6 +1300,32 @@ void ServiceList::DumpState() const { } } +void ServiceList::MarkServicesUpdate() { + services_update_finished_ = true; + + // start the delayed services + for (const auto& name : delayed_service_names_) { + Service* service = FindService(name); + if (service == nullptr) { + LOG(ERROR) << "delayed service '" << name << "' could not be found."; + continue; + } + if (auto result = service->Start(); !result) { + LOG(ERROR) << result.error_string(); + } + } + delayed_service_names_.clear(); +} + +void ServiceList::DelayService(const Service& service) { + if (services_update_finished_) { + LOG(ERROR) << "Cannot delay the start of service '" << service.name() + << "' because all services are already updated. Ignoring."; + return; + } + delayed_service_names_.emplace_back(service.name()); +} + Result ServiceParser::ParseSection(std::vector&& args, const std::string& filename, int line) { if (args.size() < 3) { @@ -1291,6 +1337,8 @@ Result ServiceParser::ParseSection(std::vector&& args, return Error() << "invalid service name '" << name << "'"; } + filename_ = filename; + Subcontext* restart_action_subcontext = nullptr; if (subcontexts_) { for (auto& subcontext : *subcontexts_) { @@ -1326,6 +1374,11 @@ Result ServiceParser::EndSection() { << "'"; } + if (StartsWith(filename_, "/apex/") && !old_service->is_updatable()) { + return Error() << "cannot update a non-updatable service '" << service_->name() + << "' with a config in APEX"; + } + service_list_->RemoveService(*old_service); old_service = nullptr; } diff --git a/init/service.h b/init/service.h index 49b09ceca..56e75b0bf 100644 --- a/init/service.h +++ b/init/service.h @@ -123,6 +123,7 @@ class Service { std::chrono::seconds restart_period() const { return restart_period_; } std::optional timeout_period() const { return timeout_period_; } const std::vector& args() const { return args_; } + bool is_updatable() const { return updatable_; } private: using OptionParser = Result (Service::*)(std::vector&& args); @@ -170,6 +171,7 @@ class Service { Result ParseFile(std::vector&& args); Result ParseUser(std::vector&& args); Result ParseWritepid(std::vector&& args); + Result ParseUpdatable(std::vector&& args); template Result AddDescriptor(std::vector&& args); @@ -235,6 +237,8 @@ class Service { std::chrono::seconds restart_period_ = 5s; std::optional timeout_period_; + bool updatable_ = false; + std::vector args_; std::vector> reap_callbacks_; @@ -279,8 +283,15 @@ class ServiceList { const std::vector>& services() const { return services_; } const std::vector services_in_shutdown_order() const; + void MarkServicesUpdate(); + bool IsServicesUpdated() const { return services_update_finished_; } + void DelayService(const Service& service); + private: std::vector> services_; + + bool services_update_finished_ = false; + std::vector delayed_service_names_; }; class ServiceParser : public SectionParser { @@ -291,6 +302,7 @@ class ServiceParser : public SectionParser { int line) override; Result ParseLineSection(std::vector&& args, int line) override; Result EndSection() override; + void EndFile() override { filename_ = ""; } private: bool IsValidName(const std::string& name) const; @@ -298,6 +310,7 @@ class ServiceParser : public SectionParser { ServiceList* service_list_; std::vector* subcontexts_; std::unique_ptr service_; + std::string filename_; }; } // namespace init