runsvd: shrink by Vladimir
[oweals/busybox.git] / runit / runsvdir.c
1 /*
2 Copyright (c) 2001-2006, Gerrit Pape
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8    1. Redistributions of source code must retain the above copyright notice,
9       this list of conditions and the following disclaimer.
10    2. Redistributions in binary form must reproduce the above copyright
11       notice, this list of conditions and the following disclaimer in the
12       documentation and/or other materials provided with the distribution.
13    3. The name of the author may not be used to endorse or promote products
14       derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
29 /* TODO: depends on runit_lib.c - review and reduce/eliminate */
30
31 #include <sys/poll.h>
32 #include <sys/file.h>
33 #include "libbb.h"
34 #include "runit_lib.h"
35
36 #define MAXSERVICES 1000
37
38 struct service {
39         dev_t dev;
40         ino_t ino;
41         pid_t pid;
42         smallint isgone;
43 };
44
45 struct globals {
46         struct service *sv;
47         char *svdir;
48         char *rplog;
49         int svnum;
50         int rploglen;
51         struct fd_pair logpipe;
52         struct pollfd pfd[1];
53         unsigned stamplog;
54         smallint check; /* = 1; */
55         smallint set_pgrp;
56 };
57 #define G (*(struct globals*)&bb_common_bufsiz1)
58 #define sv        (G.sv        )
59 #define svdir     (G.svdir     )
60 #define rplog     (G.rplog     )
61 #define svnum     (G.svnum     )
62 #define rploglen  (G.rploglen  )
63 #define logpipe   (G.logpipe   )
64 #define pfd       (G.pfd       )
65 #define stamplog  (G.stamplog  )
66 #define check     (G.check     )
67 #define set_pgrp  (G.set_pgrp  )
68 #define INIT_G() do { \
69         check = 1; \
70 } while (0)
71
72 static void fatal2_cannot(const char *m1, const char *m2)
73 {
74         bb_perror_msg_and_die("%s: fatal: cannot %s%s", svdir, m1, m2);
75         /* was exiting 100 */
76 }
77 static void warn3x(const char *m1, const char *m2, const char *m3)
78 {
79         bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3);
80 }
81 static void warn2_cannot(const char *m1, const char *m2)
82 {
83         warn3x("cannot ", m1, m2);
84 }
85 static void warnx(const char *m1)
86 {
87         warn3x(m1, "", "");
88 }
89
90 static void runsv(int no, const char *name)
91 {
92         pid_t pid;
93         char *prog[3];
94
95         prog[0] = (char*)"runsv";
96         prog[1] = (char*)name;
97         prog[2] = NULL;
98
99         pid = vfork();
100
101         if (pid == -1) {
102                 warn2_cannot("vfork", "");
103                 return;
104         }
105         if (pid == 0) {
106                 /* child */
107                 if (set_pgrp)
108                         setsid();
109                 bb_signals(0
110                         + (1 << SIGHUP)
111                         + (1 << SIGTERM)
112                         , SIG_DFL);
113                 execvp(prog[0], prog);
114                 fatal2_cannot("start runsv ", name);
115         }
116         sv[no].pid = pid;
117 }
118
119 static void runsvdir(void)
120 {
121         DIR *dir;
122         direntry *d;
123         int i;
124         struct stat s;
125
126         dir = opendir(".");
127         if (!dir) {
128                 warn2_cannot("open directory ", svdir);
129                 return;
130         }
131         for (i = 0; i < svnum; i++)
132                 sv[i].isgone = 1;
133
134         while (1) {
135                 errno = 0;
136                 d = readdir(dir);
137                 if (!d)
138                         break;
139                 if (d->d_name[0] == '.')
140                         continue;
141                 if (stat(d->d_name, &s) == -1) {
142                         warn2_cannot("stat ", d->d_name);
143                         errno = 0;
144                         continue;
145                 }
146                 if (!S_ISDIR(s.st_mode))
147                         continue;
148                 for (i = 0; i < svnum; i++) {
149                         if ((sv[i].ino == s.st_ino) && (sv[i].dev == s.st_dev)) {
150                                 sv[i].isgone = 0;
151                                 if (!sv[i].pid)
152                                         runsv(i, d->d_name);
153                                 break;
154                         }
155                 }
156                 if (i == svnum) {
157                         /* new service */
158                         struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
159                         if (!svnew) {
160                                 warn3x("cannot start runsv ", d->d_name,
161                                                 " too many services");
162                                 continue;
163                         }
164                         sv = svnew;
165                         svnum++;
166                         memset(&sv[i], 0, sizeof(sv[i]));
167                         sv[i].ino = s.st_ino;
168                         sv[i].dev = s.st_dev;
169                         /*sv[i].pid = 0;*/
170                         /*sv[i].isgone = 0;*/
171                         runsv(i, d->d_name);
172                         check = 1;
173                 }
174         }
175         if (errno) {
176                 warn2_cannot("read directory ", svdir);
177                 closedir(dir);
178                 check = 1;
179                 return;
180         }
181         closedir(dir);
182
183         /* SIGTERM removed runsv's */
184         for (i = 0; i < svnum; i++) {
185                 if (!sv[i].isgone)
186                         continue;
187                 if (sv[i].pid)
188                         kill(sv[i].pid, SIGTERM);
189                 sv[i] = sv[--svnum];
190 /* BUG? we deleted sv[i] by copying over sv[last], but we will not check this newly-copied one! */
191                 check = 1;
192         }
193 }
194
195 static int setup_log(void)
196 {
197         rploglen = strlen(rplog);
198         if (rploglen < 7) {
199                 warnx("log must have at least seven characters");
200                 return 0;
201         }
202         if (piped_pair(logpipe)) {
203                 warnx("cannot create pipe for log");
204                 return -1;
205         }
206         close_on_exec_on(logpipe.rd);
207         close_on_exec_on(logpipe.wr);
208         ndelay_on(logpipe.rd);
209         ndelay_on(logpipe.wr);
210         if (dup2(logpipe.wr, 2) == -1) {
211                 warnx("cannot set filedescriptor for log");
212                 return -1;
213         }
214         pfd[0].fd = logpipe.rd;
215         pfd[0].events = POLLIN;
216         stamplog = monotonic_sec();
217         return 1;
218 }
219
220 int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
221 int runsvdir_main(int argc UNUSED_PARAM, char **argv)
222 {
223         struct stat s;
224         dev_t last_dev = last_dev; /* for gcc */
225         ino_t last_ino = last_ino; /* for gcc */
226         time_t last_mtime = 0;
227         int wstat;
228         int curdir;
229         int pid;
230         unsigned deadline;
231         unsigned now;
232         unsigned stampcheck;
233         char ch;
234         int i;
235
236         INIT_G();
237
238         opt_complementary = "-1";
239         set_pgrp = getopt32(argv, "P");
240         argv += optind;
241
242         bb_signals_recursive((1 << SIGTERM) | (1 << SIGHUP), record_signo);
243         svdir = *argv++;
244         if (argv && *argv) {
245                 rplog = *argv;
246                 if (setup_log() != 1) {
247                         rplog = 0;
248                         warnx("log service disabled");
249                 }
250         }
251         curdir = open_read(".");
252         if (curdir == -1)
253                 fatal2_cannot("open current directory", "");
254         close_on_exec_on(curdir);
255
256         stampcheck = monotonic_sec();
257
258         for (;;) {
259                 /* collect children */
260                 for (;;) {
261                         pid = wait_any_nohang(&wstat);
262                         if (pid <= 0)
263                                 break;
264                         for (i = 0; i < svnum; i++) {
265                                 if (pid == sv[i].pid) {
266                                         /* runsv has gone */
267                                         sv[i].pid = 0;
268                                         check = 1;
269                                         break;
270                                 }
271                         }
272                 }
273
274                 now = monotonic_sec();
275                 if ((int)(now - stampcheck) >= 0) {
276                         /* wait at least a second */
277                         stampcheck = now + 1;
278
279                         if (stat(svdir, &s) != -1) {
280                                 if (check || s.st_mtime != last_mtime
281                                  || s.st_ino != last_ino || s.st_dev != last_dev
282                                 ) {
283                                         /* svdir modified */
284                                         if (chdir(svdir) != -1) {
285                                                 last_mtime = s.st_mtime;
286                                                 last_dev = s.st_dev;
287                                                 last_ino = s.st_ino;
288                                                 check = 0;
289                                                 //if (now <= mtime)
290                                                 //      sleep(1);
291                                                 runsvdir();
292                                                 while (fchdir(curdir) == -1) {
293                                                         warn2_cannot("change directory, pausing", "");
294                                                         sleep(5);
295                                                 }
296                                         } else
297                                                 warn2_cannot("change directory to ", svdir);
298                                 }
299                         } else
300                                 warn2_cannot("stat ", svdir);
301                 }
302
303                 if (rplog) {
304                         if ((int)(now - stamplog) >= 0) {
305                                 write(logpipe.wr, ".", 1);
306                                 stamplog = now + 900;
307                         }
308                 }
309
310                 pfd[0].revents = 0;
311                 sig_block(SIGCHLD);
312                 deadline = (check ? 1 : 5);
313                 if (rplog)
314                         poll(pfd, 1, deadline*1000);
315                 else
316                         sleep(deadline);
317                 sig_unblock(SIGCHLD);
318
319                 if (pfd[0].revents & POLLIN) {
320                         while (read(logpipe.rd, &ch, 1) > 0) {
321                                 if (ch) {
322                                         for (i = 6; i < rploglen; i++)
323                                                 rplog[i-1] = rplog[i];
324                                         rplog[rploglen-1] = ch;
325                                 }
326                         }
327                 }
328
329                 switch (bb_got_signal) {
330                 case SIGHUP:
331                         for (i = 0; i < svnum; i++)
332                                 if (sv[i].pid)
333                                         kill(sv[i].pid, SIGTERM);
334                         // N.B. fall through
335                 case SIGTERM:
336                         _exit((SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS);
337                 }
338         }
339         /* not reached */
340         return 0;
341 }