#include <memory>
#include <cstddef>
-#include <sys/types.h>
-#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
-#include <unistd.h>
#include <termios.h>
#include "dinit.h"
#include "dinit-log.h"
#include "dinit-socket.h"
#include "dinit-util.h"
+#include "baseproc-sys.h"
/*
* service.cc - Service management.
{
using std::list;
list<service_record *>::const_iterator i = records.begin();
- for ( ; i != records.end(); i++ ) {
+ for ( ; i != records.end(); ++i ) {
if (strcmp((*i)->get_name().c_str(), name) == 0) {
return *i;
}
// is due to an unexpected process termination.
void service_record::stopped() noexcept
{
- if (onstart_flags.runs_on_console) {
- tcsetpgrp(0, getpgrp());
- discard_console_log_buffer();
+ if (have_console) {
+ bp_sys::tcsetpgrp(0, bp_sys::getpgrp());
release_console();
}
force_stop = false;
- // If we are a soft dependency of another target, break the acquisition from that target now:
+ // If we are a soft dependency of another target, break the acquisition from that target now,
+ // so that we don't re-start:
for (auto & dependent : dependents) {
if (dependent->dep_type != dependency_type::REGULAR) {
- if (dependent->holding_acq) {
+ if (dependent->holding_acq && ! dependent->waiting_on) {
dependent->holding_acq = false;
release();
}
}
bool will_restart = (desired_state == service_state_t::STARTED)
- && services->get_auto_restart();
+ && !services->is_shutting_down();
- for (auto dependency : depends_on) {
+ for (auto & dependency : depends_on) {
// we signal dependencies in case they are waiting for us to stop:
dependency.get_to()->dependent_stopped();
}
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();
}
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.
+ prop_release = !prop_require;
+ prop_require = false;
+ services->add_prop_queue(this);
services->service_inactive(this);
}
}
- log_service_stopped(service_name);
+ // 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 other service now:
+ if (! will_restart && ! start_on_completion.empty()) {
+ 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);
}
bool service_record::do_auto_restart() noexcept
{
if (auto_restart) {
- return services->get_auto_restart();
+ return !services->is_shutting_down();
}
return false;
}
prop_require = !prop_release;
prop_release = false;
services->add_prop_queue(this);
+ if (service_state != service_state_t::STARTING && service_state != service_state_t::STARTED) {
+ prop_start = true;
+ }
}
}
services->service_inactive(this);
}
else if (issue_stop) {
+ stop_reason = stopped_reason_t::NORMAL;
do_stop();
}
}
for (auto & dependency : depends_on) {
service_record * dep_to = dependency.get_to();
if (dependency.holding_acq) {
- dep_to->release();
+ // 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();
}
}
}
services->service_active(this);
}
+ start_failed = false;
+ start_skipped = false;
service_state = service_state_t::STARTING;
waiting_for_deps = true;
if (prop_failure) {
prop_failure = false;
+ stop_reason = stopped_reason_t::DEPFAILED;
failed_to_start(true);
}
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);
}
}
-
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) {
- tcsetpgrp(0, getpgrp());
+ bp_sys::tcsetpgrp(0, bp_sys::getpgrp());
release_console();
}
notify_listeners(service_event_t::STARTED);
if (onstart_flags.rw_ready) {
- open_control_socket();
+ rootfs_is_rw();
}
if (onstart_flags.log_ready) {
setup_external_log();
}
}
-void service_record::failed_to_start(bool depfailed) noexcept
+void service_record::failed_to_start(bool depfailed, bool immediate_stop) noexcept
{
- if (have_console) {
- tcsetpgrp(0, getpgrp());
- release_console();
- }
if (waiting_for_console) {
services->unqueue_console(this);
waiting_for_console = false;
}
-
- log_service_failed(get_name());
- service_state = service_state_t::STOPPED;
+
if (start_explicit) {
start_explicit = false;
release(false);
}
- notify_listeners(service_event_t::FAILEDSTART);
-
+
// Cancel start of dependents:
for (auto & dept : dependents) {
switch (dept->dep_type) {
dept->waiting_on = false;
dept->get_from()->dependency_started();
}
- if (dept->holding_acq) {
- dept->holding_acq = false;
- release();
- }
}
+
+ // 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());
+ notify_listeners(service_event_t::FAILEDSTART);
+
+ if (immediate_stop) {
+ stopped();
}
}
{
if (service_state != service_state_t::STOPPED) {
force_stop = true;
- services->add_transition_queue(this);
+ if (! pinned_started) {
+ prop_stop = true;
+ services->add_prop_queue(this);
+ }
}
}
release();
}
- if (bring_down) {
+ if (bring_down && service_state != service_state_t::STOPPED
+ && service_state != service_state_t::STOPPING) {
+ stop_reason = stopped_reason_t::NORMAL;
do_stop();
}
}
void service_record::do_stop() noexcept
{
- if (pinned_started) return;
-
+ // A service that does actually stop for any reason should have its explicit activation released, unless
+ // it will restart:
if (start_explicit && ! do_auto_restart()) {
start_explicit = false;
release(false);
// 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.
+ // 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;
}
}
}
}
+ if (pinned_started) return;
+
service_state = service_state_t::STOPPING;
waiting_for_deps = true;
if (all_deps_stopped) {
{
bool all_deps_stopped = true;
for (auto dept : dependents) {
- if (dept->dep_type == dependency_type::REGULAR) {
+ if (dept->dep_type == dependency_type::REGULAR ||
+ (dept->dep_type == dependency_type::MILESTONE &&
+ dept->get_from()->service_state != service_state_t::STARTED)) {
if (! dept->get_from()->is_stopped()) {
// Note we check *first* since if the dependent service is not stopped,
// 1. We will issue a stop to it shortly and
dept->get_from()->prop_stop = true;
services->add_prop_queue(dept->get_from());
}
+ else {
+ // 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 should be called only when this
+ // service is already stopped/stopping:
+ release(false);
+ }
+ }
}
return all_deps_stopped;
bool service_record::interrupt_start() noexcept
{
- services->unqueue_console(this);
return true;
}