Properly intiiatiise force_stop
[oweals/dinit.git] / dinit.cc
1 #include <iostream>
2 #include <cstring>
3 #include <csignal>
4 #include <list>
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <sys/un.h>
8 #include <sys/socket.h>
9 #include <unistd.h>
10 #include <fcntl.h>
11 #include "service.h"
12 #include "ev++.h"
13 #include "control.h"
14
15
16 /* TODO: prevent services from respawning too quickly */
17 /* TODO: detect/guard against dependency cycles */
18 /* TODO: optional automatic restart of services */
19
20 /*
21  * "simpleinit" from util-linux package handles signals as follows:
22  * SIGTSTP - spawn no more gettys (in preparation for shutdown etc).
23  *          In dinit terms this should probably mean "no more auto restarts"
24  *          (for any service). (Actually the signal acts as a toggle, if
25  *          respawn is disabled it will be re-enabled and init will
26  *          act as if SIGHUP had also been sent)
27  * SIGTERM - kill spawned gettys (which are still alive)
28  *          Interestingly, simpleinit just sends a SIGTERM to the gettys.
29  *          "shutdown" however has already sent SIGTERM to every process...
30  * "/sbin/initctl -r" - rollback services (ran by "shutdown"/halt etc)
31  *           shouldn't return until all services have been stopped.
32  *           shutdown calls this *after* sending SIGTERM to all processes.
33  *           I guess this allows user processes, if any are still around,
34  *           to die before (or just as) the services fall out from underneath
35  *           them. On the other hand it largely subverts the ordered service
36  *           shutdown that init provides.
37  * SIGQUIT - init will exec() shutdown. shutdown will detect that it is
38  *           running as pid 1 and will just loop and reap child processes.
39  *           This is used by shutdown so that init will not hang on to its
40  *           inode, allowing clean filesystem unmounting.
41  *
42  * Not sent by shutdown:
43  * SIGHUP -  re-read inittab and spawn any new getty entries
44  * SIGINT - (ctrl+alt+del handler) - fork & exec "reboot"
45  * 
46  * On the contrary dinit currently uses:
47  * SIGTERM - roll back services and then exec /sbin/halt
48  * SIGINT - roll back services and then exec /sbin/reboot
49  *
50  * It's an open question about whether dinit should roll back services *before*
51  * running halt/reboot, since those commands should prompt rollback of services
52  * anyway. But it seems safe to do so.
53  */
54
55
56 static bool got_sigterm = false;
57
58 static ServiceSet *service_set;
59
60 static bool am_system_init = false; // true if we are the system init process
61 static bool reboot = false; // whether to reboot (instead of halting)
62
63 static void sigint_reboot_cb(struct ev_loop *loop, ev_signal *w, int revents);
64 static void sigquit_cb(struct ev_loop *loop, ev_signal *w, int revents);
65 static void sigterm_cb(struct ev_loop *loop, ev_signal *w, int revents);
66
67 static void open_control_socket(struct ev_loop *loop);
68
69 struct ev_io control_socket_io;
70
71
72 int main(int argc, char **argv)
73 {
74     using namespace std;
75     
76     am_system_init = (getpid() == 1);
77     
78     if (am_system_init) {
79         // setup STDIN, STDOUT, STDERR so that we can use them
80         int onefd = open("/dev/console", O_RDONLY, 0);
81         dup2(onefd, 0);
82         int twofd = open("/dev/console", O_RDWR, 0);
83         dup2(twofd, 1);
84         dup2(twofd, 2);
85     }
86     
87     /* Set up signal handlers etc */
88     /* SIG_CHILD is ignored by default: good */
89     /* sigemptyset(&sigwait_set); */
90     /* sigaddset(&sigwait_set, SIGCHLD); */
91     /* sigaddset(&sigwait_set, SIGINT); */
92     /* sigaddset(&sigwait_set, SIGTERM); */
93     /* sigprocmask(SIG_BLOCK, &sigwait_set, NULL); */
94     
95     /* list of services to start */
96     list<const char *> services_to_start;
97     
98     /* service directory name */
99     const char * service_dir = "/etc/dinit.d";
100     
101     /* arguments, if given, specify a list of services to start. */
102     /* if none are given the "boot" service is started. */
103     if (argc > 1) {
104       for (int i = 1; i < argc; i++) {
105         if (argv[i][0] == '-') {
106             // An option...
107             if (strcmp(argv[i], "--services-dir") == 0 ||
108                     strcmp(argv[i], "-d") == 0) {
109                 ++i;
110                 if (i < argc) {
111                     service_dir = argv[i];
112                 }
113                 else {
114                     // error TODO
115                 }
116             }
117             else if (strcmp(argv[i], "--help") == 0) {
118                 cout << "dinit, an init with dependency management" << endl;
119                 cout << " --help                         : display help" << endl;
120                 cout << " --services-dir <dir>, -d <dir> : set base directory for service description files (-d <dir>)" << endl;
121                 cout << " <service-name>                 : start service with name <service-name>" << endl;
122                 return 0;
123             }
124             else {
125                 // unrecognized
126                 if (! am_system_init) {
127                     cerr << "Unrecognized option: " << argv[i] << endl;
128                     return 1;
129                 }
130             }
131         }
132         else {
133             services_to_start.push_back(argv[i]);
134         }
135       }
136     }
137     
138     if (services_to_start.empty()) {
139         services_to_start.push_back("boot");
140     }
141
142     // Set up signal handlers
143     ev_signal sigint_ev_signal;
144     if (am_system_init) {
145       ev_signal_init(&sigint_ev_signal, sigint_reboot_cb, SIGINT);
146     }
147     else {
148       ev_signal_init(&sigint_ev_signal, sigterm_cb, SIGINT);
149     }
150     
151     ev_signal sigquit_ev_signal;
152     if (am_system_init) {
153         // PID 1: SIGQUIT exec's shutdown
154         ev_signal_init(&sigquit_ev_signal, sigquit_cb, SIGQUIT);
155     }
156     else {
157         // Otherwise: SIGQUIT terminates dinit
158         ev_signal_init(&sigquit_ev_signal, sigterm_cb, SIGQUIT);
159     }
160     
161     ev_signal sigterm_ev_signal;
162     ev_signal_init(&sigterm_ev_signal, sigterm_cb, SIGTERM);
163     
164     /* Set up libev */
165     struct ev_loop *loop = ev_default_loop(EVFLAG_AUTO /* | EVFLAG_SIGNALFD */);
166     ev_signal_start(loop, &sigint_ev_signal);
167     ev_signal_start(loop, &sigquit_ev_signal);
168     ev_signal_start(loop, &sigterm_ev_signal);
169
170     // Try to open control socket (may fail due to readonly filesystem)
171     open_control_socket(loop);
172     
173     /* start requested services */
174     service_set = new ServiceSet(service_dir);
175     for (list<const char *>::iterator i = services_to_start.begin();
176             i != services_to_start.end();
177             ++i) {
178         try {
179             service_set->startService(*i);
180         }
181         catch (ServiceNotFound &snf) {
182             // TODO log this better
183             cerr << "Could not find service description: " << snf.serviceName << endl;
184         }
185         catch (ServiceLoadExc &sle) {
186             // TODO log this better
187             cerr << "Problem loading service description: " << sle.serviceName << endl;
188         }
189     }
190     
191     event_loop:
192     
193     // Process events until all services have terminated.
194     while (! service_set->count_active_services() == 0) {
195         ev_loop(loop, EVLOOP_ONESHOT);
196     }
197     
198     if (am_system_init) {
199         // TODO log this output properly
200         cout << "dinit: No more active services.";
201         if (reboot) {
202             cout << " Will reboot.";
203         }
204         else if (got_sigterm) {
205             cout << " Will halt.";
206         }
207         else {
208             cout << " Re-initiating boot sequence.";
209         }
210         cout << endl;
211     }
212     
213     
214     if (am_system_init) {
215         if (reboot) {
216             // TODO log error from fork
217             if (fork() == 0) {
218                 execl("/sbin/reboot", "/sbin/reboot", (char *) 0);
219             }
220         }
221         else if (got_sigterm) {
222             // TODO log error from fork
223             if (fork() == 0) {
224                 execl("/sbin/halt", "/sbin/halt", (char *) 0);
225             }
226         }
227         else {
228             // Hmmmmmm.
229             // It could be that we started in single user mode, and the
230             // user has now exited the shell. We'll try and re-start the
231             // boot process...
232             try {
233                 service_set->startService("boot");
234                 goto event_loop; // yes, the "evil" goto
235             }
236             catch (...) {
237                 // TODO catch exceptions and log message as appropriate
238                 // Now WTF do we do? try and reboot
239                 if (fork() == 0) {
240                     execl("/sbin/reboot", "/sbin/reboot", (char *) 0);
241                 }
242             }
243         }
244         
245         // PID 1 should never exit:
246         while (true) {
247             pause();
248         }
249     }
250     
251     return 0;
252 }
253
254 // Callback for control socket
255 static void control_socket_cb(struct ev_loop *loop, ev_io *w, int revents)
256 {
257     // Accept a connection
258     int sockfd = w->fd;
259     
260     int newfd = accept4(sockfd, nullptr, nullptr, SOCK_NONBLOCK | SOCK_CLOEXEC);
261     
262     if (newfd != -1) {    
263         new ControlConn(loop, service_set, newfd);  // will delete itself when it's finished
264         // TODO keep a set of control connections so that we can close them when
265         // terminating?
266     }
267 }
268
269 static void open_control_socket(struct ev_loop *loop)
270 {
271     // TODO make this use a per-user address if PID != 1, and make the address
272     // overridable from the command line
273     
274     const char * saddrname = "/dev/dinitctl";
275     struct sockaddr_un name;
276
277     unlink(saddrname);
278
279     name.sun_family = AF_UNIX;
280     strcpy(name.sun_path, saddrname); // TODO make this safe for long names
281     int namelen = 2 + strlen(saddrname);
282     //int namelen = sizeof(name);
283     
284     int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
285     if (sockfd == -1) {
286         // TODO log error
287         perror("socket");
288         return;
289     }
290     
291     if (bind(sockfd, (struct sockaddr *) &name, namelen) == -1) {
292         // TODO log error
293         perror("bind");
294         close(sockfd);
295         return;
296     }
297     
298     if (listen(sockfd, 10) == -1) {
299         // TODO log error
300         perror("listen");
301         close(sockfd);
302         return;
303     }
304     
305     ev_io_init(&control_socket_io, control_socket_cb, sockfd, EV_READ);
306     ev_io_start(loop, &control_socket_io);
307 }
308
309 /* handle SIGINT signal (generated by kernel when ctrl+alt+del pressed) */
310 static void sigint_reboot_cb(struct ev_loop *loop, ev_signal *w, int revents)
311 {
312     reboot = true;
313     service_set->stop_all_services();
314 }
315
316 /* handle SIGQUIT (if we are system init) */
317 static void sigquit_cb(struct ev_loop *loop, ev_signal *w, int revents)
318 {
319     // This allows remounting the filesystem read-only if the dinit binary has been
320     // unlinked. In that case the kernel holds the binary open, so that it can't be
321     // properly removed.
322     execl("/sbin/shutdown", "/sbin/shutdown", (char *) 0);
323 }
324
325 /* handle SIGTERM - stop all services */
326 static void sigterm_cb(struct ev_loop *loop, ev_signal *w, int revents)
327 {
328     got_sigterm = true;
329     service_set->stop_all_services();
330 }