runsvdir: conditionalize "log in argiment" (mis)feature. By Vladimir.
[platform/upstream/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 /* Should be not needed - all dirs are on same FS, right? */
39 #define CHECK_DEVNO_TOO 0
40
41 struct service {
42 #if CHECK_DEVNO_TOO
43         dev_t dev;
44 #endif
45         ino_t ino;
46         pid_t pid;
47         smallint isgone;
48 };
49
50 struct globals {
51         struct service *sv;
52         char *svdir;
53         int svnum;
54 #if ENABLE_FEATURE_RUNSVDIR_LOG
55         char *rplog;
56         int rploglen;
57         struct fd_pair logpipe;
58         struct pollfd pfd[1];
59         unsigned stamplog;
60 #endif
61         smallint need_rescan; /* = 1; */
62         smallint set_pgrp;
63 };
64 #define G (*(struct globals*)&bb_common_bufsiz1)
65 #define sv          (G.sv          )
66 #define svdir       (G.svdir       )
67 #define svnum       (G.svnum       )
68 #define rplog       (G.rplog       )
69 #define rploglen    (G.rploglen    )
70 #define logpipe     (G.logpipe     )
71 #define pfd         (G.pfd         )
72 #define stamplog    (G.stamplog    )
73 #define need_rescan (G.need_rescan )
74 #define set_pgrp    (G.set_pgrp    )
75 #define INIT_G() do { \
76         need_rescan = 1; \
77 } while (0)
78
79 static void fatal2_cannot(const char *m1, const char *m2)
80 {
81         bb_perror_msg_and_die("%s: fatal: cannot %s%s", svdir, m1, m2);
82         /* was exiting 100 */
83 }
84 static void warn3x(const char *m1, const char *m2, const char *m3)
85 {
86         bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3);
87 }
88 static void warn2_cannot(const char *m1, const char *m2)
89 {
90         warn3x("cannot ", m1, m2);
91 }
92 #if ENABLE_FEATURE_RUNSVDIR_LOG
93 static void warnx(const char *m1)
94 {
95         warn3x(m1, "", "");
96 }
97 #endif
98
99 static void runsv(int no, const char *name)
100 {
101         pid_t pid;
102         char *prog[3];
103
104         prog[0] = (char*)"runsv";
105         prog[1] = (char*)name;
106         prog[2] = NULL;
107
108         pid = vfork();
109
110         if (pid == -1) {
111                 warn2_cannot("vfork", "");
112                 return;
113         }
114         if (pid == 0) {
115                 /* child */
116                 if (set_pgrp)
117                         setsid();
118                 bb_signals(0
119                         + (1 << SIGHUP)
120                         + (1 << SIGTERM)
121                         , SIG_DFL);
122                 execvp(prog[0], prog);
123                 fatal2_cannot("start runsv ", name);
124         }
125         sv[no].pid = pid;
126 }
127
128 static void do_rescan(void)
129 {
130         DIR *dir;
131         direntry *d;
132         int i;
133         struct stat s;
134
135         dir = opendir(".");
136         if (!dir) {
137                 warn2_cannot("open directory ", svdir);
138                 return;
139         }
140         for (i = 0; i < svnum; i++)
141                 sv[i].isgone = 1;
142
143         while (1) {
144                 errno = 0;
145                 d = readdir(dir);
146                 if (!d)
147                         break;
148                 if (d->d_name[0] == '.')
149                         continue;
150                 if (stat(d->d_name, &s) == -1) {
151                         warn2_cannot("stat ", d->d_name);
152                         continue;
153                 }
154                 if (!S_ISDIR(s.st_mode))
155                         continue;
156                 for (i = 0; i < svnum; i++) {
157                         if ((sv[i].ino == s.st_ino)
158 #if CHECK_DEVNO_TOO
159                          && (sv[i].dev == s.st_dev)
160 #endif
161                         ) {
162                                 sv[i].isgone = 0;
163                                 if (!sv[i].pid)
164                                         runsv(i, d->d_name);
165                                 break;
166                         }
167                 }
168                 if (i == svnum) {
169                         /* new service */
170                         struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
171                         if (!svnew) {
172                                 warn2_cannot("start runsv ", d->d_name);
173                                 continue;
174                         }
175                         sv = svnew;
176                         svnum++;
177                         memset(&sv[i], 0, sizeof(sv[i]));
178                         sv[i].ino = s.st_ino;
179 #if CHECK_DEVNO_TOO
180                         sv[i].dev = s.st_dev;
181 #endif
182                         /*sv[i].pid = 0;*/
183                         /*sv[i].isgone = 0;*/
184                         runsv(i, d->d_name);
185                         need_rescan = 1;
186                 }
187         }
188         i = errno;
189         closedir(dir);
190         if (i) {
191                 warn2_cannot("read directory ", svdir);
192                 need_rescan = 1;
193                 return;
194         }
195
196         /* Send SIGTERM to runsv whose directories were not found (removed) */
197         for (i = 0; i < svnum; i++) {
198                 if (!sv[i].isgone)
199                         continue;
200                 if (sv[i].pid)
201                         kill(sv[i].pid, SIGTERM);
202                 svnum--;
203                 sv[i] = sv[svnum];
204                 i--; /* so that we don't skip new sv[i] (bug was here!) */
205                 need_rescan = 1;
206         }
207 }
208
209 int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
210 int runsvdir_main(int argc UNUSED_PARAM, char **argv)
211 {
212         struct stat s;
213         dev_t last_dev = last_dev; /* for gcc */
214         ino_t last_ino = last_ino; /* for gcc */
215         time_t last_mtime = 0;
216         int wstat;
217         int curdir;
218         int pid;
219         unsigned deadline;
220         unsigned now;
221         unsigned stampcheck;
222         int i;
223
224         INIT_G();
225
226         opt_complementary = "-1";
227         set_pgrp = getopt32(argv, "P");
228         argv += optind;
229
230         bb_signals_recursive((1 << SIGTERM) | (1 << SIGHUP), record_signo);
231         svdir = *argv++;
232
233 #if ENABLE_FEATURE_RUNSVDIR_LOG
234         /* setup log */
235         if (*argv) {
236                 rplog = *argv;
237                 rploglen = strlen(rplog);
238                 if (rploglen < 7) {
239                         warnx("log must have at least seven characters");
240                 } else if (piped_pair(logpipe)) {
241                         warnx("cannot create pipe for log");
242                 } else {
243                         close_on_exec_on(logpipe.rd);
244                         close_on_exec_on(logpipe.wr);
245                         ndelay_on(logpipe.rd);
246                         ndelay_on(logpipe.wr);
247                         if (dup2(logpipe.wr, 2) == -1) {
248                                 warnx("cannot set filedescriptor for log");
249                         } else {
250                                 pfd[0].fd = logpipe.rd;
251                                 pfd[0].events = POLLIN;
252                                 stamplog = monotonic_sec();
253                                 goto run;
254                         }
255                 }
256                 rplog = NULL;
257                 warnx("log service disabled");
258         }
259 run:
260 #endif
261         curdir = open_read(".");
262         if (curdir == -1)
263                 fatal2_cannot("open current directory", "");
264         close_on_exec_on(curdir);
265
266         stampcheck = monotonic_sec();
267
268         for (;;) {
269                 /* collect children */
270                 for (;;) {
271                         pid = wait_any_nohang(&wstat);
272                         if (pid <= 0)
273                                 break;
274                         for (i = 0; i < svnum; i++) {
275                                 if (pid == sv[i].pid) {
276                                         /* runsv has gone */
277                                         sv[i].pid = 0;
278                                         need_rescan = 1;
279                                         break;
280                                 }
281                         }
282                 }
283
284                 now = monotonic_sec();
285                 if ((int)(now - stampcheck) >= 0) {
286                         /* wait at least a second */
287                         stampcheck = now + 1;
288
289                         if (stat(svdir, &s) != -1) {
290                                 if (need_rescan || s.st_mtime != last_mtime
291                                  || s.st_ino != last_ino || s.st_dev != last_dev
292                                 ) {
293                                         /* svdir modified */
294                                         if (chdir(svdir) != -1) {
295                                                 last_mtime = s.st_mtime;
296                                                 last_dev = s.st_dev;
297                                                 last_ino = s.st_ino;
298                                                 need_rescan = 0;
299                                                 //if (now <= mtime)
300                                                 //      sleep(1);
301                                                 do_rescan();
302                                                 while (fchdir(curdir) == -1) {
303                                                         warn2_cannot("change directory, pausing", "");
304                                                         sleep(5);
305                                                 }
306                                         } else
307                                                 warn2_cannot("change directory to ", svdir);
308                                 }
309                         } else
310                                 warn2_cannot("stat ", svdir);
311                 }
312
313 #if ENABLE_FEATURE_RUNSVDIR_LOG
314                 if (rplog) {
315                         if ((int)(now - stamplog) >= 0) {
316                                 write(logpipe.wr, ".", 1);
317                                 stamplog = now + 900;
318                         }
319                 }
320                 pfd[0].revents = 0;
321 #endif
322                 sig_block(SIGCHLD);
323                 deadline = (need_rescan ? 1 : 5);
324 #if ENABLE_FEATURE_RUNSVDIR_LOG
325                 if (rplog)
326                         poll(pfd, 1, deadline*1000);
327                 else
328 #endif
329                         sleep(deadline);
330                 sig_unblock(SIGCHLD);
331
332 #if ENABLE_FEATURE_RUNSVDIR_LOG
333                 if (pfd[0].revents & POLLIN) {
334                         char ch;
335                         while (read(logpipe.rd, &ch, 1) > 0) {
336                                 if (ch) {
337                                         for (i = 6; i < rploglen; i++)
338                                                 rplog[i-1] = rplog[i];
339                                         rplog[rploglen-1] = ch;
340                                 }
341                         }
342                 }
343 #endif
344                 switch (bb_got_signal) {
345                 case SIGHUP:
346                         for (i = 0; i < svnum; i++)
347                                 if (sv[i].pid)
348                                         kill(sv[i].pid, SIGTERM);
349                         // N.B. fall through
350                 case SIGTERM:
351                         _exit((SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS);
352                 }
353         }
354         /* not reached */
355         return 0;
356 }