Add some process-service tests.
[oweals/dinit.git] / src / includes / proc-service.h
1 #include "service.h"
2
3 // Given a string and a list of pairs of (start,end) indices for each argument in that string,
4 // store a null terminator for the argument. Return a `char *` vector containing the beginning
5 // of each argument and a trailing nullptr. (The returned array is invalidated if the string is later modified).
6 std::vector<const char *> separate_args(std::string &s, std::list<std::pair<unsigned,unsigned>> &arg_indices);
7
8 class base_process_service;
9
10 // A timer for process restarting. Used to ensure a minimum delay between process restarts (and
11 // also for timing service stop before the SIGKILL hammer is used).
12 class process_restart_timer : public eventloop_t::timer_impl<process_restart_timer>
13 {
14     public:
15     base_process_service * service;
16
17     process_restart_timer(base_process_service *service_p)
18         : service(service_p)
19     {
20     }
21
22     dasynq::rearm timer_expiry(eventloop_t &, int expiry_count);
23 };
24
25 class base_process_service : public service_record
26 {
27     friend class service_child_watcher;
28     friend class exec_status_pipe_watcher;
29     friend class process_restart_timer;
30     friend class base_process_service_test;
31
32     private:
33     // Re-launch process
34     void do_restart() noexcept;
35
36     protected:
37     string program_name;          // storage for program/script and arguments
38     std::vector<const char *> exec_arg_parts; // pointer to each argument/part of the program_name, and nullptr
39
40     string stop_command;          // storage for stop program/script and arguments
41     std::vector<const char *> stop_arg_parts; // pointer to each argument/part of the stop_command, and nullptr
42
43     service_child_watcher child_listener;
44     exec_status_pipe_watcher child_status_listener;
45     process_restart_timer restart_timer;
46     time_val last_start_time;
47
48     // Restart interval time and restart count are used to track the number of automatic restarts
49     // over an interval. Too many restarts over an interval will inhibit further restarts.
50     time_val restart_interval_time;  // current restart interval
51     int restart_interval_count;      // count of restarts within current interval
52
53     time_val restart_interval;       // maximum restart interval
54     int max_restart_interval_count;  // number of restarts allowed over maximum interval
55     time_val restart_delay;          // delay between restarts
56
57     // Time allowed for service stop, after which SIGKILL is sent. 0 to disable.
58     time_val stop_timeout = {10, 0}; // default of 10 seconds
59
60     // Time allowed for service start, after which SIGINT is sent (and then SIGKILL after
61     // <stop_timeout>). 0 to disable.
62     time_val start_timeout = {60, 0}; // default of 1 minute
63
64     bool waiting_restart_timer : 1;
65     bool stop_timer_armed : 1;
66     bool reserved_child_watch : 1;
67     bool tracking_child : 1;  // whether we expect to see child process status
68     bool start_is_interruptible : 1;  // whether we can interrupt start
69
70     // Launch the process with the given arguments, return true on success
71     bool start_ps_process(const std::vector<const char *> &args, bool on_console) noexcept;
72
73     // Restart the process (due to start failure or unexpected termination). Restarts will be
74     // rate-limited.
75     bool restart_ps_process() noexcept;
76
77     // Perform smooth recovery process
78     void do_smooth_recovery() noexcept;
79
80     // Start the process, return true on success
81     virtual bool bring_up() noexcept override;
82
83     virtual void bring_down() noexcept override;
84
85     // Called when the process exits. The exit_status is the status value yielded by
86     // the "wait" system call.
87     virtual void handle_exit_status(int exit_status) noexcept = 0;
88
89     // Called if an exec fails.
90     virtual void exec_failed(int errcode) noexcept = 0;
91
92     // Called if exec succeeds.
93     virtual void exec_succeeded() noexcept { };
94
95     virtual bool can_interrupt_start() noexcept override
96     {
97         return waiting_restart_timer || start_is_interruptible || service_record::can_interrupt_start();
98     }
99
100     virtual bool can_proceed_to_start() noexcept override
101     {
102         return ! waiting_restart_timer;
103     }
104
105     virtual bool interrupt_start() noexcept override;
106
107     // Kill with SIGKILL
108     void kill_with_fire() noexcept;
109
110     // Signal the process group of the service process
111     void kill_pg(int signo) noexcept;
112
113     public:
114     base_process_service(service_set *sset, string name, service_type_t record_type_p, string &&command,
115             std::list<std::pair<unsigned,unsigned>> &command_offsets,
116             const std::list<prelim_dep> &deplist_p);
117
118     ~base_process_service() noexcept
119     {
120     }
121
122     // Set the stop command and arguments (may throw std::bad_alloc)
123     void set_stop_command(std::string command, std::list<std::pair<unsigned,unsigned>> &stop_command_offsets)
124     {
125         stop_command = command;
126         stop_arg_parts = separate_args(stop_command, stop_command_offsets);
127     }
128
129     void set_restart_interval(timespec interval, int max_restarts) noexcept
130     {
131         restart_interval = interval;
132         max_restart_interval_count = max_restarts;
133     }
134
135     void set_restart_delay(timespec delay) noexcept
136     {
137         restart_delay = delay;
138     }
139
140     void set_stop_timeout(timespec timeout) noexcept
141     {
142         stop_timeout = timeout;
143     }
144
145     void set_start_timeout(timespec timeout) noexcept
146     {
147         start_timeout = timeout;
148     }
149
150     void set_start_interruptible(bool value) noexcept
151     {
152         start_is_interruptible = value;
153     }
154 };
155
156 class process_service : public base_process_service
157 {
158     virtual void handle_exit_status(int exit_status) noexcept override;
159     virtual void exec_failed(int errcode) noexcept override;
160     virtual void exec_succeeded() noexcept override;
161     virtual void bring_down() noexcept override;
162
163     public:
164     process_service(service_set *sset, string name, string &&command,
165             std::list<std::pair<unsigned,unsigned>> &command_offsets,
166             std::list<prelim_dep> depends_p)
167          : base_process_service(sset, name, service_type_t::PROCESS, std::move(command), command_offsets,
168              depends_p)
169     {
170     }
171
172     ~process_service() noexcept
173     {
174     }
175 };
176
177 class bgproc_service : public base_process_service
178 {
179     virtual void handle_exit_status(int exit_status) noexcept override;
180     virtual void exec_failed(int errcode) noexcept override;
181
182     enum class pid_result_t {
183         OK,
184         FAILED,      // failed to read pid or read invalid pid
185         TERMINATED   // read pid successfully, but the process already terminated
186     };
187
188     // Read the pid-file, return false on failure
189     pid_result_t read_pid_file(int *exit_status) noexcept;
190
191     public:
192     bgproc_service(service_set *sset, string name, string &&command,
193             std::list<std::pair<unsigned,unsigned>> &command_offsets,
194             std::list<prelim_dep> depends_p)
195          : base_process_service(sset, name, service_type_t::BGPROCESS, std::move(command), command_offsets,
196              depends_p)
197     {
198     }
199
200     ~bgproc_service() noexcept
201     {
202     }
203 };
204
205 class scripted_service : public base_process_service
206 {
207     virtual void handle_exit_status(int exit_status) noexcept override;
208     virtual void exec_failed(int errcode) noexcept override;
209     virtual void bring_down() noexcept override;
210
211     virtual bool interrupt_start() noexcept override
212     {
213         // if base::interrupt_start() returns false, then start hasn't been fully interrupted, but an
214         // interrupt has been issued:
215         interrupting_start = ! base_process_service::interrupt_start();
216         return ! interrupting_start;
217     }
218
219     bool interrupting_start : 1;  // running start script (true) or stop script (false)
220
221     public:
222     scripted_service(service_set *sset, string name, string &&command,
223             std::list<std::pair<unsigned,unsigned>> &command_offsets,
224             std::list<prelim_dep> depends_p)
225          : base_process_service(sset, name, service_type_t::SCRIPTED, std::move(command), command_offsets,
226              depends_p), interrupting_start(false)
227     {
228     }
229
230     ~scripted_service() noexcept
231     {
232     }
233 };