Add "nosigterm" service setting to inhibit sending SIGTERM to service
[oweals/dinit.git] / service.h
index 9c3cde47f62d5b217e93419f3fd539231f539b88..2ad60ba44b708149a4f1fce93e1df3d7a595fbb4 100644 (file)
--- a/service.h
+++ b/service.h
@@ -1,7 +1,9 @@
 #include <string>
 #include <list>
 #include <vector>
+#include <csignal>
 #include "ev.h"
+#include "control.h"
 
 /*
  * Possible service states
  *
  * The total state is a combination of the two, current and desired:
  *      STOPPED/STOPPED  : stopped and will remain stopped
- *      STOPPED/STARTED  : stopped and will be started; waiting for dependencies to start.
- *      STARTING/STARTED : starting, but not yet started. All dependencies have started already.
+ *      STOPPED/STARTED  :  - (this state cannot occur)
+ *      STARTING/STARTED : starting, but not yet started. Dependencies may also be starting.
  *      STARTING/STOPPED : as above, but the service will be stopped again as soon as it has
  *                         completed startup.
  *      STARTED/STARTED  : running and will continue running.
- *      STARTED/STOPPED  : running but will stop; waiting for dependents to stop.
- *      STOPPING/STOPPED : stopping and will stop. All dependents have stopped.
+ *      STARTED/STOPPED  :  - (this state cannot occur)
+ *      STOPPING/STOPPED : stopping and will stop. Dependents may be stopping.
  *      STOPPING/STARTED : as above, but the service will be re-started again once it stops.
  *
  * A scripted service is in the STARTING/STOPPING states during the script execution.
- * A process service is in the STOPPING state when it has been signalled to stop (and is never
- *       in the STARTING state; it moves directly from STOPPED to STARTED).
+ * A process service is in the STOPPING state when it has been signalled to stop, and is
+ *       in the STARTING state when waiting for dependencies to start.
  */
 enum class ServiceState {
     STOPPED,    // service is not running.
-    STARTING,   // service is starting, and will start (or fail to start) in time. All dependencies have started.
+    STARTING,   // service is starting, and will start (or fail to start) in time.
     STARTED,    // service is running,
     STOPPING    // service script is stopping and will stop.
 };
