Finish soft dependency support.
[oweals/dinit.git] / service.h
1 #include <string>
2 #include <list>
3 #include <vector>
4 #include "ev.h"
5
6 /*
7  * Possible service states
8  *
9  * Services have both a current state and a desired state. The desired state can be
10  * either SVC_STARTED or SVC_STOPPED. The current state can also be SVC_STARTING
11  * or SVC_STOPPING.
12  *
13  * The total state is a combination of the two, current and desired:
14  * SVC_STOPPED/SVC_STOPPED  : stopped and will remain stopped
15  * SVC_STOPPED/SVC_STARTED  : stopped and will be started; waiting for dependencies to start.
16  * SVC_STARTING/SVC_STARTED : starting, but not yet started. All dependencies have started already.
17  * SVC_STARTING/SVC_STOPPED : as above, but the service will be stopped again as soon as it has
18  *                            completed startup.
19  * SVC_STARTED/SVC_STARTED  : running and will continue running.
20  * SVC_STARTED/SVC_STOPPED  : running but will stop; waiting for dependents to stop.
21  * SVC_STOPPING/SVC_STOPPED : stopping and will stop. All dependents have stopped.
22  * SVC_STOPPING/SVC_STARTED : as above, but the service will be re-started again once it stops.
23  *
24  * A scripted service is in the STARTING/STOPPING states during the script execution.
25  * A process service is in the STOPPING state when it has been signalled to stop (and is never
26  *       in the STARTING state; it moves directly from STOPPED to STARTED).
27  */
28 // TODO can we use typesafe enum?
29 constexpr static int SVC_STOPPED  = 0;  // service is not running
30 constexpr static int SVC_STARTING = 1;  // service is starting, and will start (or fail to start) in time. All dependencies have started.
31 constexpr static int SVC_STARTED  = 2;  // service is running
32 constexpr static int SVC_STOPPING = 3;  // service script is stopping and will stop.
33
34
35 /* Service types */
36 #define SVC_DUMMY    0  /* dummy service, used to detect cyclic dependencies */
37 #define SVC_PROCESS  1  /* service runs as a process, and can be stopped
38                            by sending the process a signal */
39 #define SVC_SCRIPTED 2  /* service requires a command to start, and another
40                            command to stop */
41
42
43 // Exception loading service
44 class ServiceLoadExc
45 {
46     public:
47     std::string serviceName;
48     
49     ServiceLoadExc(std::string serviceName)
50         : serviceName(serviceName)
51     {
52     }
53 };
54
55 class ServiceNotFound : public ServiceLoadExc
56 {
57     public:
58     ServiceNotFound(std::string serviceName)
59         : ServiceLoadExc(serviceName)
60     {
61     }
62 };
63
64 class ServiceCyclicDependency : public ServiceLoadExc
65 {
66     public:
67     ServiceCyclicDependency(std::string serviceName)
68         : ServiceLoadExc(serviceName)
69     {
70     }
71 };
72
73 class ServiceDescriptionExc : public ServiceLoadExc
74 {
75     public:
76     std::string extraInfo;
77     
78     ServiceDescriptionExc(std::string serviceName, std::string extraInfo)
79         : ServiceLoadExc(serviceName), extraInfo(extraInfo)
80     {
81     }    
82 };
83
84 class ServiceRecord; // forward declaration
85 class ServiceSet; // forward declaration
86
87 /* Service dependency record */
88 class ServiceDep
89 {
90     ServiceRecord * from;
91     ServiceRecord * to;
92     
93     public:
94     /* Whether the 'from' service is waiting for the 'to' service to start */
95     bool waiting_on;
96     
97     ServiceDep(ServiceRecord * from, ServiceRecord * to) : from(from), to(to), waiting_on(false)
98     {  }
99     
100     ServiceRecord * getFrom()
101     {
102         return from;
103     }
104     
105     ServiceRecord * getTo()
106     {
107         return to;
108     }
109 };
110
111
112
113 class ServiceRecord
114 {
115     typedef std::string string;
116     
117     string service_name;
118     int service_type;  /* SVC_DAEMON or SVC_SCRIPTED */
119     int service_state; /* SVC_STOPPED, _STARTING, _STARTED, _STOPPING */
120     int desired_state; /* SVC_STOPPED / SVC_STARTED */
121     bool force_stop; // true if the service must actually stop. This is the
122                      // case if for example the process dies; the service,
123                      // and all its dependencies, MUST be stopped.
124     string program_name;  /* executable program or script */
125     string logfile; /* log file name, empty string specifies /dev/null */
126     bool auto_restart; /* whether to restart this (process) if it dies unexpectedly */
127     
128     typedef std::list<ServiceRecord *> sr_list;
129     typedef sr_list::iterator sr_iter;
130     
131     // list of soft dependencies
132     typedef std::list<ServiceDep> softdep_list;
133     
134     // list of soft dependents
135     typedef std::list<ServiceDep *> softdpt_list;
136     
137     sr_list depends_on; // services this one depends on
138     sr_list dependents; // services depending on this one
139     softdep_list soft_deps;  // services this one depends on via a soft dependency
140     softdpt_list soft_dpts;  // services depending on this one via a soft dependency
141     
142     // unsigned wait_count;  /* if we are waiting for dependents/dependencies to
143     //                         start/stop, this is how many we're waiting for */
144     
145     ServiceSet *service_set; // the set this service belongs to
146     
147     // Implementation details
148     
149     pid_t pid;  /* PID of the process. If state is STARTING or STOPPING,
150                    this is PID of the service script; otherwise it is the
151                    PID of the process itself (process service).
152                    */
153
154     ev_child child_listener;
155     
156     // Move service to STOPPING state. This can only be called once
157     // all dependents have stopped.
158     void stopping();
159     
160     // Service has actually stopped (includes having all dependents
161     // reaching STOPPED state).
162     void stopped();
163     
164     // Service has successfully started
165     void started();
166     
167     // Service failed to start
168     void failed_to_start();
169     
170     // A dependency of this service failed to start.
171     void failed_dependency();
172     
173     // For process services, start the process, return true on success
174     bool start_ps_process();
175     bool start_ps_process(const std::vector<std::string> &args);
176    
177     // Callback from libev when a child process dies
178     static void process_child_callback(struct ev_loop *loop, struct ev_child *w,
179             int revents);
180     
181     // A dependent has reached STOPPED state
182     void dependentStopped();
183
184     // check if all dependents have stopped
185     bool stopCheckDependents();
186     
187     // issue a stop to all dependents, return true if they are all already stopped
188     bool stopDependents();
189     
190     void forceStop(); // force-stop this service and all dependents
191     
192     public:
193
194     ServiceRecord(ServiceSet *set, string name)
195         : service_state(SVC_STOPPED), desired_state(SVC_STOPPED), force_stop(false), auto_restart(false)
196     {
197         service_set = set;
198         service_name = name;
199         service_type = SVC_DUMMY;
200     }
201     
202     ServiceRecord(ServiceSet *set, string name, int service_type, string command,
203             sr_list * pdepends_on, sr_list * pdepends_soft)
204         : service_state(SVC_STOPPED), desired_state(SVC_STOPPED), force_stop(false), auto_restart(false)
205     {
206         service_set = set;
207         service_name = name;
208         this->service_type = service_type;
209         program_name = command;
210         // TODO splice the contents from the depends_on parameter
211         // rather than duplicating the list.
212         this->depends_on = *pdepends_on;
213         //this->depends_on.insert(this->depends_on.end(), pdepends_soft->begin(), pdepends_soft->end());
214
215         for (sr_iter i = pdepends_on->begin(); i != pdepends_on->end(); ++i) {
216             (*i)->dependents.push_back(this);
217         }
218         
219         // Soft dependencies
220         auto b_iter = soft_deps.end();
221         for (sr_iter i = pdepends_soft->begin(); i != pdepends_soft->end(); ++i) {
222             b_iter = soft_deps.emplace(b_iter, this, *i);
223             (*i)->soft_dpts.push_back(&(*b_iter));
224             ++b_iter;
225         }
226
227         /*
228         // For each dependency, add us as a dependent.
229         for (sr_iter i = pdepends_on->begin(); i != pdepends_on->end(); ++i) {
230             (*i)->dependents.push_back(this);
231         }
232         for (sr_iter i = pdepends_soft->begin(); i != pdepends_soft->end(); ++i) {
233             (*i)->soft_dependents.push_back(this);
234         }
235         */
236     }
237     
238     // Set logfile, should be done before service is started
239     void setLogfile(string logfile)
240     {
241         this->logfile = logfile;
242     }
243     
244     // Set whether this service should automatically restart when it dies
245     void setAutoRestart(bool auto_restart)
246     {
247         this->auto_restart = auto_restart;
248     }
249     
250     const char *getServiceName() const { return service_name.c_str(); }
251     int getState() const { return service_state; }
252     
253     void start();  // start the service
254     void stop();   // stop the service
255     
256     bool isDummy()
257     {
258         return service_type == SVC_DUMMY;
259     }
260 };
261
262
263 class ServiceSet
264 {
265     int active_services;
266     std::list<ServiceRecord *> records;
267     const char *service_dir;  // directory containing service descriptions
268     bool restart_enabled; // whether automatic restart is enabled (allowed)
269     
270     // Private methods
271     
272     // Locate an existing service record.
273     ServiceRecord *findService(std::string name);
274     
275     // Load a service description, and dependencies, if there is no existing
276     // record for the given name.
277     ServiceRecord *loadServiceRecord(const char *name);
278
279     // Public
280     
281     public:
282     ServiceSet(const char *service_dir)
283     {
284         this->service_dir = service_dir;
285         active_services = 0;
286         restart_enabled = true;
287     }
288     
289     // Start the service with the given name. The named service will begin
290     // transition to the 'started' state.
291     //
292     // Throws an exception if the
293     // service description cannot be loaded.
294     void startService(const char *name);
295     
296     // Stop the service with the given name. The named service will begin
297     // transition to the 'stopped' state.
298     void stopService(const std::string &name);
299     
300     // Notification from service that it is active (state != SVC_STOPPED)
301     // Only to be called on the transition from inactive to active.
302     void service_active(ServiceRecord *);
303     
304     // Notification from service that it is inactive (SVC_STOPPED)
305     // Only to be called on the transition from active to inactive.
306     void service_inactive(ServiceRecord *);
307     
308     // Find out how many services are active (starting, running or stopping,
309     // but not stopped).
310     int count_active_services()
311     {
312         return active_services;
313     }
314     
315     void stop_all_services()
316     {
317         restart_enabled = false;
318         for (std::list<ServiceRecord *>::iterator i = records.begin(); i != records.end(); ++i) {
319             (*i)->stop();
320         }
321     }
322     
323     void set_auto_restart(bool restart)
324     {
325         restart_enabled = restart;
326     }
327     
328     bool get_auto_restart()
329     {
330         return restart_enabled;
331     }
332 };