Improve error message logged when failing to load a service (give
[oweals/dinit.git] / service.h
1 #include <string>
2 #include <list>
3 #include <vector>
4 #include <csignal>
5 #include "ev.h"
6 #include "control.h"
7
8 /*
9  * Possible service states
10  *
11  * Services have both a current state and a desired state. The desired state can be
12  * either STARTED or STOPPED. The current state can also be STARTING or STOPPING.
13  *
14  * The total state is a combination of the two, current and desired:
15  *      STOPPED/STOPPED  : stopped and will remain stopped
16  *      STOPPED/STARTED  :  - (this state cannot occur)
17  *      STARTING/STARTED : starting, but not yet started. Dependencies may also be starting.
18  *      STARTING/STOPPED : as above, but the service will be stopped again as soon as it has
19  *                         completed startup.
20  *      STARTED/STARTED  : running and will continue running.
21  *      STARTED/STOPPED  :  - (this state cannot occur)
22  *      STOPPING/STOPPED : stopping and will stop. Dependents may be stopping.
23  *      STOPPING/STARTED : as above, but the service will be re-started again once it stops.
24  *
25  * A scripted service is in the STARTING/STOPPING states during the script execution.
26  * A process service is in the STOPPING state when it has been signalled to stop, and is
27  *       in the STARTING state when waiting for dependencies to start.
28  */
29 enum class ServiceState {
30     STOPPED,    // service is not running.
31     STARTING,   // service is starting, and will start (or fail to start) in time.
32     STARTED,    // service is running,
33     STOPPING    // service script is stopping and will stop.
34 };
35
36
37
38 /* Service types */
39 enum class ServiceType {
40     DUMMY,      // dummy service, used to detect cyclice dependencies
41     PROCESS,    // service runs as a process, and can be stopped by
42                 // sending the process a signal (SIGTERM)
43     SCRIPTED,   // service requires an external command to start,
44                 // and a second command to stop
45     INTERNAL    // internal service, runs no external process
46 };
47
48
49 struct OnstartFlags {
50     bool release_console : 1;
51     bool rw_ready : 1;
52     
53     // Not actually "onstart" commands:
54     bool no_sigterm : 1;  // do not send SIGTERM
55     
56     OnstartFlags() noexcept : release_console(false), rw_ready(false), no_sigterm(false)
57     {
58     }
59 };
60
61 // Exception while loading a service
62 class ServiceLoadExc
63 {
64     public:
65     std::string serviceName;
66     const char *excDescription;
67     
68     protected:
69     ServiceLoadExc(std::string serviceName) noexcept
70         : serviceName(serviceName)
71     {
72     }
73 };
74
75 class ServiceNotFound : public ServiceLoadExc
76 {
77     public:
78     ServiceNotFound(std::string serviceName) noexcept
79         : ServiceLoadExc(serviceName)
80     {
81         excDescription = "Service description not found.";
82     }
83 };
84
85 class ServiceCyclicDependency : public ServiceLoadExc
86 {
87     public:
88     ServiceCyclicDependency(std::string serviceName) noexcept
89         : ServiceLoadExc(serviceName)
90     {
91         excDescription = "Has cyclic dependency.";
92     }
93 };
94
95 class ServiceDescriptionExc : public ServiceLoadExc
96 {
97     public:
98     std::string extraInfo;
99     
100     ServiceDescriptionExc(std::string serviceName, std::string extraInfo) noexcept
101         : ServiceLoadExc(serviceName), extraInfo(extraInfo)
102     {
103         excDescription = extraInfo.c_str();
104     }    
105 };
106
107 class ServiceRecord; // forward declaration
108 class ServiceSet; // forward declaration
109
110 /* Service dependency record */
111 class ServiceDep
112 {
113     ServiceRecord * from;
114     ServiceRecord * to;
115
116     public:
117     /* Whether the 'from' service is waiting for the 'to' service to start */
118     bool waiting_on;
119
120     ServiceDep(ServiceRecord * from, ServiceRecord * to) noexcept : from(from), to(to), waiting_on(false)
121     {  }
122
123     ServiceRecord * getFrom() noexcept
124     {
125         return from;
126     }
127
128     ServiceRecord * getTo() noexcept
129     {
130         return to;
131     }
132 };
133
134 // Given a string and a list of pairs of (start,end) indices for each argument in that string,
135 // store a null terminator for the argument. Return a `char *` array pointing at the beginning
136 // of each argument. (The returned array is invalidated if the string is later modified).
137 static const char ** separate_args(std::string &s, std::list<std::pair<unsigned,unsigned>> &arg_indices)
138 {
139     const char ** r = new const char *[arg_indices.size()];
140     int i = 0;
141
142     // First store nul terminator for each part:
143     for (auto index_pair : arg_indices) {
144         if (index_pair.second < s.length()) {
145             s[index_pair.second] = 0;
146         }
147     }
148
149     // Now we can get the C string (c_str) and store offsets into it:
150     const char * cstr = s.c_str();
151     for (auto index_pair : arg_indices) {
152         r[i] = cstr + index_pair.first;
153         i++;
154     }
155     return r;
156 }
157
158 class ServiceRecord
159 {
160     typedef std::string string;
161     
162     string service_name;
163     ServiceType service_type;  /* ServiceType::DUMMY, PROCESS, SCRIPTED, INTERNAL */
164     ServiceState service_state = ServiceState::STOPPED; /* ServiceState::STOPPED, STARTING, STARTED, STOPPING */
165     ServiceState desired_state = ServiceState::STOPPED; /* ServiceState::STOPPED / STARTED */
166
167     string program_name;          /* storage for program/script and arguments */
168     const char **exec_arg_parts;  /* pointer to each argument/part of the program_name */
169     int num_args;                 /* number of argumrnets (including program) */
170     OnstartFlags onstart_flags;
171
172     string logfile; /* log file name, empty string specifies /dev/null */
173     bool auto_restart; /* whether to restart this (process) if it dies unexpectedly */
174
175
176     typedef std::list<ServiceRecord *> sr_list;
177     typedef sr_list::iterator sr_iter;
178     
179     // list of soft dependencies
180     typedef std::list<ServiceDep> softdep_list;
181     
182     // list of soft dependents
183     typedef std::list<ServiceDep *> softdpt_list;
184     
185     sr_list depends_on; // services this one depends on
186     sr_list dependents; // services depending on this one
187     softdep_list soft_deps;  // services this one depends on via a soft dependency
188     softdpt_list soft_dpts;  // services depending on this one via a soft dependency
189     
190     // unsigned wait_count;  /* if we are waiting for dependents/dependencies to
191     //                         start/stop, this is how many we're waiting for */
192     
193     ServiceSet *service_set; // the set this service belongs to
194     
195     // Process services:
196     bool force_stop; // true if the service must actually stop. This is the
197                      // case if for example the process dies; the service,
198                      // and all its dependencies, MUST be stopped.
199
200     int term_signal = -1;  // signal to use for process termination
201
202     // Implementation details
203     
204     pid_t pid;  /* PID of the process. If state is STARTING or STOPPING,
205                    this is PID of the service script; otherwise it is the
206                    PID of the process itself (process service).
207                    */
208
209     ev_child child_listener;
210     
211     // All dependents have stopped.
212     void allDepsStopped();
213     
214     // Service has actually stopped (includes having all dependents
215     // reaching STOPPED state).
216     void stopped() noexcept;
217     
218     // Service has successfully started
219     void started();
220     
221     // Service failed to start
222     void failed_to_start();
223     
224     // A dependency of this service failed to start.
225     void failed_dependency();
226     
227     // For process services, start the process, return true on success
228     bool start_ps_process() noexcept;
229     bool start_ps_process(const std::vector<std::string> &args) noexcept;
230
231     // Callback from libev when a child process dies
232     static void process_child_callback(struct ev_loop *loop, struct ev_child *w,
233             int revents) noexcept;
234
235     // A dependency has reached STARTED state
236     void dependencyStarted() noexcept;
237     
238     void allDepsStarted() noexcept;
239     
240     // Check whether dependencies have started, and optionally ask them to start
241     bool startCheckDependencies(bool do_start) noexcept;
242
243     // A dependent has reached STOPPED state
244     void dependentStopped() noexcept;
245
246     // check if all dependents have stopped
247     bool stopCheckDependents() noexcept;
248     
249     // issue a stop to all dependents, return true if they are all already stopped
250     bool stopDependents() noexcept;
251     
252     void forceStop() noexcept; // force-stop this service and all dependents
253     
254     public:
255
256     ServiceRecord(ServiceSet *set, string name)
257         : service_state(ServiceState::STOPPED), desired_state(ServiceState::STOPPED), auto_restart(false), force_stop(false)
258     {
259         service_set = set;
260         service_name = name;
261         service_type = ServiceType::DUMMY;
262     }
263     
264     ServiceRecord(ServiceSet *set, string name, ServiceType service_type, string &&command, std::list<std::pair<unsigned,unsigned>> &command_offsets,
265             sr_list * pdepends_on, sr_list * pdepends_soft)
266         : service_state(ServiceState::STOPPED), desired_state(ServiceState::STOPPED), auto_restart(false), force_stop(false)
267     {
268         service_set = set;
269         service_name = name;
270         this->service_type = service_type;
271         this->depends_on = std::move(*pdepends_on);
272
273         program_name = command;
274         exec_arg_parts = separate_args(program_name, command_offsets);
275         num_args = command_offsets.size();
276
277         for (sr_iter i = depends_on.begin(); i != depends_on.end(); ++i) {
278             (*i)->dependents.push_back(this);
279         }
280
281         // Soft dependencies
282         auto b_iter = soft_deps.end();
283         for (sr_iter i = pdepends_soft->begin(); i != pdepends_soft->end(); ++i) {
284             b_iter = soft_deps.emplace(b_iter, this, *i);
285             (*i)->soft_dpts.push_back(&(*b_iter));
286             ++b_iter;
287         }
288     }
289     
290     // TODO write a destructor
291
292     // Set logfile, should be done before service is started
293     void setLogfile(string logfile)
294     {
295         this->logfile = logfile;
296     }
297     
298     // Set whether this service should automatically restart when it dies
299     void setAutoRestart(bool auto_restart) noexcept
300     {
301         this->auto_restart = auto_restart;
302     }
303     
304     // Set "on start" flags (commands)
305     void setOnstartFlags(OnstartFlags flags) noexcept
306     {
307         this->onstart_flags = flags;
308     }
309     
310     // Set an additional signal (other than SIGTERM) to be used to terminate the process
311     void setExtraTerminationSignal(int signo) noexcept
312     {
313         this->term_signal = signo;
314     }
315
316     const char *getServiceName() const noexcept { return service_name.c_str(); }
317     ServiceState getState() const noexcept { return service_state; }
318     
319     void start() noexcept;  // start the service
320     void stop() noexcept;   // stop the service
321     
322     bool isDummy() noexcept
323     {
324         return service_type == ServiceType::DUMMY;
325     }
326 };
327
328
329 class ServiceSet
330 {
331     int active_services;
332     std::list<ServiceRecord *> records;
333     const char *service_dir;  // directory containing service descriptions
334     bool restart_enabled; // whether automatic restart is enabled (allowed)
335     ControlConn *rollback_handler; // recieves notification when all services stopped
336     
337     // Private methods
338     
339     // Locate an existing service record.
340     ServiceRecord *findService(std::string name) noexcept;
341     
342     // Load a service description, and dependencies, if there is no existing
343     // record for the given name.
344     // Throws:
345     //   ServiceLoadException (or subclass) on problem with service description
346     //   std::bad_alloc on out-of-memory condition
347     ServiceRecord *loadServiceRecord(const char *name);
348
349     // Public
350     
351     public:
352     ServiceSet(const char *service_dir)
353     {
354         this->service_dir = service_dir;
355         active_services = 0;
356         restart_enabled = true;
357     }
358     
359     // Start the service with the given name. The named service will begin
360     // transition to the 'started' state.
361     //
362     // Throws a ServiceLoadException (or subclass) if the service description
363     // cannot be loaded or is invalid;
364     // Throws std::bad_alloc if out of memory.
365     void startService(const char *name);
366     
367     // Stop the service with the given name. The named service will begin
368     // transition to the 'stopped' state.
369     void stopService(const std::string &name) noexcept;
370     
371     // Notification from service that it is active (state != STOPPED)
372     // Only to be called on the transition from inactive to active.
373     void service_active(ServiceRecord *) noexcept;
374     
375     // Notification from service that it is inactive (STOPPED)
376     // Only to be called on the transition from active to inactive.
377     void service_inactive(ServiceRecord *) noexcept;
378     
379     // Find out how many services are active (starting, running or stopping,
380     // but not stopped).
381     int count_active_services() noexcept
382     {
383         return active_services;
384     }
385     
386     void stop_all_services() noexcept
387     {
388         restart_enabled = false;
389         for (std::list<ServiceRecord *>::iterator i = records.begin(); i != records.end(); ++i) {
390             (*i)->stop();
391         }
392     }
393     
394     void set_auto_restart(bool restart) noexcept
395     {
396         restart_enabled = restart;
397     }
398     
399     bool get_auto_restart() noexcept
400     {
401         return restart_enabled;
402     }
403     
404     // Set the rollback handler, which will be notified when all services have stopped.
405     // There can be only one rollback handler; attempts to set it when already set will
406     // fail. Returns true if successful.
407     bool setRollbackHandler(ControlConn *conn) noexcept
408     {
409         if (rollback_handler == nullptr) {
410             rollback_handler = conn;
411             return true;
412         }
413         else {
414             return false;
415         }
416     }
417     
418     void clearRollbackHandler(ControlConn *conn) noexcept
419     {
420         if (rollback_handler == conn) {
421             rollback_handler = nullptr;
422         }
423     }
424 };