@@ -47,6 +49,13 @@ enum class ServiceType {
 struct OnstartFlags {
     bool release_console : 1;
     bool rw_ready : 1;
+    
+    // Not actually "onstart" commands:
+    bool no_sigterm : 1;  // do not send SIGTERM
+    
+    OnstartFlags() noexcept : release_console(false), rw_ready(false), no_sigterm(false)
+    {
+    }
 };
 
 // Exception while loading a service
@@ -54,8 +63,10 @@ class ServiceLoadExc
 {
     public:
     std::string serviceName;
+    const char *excDescription;
     
-    ServiceLoadExc(std::string serviceName)
+    protected:
+    ServiceLoadExc(std::string serviceName) noexcept
         : serviceName(serviceName)
     {
     }
@@ -64,18 +75,20 @@ class ServiceLoadExc
 class ServiceNotFound : public ServiceLoadExc
 {
     public:
-    ServiceNotFound(std::string serviceName)
+    ServiceNotFound(std::string serviceName) noexcept
         : ServiceLoadExc(serviceName)
     {
+        excDescription = "Not found.";
     }
 };
 
 class ServiceCyclicDependency : public ServiceLoadExc
 {
     public:
-    ServiceCyclicDependency(std::string serviceName)
+    ServiceCyclicDependency(std::string serviceName) noexcept
         : ServiceLoadExc(serviceName)
     {
+        excDescription = "Cyclic dependency.";
     }
 };
 
@@ -84,9 +97,10 @@ class ServiceDescriptionExc : public ServiceLoadExc
     public:
     std::string extraInfo;
     
-    ServiceDescriptionExc(std::string serviceName, std::string extraInfo)
+    ServiceDescriptionExc(std::string serviceName, std::string extraInfo) noexcept
         : ServiceLoadExc(serviceName), extraInfo(extraInfo)
     {
+        excDescription = extraInfo.c_str();
     }    
 };
 
@@ -103,15 +117,15 @@ class ServiceDep
     /* Whether the 'from' service is waiting for the 'to' service to start */
     bool waiting_on;
 
-    ServiceDep(ServiceRecord * from, ServiceRecord * to) : from(from), to(to), waiting_on(false)
+    ServiceDep(ServiceRecord * from, ServiceRecord * to) noexcept : from(from), to(to), waiting_on(false)
     {  }
 
-    ServiceRecord * getFrom()
+    ServiceRecord * getFrom() noexcept
     {
         return from;
     }
 
-    ServiceRecord * getTo()
+    ServiceRecord * getTo() noexcept
     {
         return to;
     }
@@ -147,11 +161,8 @@ class ServiceRecord
     
     string service_name;
     ServiceType service_type;  /* ServiceType::DUMMY, PROCESS, SCRIPTED, INTERNAL */
-    ServiceState service_state; /* ServiceState::STOPPED, STARTING, STARTED, STOPPING */
-    ServiceState desired_state; /* ServiceState::STOPPED / STARTED */
-    bool force_stop; // true if the service must actually stop. This is the
-                     // case if for example the process dies; the service,
-                     // and all its dependencies, MUST be stopped.
+    ServiceState service_state = ServiceState::STOPPED; /* ServiceState::STOPPED, STARTING, STARTED, STOPPING */
+    ServiceState desired_state = ServiceState::STOPPED; /* ServiceState::STOPPED / STARTED */
 
     string program_name;          /* storage for program/script and arguments */
     const char **exec_arg_parts;  /* pointer to each argument/part of the program_name */
@@ -160,7 +171,8 @@ class ServiceRecord
 
     string logfile; /* log file name, empty string specifies /dev/null */
     bool auto_restart; /* whether to restart this (process) if it dies unexpectedly */
-    
+
+
     typedef std::list<ServiceRecord *> sr_list;
     typedef sr_list::iterator sr_iter;
     
@@ -180,6 +192,13 @@ class ServiceRecord
     
     ServiceSet *service_set; // the set this service belongs to
     
+    // Process services:
+    bool force_stop; // true if the service must actually stop. This is the
+                     // case if for example the process dies; the service,
+                     // and all its dependencies, MUST be stopped.
+
+    int term_signal = -1;  // signal to use for process termination
+
     // Implementation details
     
     pid_t pid;  /* PID of the process. If state is STARTING or STOPPING,
@@ -189,13 +208,12 @@ class ServiceRecord
 
     ev_child child_listener;
     
-    // Move service to STOPPING state. This can only be called once
-    // all dependents have stopped.
-    void stopping();
+    // All dependents have stopped.
+    void allDepsStopped();
     
     // Service has actually stopped (includes having all dependents
     // reaching STOPPED state).
-    void stopped();
+    void stopped() noexcept;
     
     // Service has successfully started
     void started();
@@ -209,26 +227,34 @@ class ServiceRecord
     // For process services, start the process, return true on success
     bool start_ps_process() noexcept;
     bool start_ps_process(const std::vector<std::string> &args) noexcept;
-   
+
     // Callback from libev when a child process dies
     static void process_child_callback(struct ev_loop *loop, struct ev_child *w,
-            int revents);
+            int revents) noexcept;
+
+    // A dependency has reached STARTED state
+    void dependencyStarted() noexcept;
+    
+    void allDepsStarted() noexcept;
     
+    // Check whether dependencies have started, and optionally ask them to start
+    bool startCheckDependencies(bool do_start) noexcept;
+
     // A dependent has reached STOPPED state
-    void dependentStopped();
+    void dependentStopped() noexcept;
 
     // check if all dependents have stopped
-    bool stopCheckDependents();
+    bool stopCheckDependents() noexcept;
     
     // issue a stop to all dependents, return true if they are all already stopped
-    bool stopDependents();
+    bool stopDependents() noexcept;
     
-    void forceStop(); // force-stop this service and all dependents
+    void forceStop() noexcept; // force-stop this service and all dependents
     
     public:
 
     ServiceRecord(ServiceSet *set, string name)
-        : service_state(ServiceState::STOPPED), desired_state(ServiceState::STOPPED), force_stop(false), auto_restart(false)
+        : service_state(ServiceState::STOPPED), desired_state(ServiceState::STOPPED), auto_restart(false), force_stop(false)
     {
         service_set = set;
         service_name = name;
@@ -237,7 +263,7 @@ class ServiceRecord
     
     ServiceRecord(ServiceSet *set, string name, ServiceType service_type, string &&command, std::list<std::pair<unsigned,unsigned>> &command_offsets,
             sr_list * pdepends_on, sr_list * pdepends_soft)
-        : service_state(ServiceState::STOPPED), desired_state(ServiceState::STOPPED), force_stop(false), auto_restart(false)
+        : service_state(ServiceState::STOPPED), desired_state(ServiceState::STOPPED), auto_restart(false), force_stop(false)
     {
         service_set = set;
         service_name = name;
@@ -270,24 +296,30 @@ class ServiceRecord
     }
     
     // Set whether this service should automatically restart when it dies
-    void setAutoRestart(bool auto_restart)
+    void setAutoRestart(bool auto_restart) noexcept
     {
         this->auto_restart = auto_restart;
     }
     
     // Set "on start" flags (commands)
-    void setOnstartFlags(OnstartFlags flags)
+    void setOnstartFlags(OnstartFlags flags) noexcept
     {
         this->onstart_flags = flags;
     }
+    
+    // Set an additional signal (other than SIGTERM) to be used to terminate the process
+    void setExtraTerminationSignal(int signo) noexcept
+    {
+        this->term_signal = signo;
+    }
 
-    const char *getServiceName() const { return service_name.c_str(); }
-    ServiceState getState() const { return service_state; }
+    const char *getServiceName() const noexcept { return service_name.c_str(); }
+    ServiceState getState() const noexcept { return service_state; }
     
-    void start();  // start the service
-    void stop();   // stop the service
+    void start() noexcept;  // start the service
+    void stop() noexcept;   // stop the service
     
-    bool isDummy()
+    bool isDummy() noexcept
     {
         return service_type == ServiceType::DUMMY;
     }
@@ -300,14 +332,18 @@ class ServiceSet
     std::list<ServiceRecord *> records;
     const char *service_dir;  // directory containing service descriptions
     bool restart_enabled; // whether automatic restart is enabled (allowed)
+    ControlConn *rollback_handler; // recieves notification when all services stopped
     
     // Private methods
     
     // Locate an existing service record.
-    ServiceRecord *findService(std::string name);
+    ServiceRecord *findService(std::string name) noexcept;
     
     // Load a service description, and dependencies, if there is no existing
     // record for the given name.
+    // Throws:
+    //   ServiceLoadException (or subclass) on problem with service description
+    //   std::bad_alloc on out-of-memory condition
     ServiceRecord *loadServiceRecord(const char *name);
 
     // Public
@@ -323,30 +359,31 @@ class ServiceSet
     // Start the service with the given name. The named service will begin
     // transition to the 'started' state.
     //
-    // Throws an exception if the
-    // service description cannot be loaded.
+    // Throws a ServiceLoadException (or subclass) if the service description
+    // cannot be loaded or is invalid;
+    // Throws std::bad_alloc if out of memory.
     void startService(const char *name);
     
     // Stop the service with the given name. The named service will begin
     // transition to the 'stopped' state.
-    void stopService(const std::string &name);
+    void stopService(const std::string &name) noexcept;
     
     // Notification from service that it is active (state != STOPPED)
     // Only to be called on the transition from inactive to active.
-    void service_active(ServiceRecord *);
+    void service_active(ServiceRecord *) noexcept;
     
     // Notification from service that it is inactive (STOPPED)
     // Only to be called on the transition from active to inactive.
-    void service_inactive(ServiceRecord *);
+    void service_inactive(ServiceRecord *) noexcept;
     
     // Find out how many services are active (starting, running or stopping,
     // but not stopped).
-    int count_active_services()
+    int count_active_services() noexcept
     {
         return active_services;
     }
     
-    void stop_all_services()
+    void stop_all_services() noexcept
     {
         restart_enabled = false;
         for (std::list<ServiceRecord *>::iterator i = records.begin(); i != records.end(); ++i) {
@@ -354,13 +391,34 @@ class ServiceSet
         }
     }
     
-    void set_auto_restart(bool restart)
+    void set_auto_restart(bool restart) noexcept
     {
         restart_enabled = restart;
     }
     
-    bool get_auto_restart()
+    bool get_auto_restart() noexcept
     {
         return restart_enabled;
     }
+    
+    // Set the rollback handler, which will be notified when all services have stopped.
+    // There can be only one rollback handler; attempts to set it when already set will
+    // fail. Returns true if successful.
+    bool setRollbackHandler(ControlConn *conn) noexcept
+    {
+        if (rollback_handler == nullptr) {
+            rollback_handler = conn;
+            return true;
+        }
+        else {
+            return false;
+        }
+    }
+    
+    void clearRollbackHandler(ControlConn *conn) noexcept
+    {
+        if (rollback_handler == conn) {
+            rollback_handler = nullptr;
+        }
+    }
 };