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