#include #include #include #include #include #include #include #include #include "dinit.h" #include "service.h" #include "dinit-log.h" #include "dinit-socket.h" #include "dinit-util.h" #include "baseproc-sys.h" /* * service.cc - Service management. * See service.h for details. */ // Find the requested service by name. static service_record * find_service(const std::list & records, const char *name) noexcept { using std::list; list::const_iterator i = records.begin(); for ( ; i != records.end(); ++i ) { if (strcmp((*i)->get_name().c_str(), name) == 0) { return *i; } } return nullptr; } service_record * service_set::find_service(const std::string &name, bool find_placeholders) noexcept { service_record *r = ::find_service(records, name.c_str()); if (r != nullptr && !find_placeholders && r->get_type() == service_type_t::PLACEHOLDER) { return nullptr; } return r; } void service_record::prepare_for_unload() noexcept { // Remove all dependencies: for (auto &dep : depends_on) { service_record *dependency = dep.get_to(); auto &dep_dpts = dependency->dependents; dep_dpts.erase(std::find(dep_dpts.begin(), dep_dpts.end(), &dep)); if (dep.dep_type == dependency_type::AFTER) { if (dependency->get_type() == service_type_t::PLACEHOLDER) { if (dependency->is_unrefd()) { services->remove_service(dependency); delete dependency; } } } } depends_on.clear(); // Also remove all dependents. This should not be necessary except for "before" links. for (auto i = dependents.begin(); i != dependents.end();) { service_record *before_svc = (*i)->get_from(); before_svc->rm_dep(**i++); if (before_svc->get_type() == service_type_t::PLACEHOLDER && before_svc->is_unrefd()) { services->remove_service(before_svc); delete before_svc; } } } // Called when a service has actually stopped; dependents have stopped already, unless this stop // is due to an unexpected process termination. void service_record::stopped() noexcept { if (have_console) { bp_sys::tcsetpgrp(0, bp_sys::getpgrp()); release_console(); } force_stop = false; // If we are to re-start, restarting should have been set true and desired_state should be STARTED. // (A restart could be cancelled via a separately issued stop, including via a shutdown). bool will_restart = desired_state == service_state_t::STARTED && !pinned_stopped; // If we won't restart, break soft dependencies now if (! will_restart) { for (auto dept : dependents) { if (!dept->is_hard()) { // waits-for or soft dependency: if (dept->waiting_on) { dept->waiting_on = false; dept->get_from()->dependency_started(); } if (dept->holding_acq) { dept->holding_acq = false; // release without issuing stop, since we're called only when this // service is already stopped/stopping: release(false); } } } } for (auto & dependency : depends_on) { // we signal dependencies in case they are waiting for us to stop: dependency.get_to()->dependent_stopped(); } service_state = service_state_t::STOPPED; if (will_restart) { // Desired state is "started". initiate_start(); } else { becoming_inactive(); if (start_explicit) { // If we were explicitly started, our required_by count must be at least 1. Use // release() to correctly release, mark inactive and release dependencies. start_explicit = false; release(false); } else if (required_by == 0) { // This can only be the case if we didn't have start_explicit, since required_by would // otherwise by non-zero. Since our release(s) above were with state != STOPPED, we now // must mark inactive (i.e. it won't have been done as part of the release). services->service_inactive(this); } } // Start failure will have been logged already, only log if we are stopped for other reasons: if (! start_failed) { log_service_stopped(service_name); // If this service chains to another, start the chained service now, if: // - this service self-terminated (rather than being stopped), // - ... successfully (i.e. exit code 0) // - this service won't restart, and // - a shutdown isn't in progress if ((onstart_flags.always_chain || (did_finish(stop_reason) && get_exit_status() == 0 && ! will_restart)) && ! start_on_completion.empty() && ! services->is_shutting_down()) { try { auto chain_to = services->load_service(start_on_completion.c_str()); chain_to->start(); } catch (service_load_exc &sle) { log(loglevel_t::ERROR, "Couldn't chain to service ", start_on_completion, ": ", "couldn't load ", sle.service_name, ": ", sle.exc_description); } catch (std::bad_alloc &bae) { log(loglevel_t::ERROR, "Couldn't chain to service ", start_on_completion, ": Out of memory"); } } } notify_listeners(service_event_t::STOPPED); } void service_record::require() noexcept { if (required_by++ == 0) { if (service_state != service_state_t::STARTING && service_state != service_state_t::STARTED) { prop_start = true; services->add_prop_queue(this); // Note: pin is checked in start(). // Require will be propagated to dependencies if/when the service actually starts. } } } void service_record::release(bool issue_stop) noexcept { if (--required_by == 0) { if (service_state == service_state_t::STOPPING) { // If we are stopping but would have restarted, we now need to notify that the restart // has been cancelled. Other start-cancelled cases are handled by do_stop() (called // below). if (desired_state == service_state_t::STARTED && !is_start_pinned()) { notify_listeners(service_event_t::STARTCANCELLED); } } desired_state = service_state_t::STOPPED; if (is_start_pinned()) return; // Can stop, and can release dependencies now. We don't need to issue a release if // a require was pending though: prop_release = !prop_require; prop_require = false; if (prop_release) { services->add_prop_queue(this); } if (service_state != service_state_t::STOPPED && service_state != service_state_t::STOPPING && issue_stop) { stop_reason = stopped_reason_t::NORMAL; do_stop(); } } } void service_record::release_dependencies() noexcept { for (auto & dependency : depends_on) { service_record * dep_to = dependency.get_to(); if (dependency.holding_acq) { // We must clear holding_acq before calling release, otherwise the dependency // may decide to stop, check this link and release itself a second time. dependency.holding_acq = false; dep_to->release(); } } } void service_record::start() noexcept { if (pinned_stopped) { // bail out early for this case, we don't want to set start_explicit return; } if (!start_explicit) { ++required_by; start_explicit = true; } do_start(); } void service_record::initiate_start() noexcept { start_failed = false; start_skipped = false; service_state = service_state_t::STARTING; waiting_for_deps = true; if (start_check_dependencies()) { // Note: we can't set waiting_for_deps false here since ordering dependencies might still kick in services->add_transition_queue(this); } } void service_record::do_propagation() noexcept { if (prop_require) { // Need to require all our dependencies for (auto & dep : depends_on) { if (!dep.is_only_ordering()) { dep.get_to()->require(); dep.holding_acq = true; } } prop_require = false; } if (prop_release) { release_dependencies(); prop_release = false; } if (prop_failure) { prop_failure = false; stop_reason = stopped_reason_t::DEPFAILED; service_state = service_state_t::STOPPED; failed_to_start(true); } if (prop_start) { prop_start = false; do_start(); } if (prop_stop) { prop_stop = false; do_stop(in_user_restart); } if (prop_pin_dpt) { bool dept_pin = false; for (auto *dept : dependents) { if (dept->is_hard() && dept->get_from()->is_start_pinned()) { dept_pin = true; break; } } if (dept_pin != dept_pinned_started) { dept_pinned_started = dept_pin; for (auto &dep : depends_on) { if (dep.is_hard() && dep.get_to()->dept_pinned_started != dept_pin) { dep.get_to()->prop_pin_dpt = true; services->add_prop_queue(dep.get_to()); } } if (!dept_pinned_started && !pinned_started) { // No longer pinned at all if ((desired_state == service_state_t::STOPPED || force_stop) && service_state == service_state_t::STARTED) { do_stop(); } } } } } void service_record::execute_transition() noexcept { if (service_state == service_state_t::STARTING) { if (check_deps_started()) { waiting_for_deps = false; all_deps_started(); } } else if (service_state == service_state_t::STOPPING) { if (stop_check_dependents()) { waiting_for_deps = false; if (onstart_flags.kill_all_on_stop) { log(loglevel_t::NOTICE, true, "Sending TERM/KILL to all processes...\n"); kill(-1, SIGTERM); sleep(1); kill(-1, SIGKILL); } bring_down(); } } } void service_record::do_start() noexcept { bool was_active = service_state != service_state_t::STOPPED; desired_state = service_state_t::STARTED; if (pinned_stopped) { if (!was_active) { failed_to_start(false, false); } return; } // re-attach any soft dependents, now that we are starting again if (!was_active) { for (auto dept : dependents) { if (!dept->is_hard()) { service_state_t dept_state = dept->get_from()->service_state; if (!dept->holding_acq && (dept_state == service_state_t::STARTED || dept_state == service_state_t::STARTING)) { dept->holding_acq = true; ++required_by; } } } } if (was_active) { // We're already starting/started, or we are stopping and need to wait for // that the complete. if (service_state != service_state_t::STOPPING) { return; } if (! can_interrupt_stop()) { return; } // We're STOPPING, and that can be interrupted. Our dependencies might be STOPPING, // but if so they are waiting (for us), so they too can be instantly returned to // STARTING state. notify_listeners(service_event_t::STOPCANCELLED); } else { // !was_active services->service_active(this); prop_require = !prop_release; prop_release = false; if (prop_require) { services->add_prop_queue(this); } } initiate_start(); } void service_record::dependency_started() noexcept { // Note that we check for STARTED state here in case the service is in smooth recovery while pinned. // In that case it will wait for dependencies to start before restarting the process. if ((service_state == service_state_t::STARTING || service_state == service_state_t::STARTED) && waiting_for_deps) { services->add_transition_queue(this); } } bool service_record::start_check_dependencies() noexcept { bool all_deps_started = true; for (auto & dep : depends_on) { service_record * to = dep.get_to(); if (dep.is_only_ordering() && to->service_state != service_state_t::STARTING) continue; if (to->service_state != service_state_t::STARTED) { dep.waiting_on = true; all_deps_started = false; } } for (auto * dept : dependents) { if (!dept->waiting_on && dept->is_only_ordering()) { service_record *from = dept->get_from(); if (from->get_state() == service_state_t::STARTING) { dept->waiting_on = true; } } } return all_deps_started; } bool service_record::check_deps_started() noexcept { for (auto & dep : depends_on) { if (dep.waiting_on) { return false; } } return true; } void service_record::all_deps_started() noexcept { if (onstart_flags.starts_on_console && !have_console) { queue_for_console(); return; } waiting_for_deps = false; if (!bring_up()) { service_state = service_state_t::STOPPING; failed_to_start(); } } void service_record::acquired_console() noexcept { waiting_for_console = false; have_console = true; if (service_state != service_state_t::STARTING) { // We got the console but no longer want it. release_console(); } else if (check_deps_started()) { all_deps_started(); } else { // We got the console but can't use it yet. release_console(); } } void service_record::started() noexcept { // If we start on console but don't keep it, release it now: if (have_console && !onstart_flags.runs_on_console) { bp_sys::tcsetpgrp(0, bp_sys::getpgrp()); release_console(); } log_service_started(get_name()); service_state = service_state_t::STARTED; notify_listeners(service_event_t::STARTED); if (onstart_flags.rw_ready) { rootfs_is_rw(); } if (onstart_flags.log_ready) { setup_external_log(); } if (force_stop || desired_state == service_state_t::STOPPED) { // We must now stop. do_stop(); return; } // Notify any dependents whose desired state is STARTED: for (auto dept : dependents) { if (dept->waiting_on) { dept->get_from()->dependency_started(); dept->waiting_on = false; } } } void service_record::failed_to_start(bool depfailed, bool immediate_stop) noexcept { desired_state = service_state_t::STOPPED; if (waiting_for_console) { services->unqueue_console(this); waiting_for_console = false; } if (start_explicit) { start_explicit = false; release(false); } // Cancel start of dependents: for (auto & dept : dependents) { switch (dept->dep_type) { case dependency_type::REGULAR: case dependency_type::MILESTONE: // If REGULAR and STARTED, we can't have failed to start i.e. we must be started, so // we don't worry about that case. If MILESTONE and started the dependency is already // satisfied so again we don't need to do anything. if (dept->get_from()->service_state == service_state_t::STARTING) { dept->get_from()->prop_failure = true; services->add_prop_queue(dept->get_from()); } break; case dependency_type::WAITS_FOR: case dependency_type::SOFT: case dependency_type::BEFORE: case dependency_type::AFTER: if (dept->waiting_on) { dept->waiting_on = false; dept->get_from()->dependency_started(); } } // Always release now, so that our desired state will be STOPPED before we call // stopped() below (if we do so). Otherwise it may decide to restart us. if (dept->holding_acq) { dept->holding_acq = false; release(false); } } start_failed = true; log_service_failed(get_name(), depfailed); notify_listeners(service_event_t::FAILEDSTART); pinned_started = false; if (immediate_stop) { stopped(); } } void service_record::unrecoverable_stop() noexcept { desired_state = service_state_t::STOPPED; forced_stop(); } bool service_record::bring_up() noexcept { // default implementation: there is no process, so we are started. started(); return true; } // Mark this and all dependent services to be force-stopped. void service_record::forced_stop() noexcept { if (service_state != service_state_t::STOPPED) { force_stop = true; if (!is_start_pinned()) { prop_stop = true; services->add_prop_queue(this); } } } void service_record::dependent_stopped() noexcept { if (service_state == service_state_t::STOPPING && waiting_for_deps) { services->add_transition_queue(this); } } void service_record::stop(bool bring_down) noexcept { // Stop; remove activation, and don't self-restart. if (start_explicit) { start_explicit = false; required_by--; } if (bring_down || required_by == 0) { // Set desired state to STOPPED, this will inhibit automatic restart (and will be // propagated to dependents) desired_state = service_state_t::STOPPED; } if (is_start_pinned()) { return; } // If our required_by count is 0, we should treat this as a full manual stop regardless if (required_by == 0) { bring_down = true; prop_release = !prop_require; if (prop_release) { services->add_prop_queue(this); } } if (bring_down && service_state != service_state_t::STOPPED) { // Note even if we are already STOPPING we should call do_stop for the case where we need // to interrupt any currently ongoing restart of dependents. stop_reason = stopped_reason_t::NORMAL; do_stop(); } } bool service_record::restart() noexcept { // Re-start without affecting dependency links/activation. Hard dependents will be similarly // restarted. if (service_state == service_state_t::STARTED) { stop_reason = stopped_reason_t::NORMAL; force_stop = true; do_stop(true); return true; } // Wrong state return false; } void service_record::do_stop(bool with_restart) noexcept { // Called when we should definitely stop. We may need to restart afterwards, but we // won't know that for sure until the execution transition. // Note: to inhibit automatic restart, including restart due to dependent still requiring this // service, caller must first set desired_state to STOPPED if (is_start_pinned()) return; in_auto_restart = false; in_user_restart = false; // Will we restart? desired state of STOPPED inhibits auto-restart bool for_restart = with_restart || (auto_restart && desired_state == service_state_t::STARTED); bool restart_deps = with_restart; if (!with_restart && for_restart) { // auto restart - check for restarting too often for_restart = check_restart(); in_auto_restart = for_restart; } // If we won't restart, release explicit activation: if (!for_restart) { if (start_explicit) { start_explicit = false; release(false); } } bool all_deps_stopped = stop_dependents(for_restart, restart_deps); if (service_state != service_state_t::STARTED) { if (service_state == service_state_t::STARTING) { // If waiting for a dependency, or waiting for the console, we can interrupt start. Otherwise, // we need to delegate to can_interrupt_start() (which can be overridden). if (!waiting_for_deps && !waiting_for_console) { if (!can_interrupt_start()) { // Well this is awkward: we're going to have to continue starting. We can stop once // we've reached the started state. return; } if (!interrupt_start()) { // Now wait for service startup to actually end; we don't need to handle it here. notify_listeners(service_event_t::STARTCANCELLED); return; } } else if (waiting_for_console) { services->unqueue_console(this); waiting_for_console = false; } // We must have had desired_state == STARTED. notify_listeners(service_event_t::STARTCANCELLED); // Reaching this point, we are starting interruptibly - so we // stop now (by falling through to below). } else { // If we're starting we need to wait for that to complete. // If we're already stopping/stopped there's nothing to do. return; } } service_state = service_state_t::STOPPING; waiting_for_deps = !all_deps_stopped; if (all_deps_stopped) { services->add_transition_queue(this); } } bool service_record::stop_check_dependents() noexcept { bool all_deps_stopped = true; for (auto dept : dependents) { // Note if the dependent is waiting on us, it must be restarting (since the // waiting_on flag gets cleared when we stop, and would only be set if the // service tries to restart). We can treat that as "stopped" for purposes of // checking whether we can transition to stopped state. if (dept->is_hard() && dept->holding_acq && !dept->waiting_on) { all_deps_stopped = false; break; } } return all_deps_stopped; } bool service_record::stop_dependents(bool for_restart, bool restart_deps) noexcept { // We are in either STARTED or STARTING states. bool all_deps_stopped = true; for (auto dept : dependents) { if (dept->is_hard()) { service_record *dep_from = dept->get_from(); if (!dep_from->is_fundamentally_stopped()) { // Note we check *first* since if the dependent service is not stopped, // 1. We will issue a stop to it shortly and // 2. It will notify us when stopped, at which point the stop_check_dependents() // check is run anyway. all_deps_stopped = false; } if (force_stop) { // If this service is to be forcefully stopped, dependents must also be. if (desired_state == service_state_t::STOPPED) { // If our target state was forced to STOPPED, this is a failure dep_from->stop_reason = stopped_reason_t::DEPFAILED; dep_from->unrecoverable_stop(); } else { dep_from->forced_stop(); } } if (dep_from->get_state() != service_state_t::STOPPED) { if (desired_state == service_state_t::STOPPED) { if (dep_from->desired_state != service_state_t::STOPPED) { // if we don't want to restart, don't restart dependent dep_from->desired_state = service_state_t::STOPPED; if (dep_from->start_explicit) { dep_from->start_explicit = false; dep_from->release(true); } dep_from->prop_stop = true; services->add_prop_queue(dep_from); } } else if (restart_deps && dep_from->get_state() != service_state_t::STOPPING) { // restart dependent and propagate restart to all their hard dependents. dep_from->stop_reason = stopped_reason_t::DEPRESTART; dep_from->in_user_restart = true; dep_from->prop_stop = true; services->add_prop_queue(dep_from); } } } // Note that soft dependencies are retained if restarting, but otherwise // they are broken. else if (!for_restart) { if (dept->waiting_on) { // Note, milestone which is still waiting is considered a hard dependency and // is handled above. This is therefore a true soft dependency, and we can just // break the dependency link. dept->waiting_on = false; dept->get_from()->dependency_started(); } if (dept->holding_acq) { dept->holding_acq = false; release(false); } } } return all_deps_stopped; } // All dependents have stopped; we can stop now, too. Only called when STOPPING. void service_record::bring_down() noexcept { stopped(); } void service_record::pin_start() noexcept { if (!pinned_started) { if (!dept_pinned_started) { for (auto &dep : depends_on) { if (dep.is_hard() && !dep.get_to()->dept_pinned_started) { dep.get_to()->prop_pin_dpt = true; services->add_prop_queue(dep.get_to()); } } } pinned_started = true; } } void service_record::unpin() noexcept { if (pinned_started) { pinned_started = false; if (dept_pinned_started) return; // unpin dependencies for (auto &dep : depends_on) { if (dep.is_hard() && dep.get_to()->dept_pinned_started) { dep.get_to()->prop_pin_dpt = true; services->add_prop_queue(dep.get_to()); } } // We only need special handling here if service was in STARTED state if (service_state == service_state_t::STARTED) { // If any dependents are stopping, then force_stop should already be set. // If we reached required_by 0, we need to propagate release now (since it wasn't // propagated as it normally would be when we hit 0, due to the pin) if (required_by == 0) { prop_release = true; services->add_prop_queue(this); } if (desired_state == service_state_t::STOPPED || force_stop) { do_stop(); services->process_queues(); } } } if (pinned_stopped) { pinned_stopped = false; // We don't need to check state. If we're pinned stopped we can't be required and so desired // state should always be stopped. } } void service_record::queue_for_console() noexcept { waiting_for_console = true; services->append_console_queue(this); } void service_record::release_console() noexcept { have_console = false; services->pull_console_queue(); } bool service_record::interrupt_start() noexcept { return true; } void service_set::service_active(service_record *sr) noexcept { active_services++; } void service_set::service_inactive(service_record *sr) noexcept { active_services--; } bool triggered_service::bring_up() noexcept { if (is_triggered) { started(); } return true; }