blob: af7e75ba761fb813a386485e6522d7b6b53e7c41
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 | //usage:#define runsvdir_trivial_usage |
32 | //usage: "[-P] [-s SCRIPT] DIR" |
33 | //usage:#define runsvdir_full_usage "\n\n" |
34 | //usage: "Start a runsv process for each subdirectory. If it exits, restart it.\n" |
35 | //usage: "\n -P Put each runsv in a new session" |
36 | //usage: "\n -s SCRIPT Run SCRIPT <signo> after signal is processed" |
37 | |
38 | #include <sys/file.h> |
39 | #include "libbb.h" |
40 | #include "runit_lib.h" |
41 | |
42 | #define MAXSERVICES 1000 |
43 | |
44 | /* Should be not needed - all dirs are on same FS, right? */ |
45 | #define CHECK_DEVNO_TOO 0 |
46 | |
47 | struct service { |
48 | #if CHECK_DEVNO_TOO |
49 | dev_t dev; |
50 | #endif |
51 | ino_t ino; |
52 | pid_t pid; |
53 | smallint isgone; |
54 | }; |
55 | |
56 | struct globals { |
57 | struct service *sv; |
58 | char *svdir; |
59 | int svnum; |
60 | #if ENABLE_FEATURE_RUNSVDIR_LOG |
61 | char *rplog; |
62 | int rploglen; |
63 | struct fd_pair logpipe; |
64 | struct pollfd pfd[1]; |
65 | unsigned stamplog; |
66 | #endif |
67 | } FIX_ALIASING; |
68 | #define G (*(struct globals*)&bb_common_bufsiz1) |
69 | #define sv (G.sv ) |
70 | #define svdir (G.svdir ) |
71 | #define svnum (G.svnum ) |
72 | #define rplog (G.rplog ) |
73 | #define rploglen (G.rploglen ) |
74 | #define logpipe (G.logpipe ) |
75 | #define pfd (G.pfd ) |
76 | #define stamplog (G.stamplog ) |
77 | #define INIT_G() do { } while (0) |
78 | |
79 | static void fatal2_cannot(const char *m1, const char *m2) |
80 | { |
81 | bb_perror_msg_and_die("%s: fatal: can't %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("can't ", 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 | /* inlining + vfork -> bigger code */ |
100 | static NOINLINE pid_t runsv(const char *name) |
101 | { |
102 | pid_t pid; |
103 | |
104 | /* If we got signaled, stop spawning children at once! */ |
105 | if (bb_got_signal) |
106 | return 0; |
107 | |
108 | pid = vfork(); |
109 | if (pid == -1) { |
110 | warn2_cannot("vfork", ""); |
111 | return 0; |
112 | } |
113 | if (pid == 0) { |
114 | /* child */ |
115 | if (option_mask32 & 1) /* -P option? */ |
116 | setsid(); |
117 | /* man execv: |
118 | * "Signals set to be caught by the calling process image |
119 | * shall be set to the default action in the new process image." |
120 | * Therefore, we do not need this: */ |
121 | #if 0 |
122 | bb_signals(0 |
123 | | (1 << SIGHUP) |
124 | | (1 << SIGTERM) |
125 | , SIG_DFL); |
126 | #endif |
127 | execlp("runsv", "runsv", name, (char *) NULL); |
128 | fatal2_cannot("start runsv ", name); |
129 | } |
130 | return pid; |
131 | } |
132 | |
133 | /* gcc 4.3.0 does better with NOINLINE */ |
134 | static NOINLINE int do_rescan(void) |
135 | { |
136 | DIR *dir; |
137 | struct dirent *d; |
138 | int i; |
139 | struct stat s; |
140 | int need_rescan = 0; |
141 | |
142 | dir = opendir("."); |
143 | if (!dir) { |
144 | warn2_cannot("open directory ", svdir); |
145 | return 1; /* need to rescan again soon */ |
146 | } |
147 | for (i = 0; i < svnum; i++) |
148 | sv[i].isgone = 1; |
149 | |
150 | while (1) { |
151 | errno = 0; |
152 | d = readdir(dir); |
153 | if (!d) |
154 | break; |
155 | if (d->d_name[0] == '.') |
156 | continue; |
157 | if (stat(d->d_name, &s) == -1) { |
158 | warn2_cannot("stat ", d->d_name); |
159 | continue; |
160 | } |
161 | if (!S_ISDIR(s.st_mode)) |
162 | continue; |
163 | /* Do we have this service listed already? */ |
164 | for (i = 0; i < svnum; i++) { |
165 | if ((sv[i].ino == s.st_ino) |
166 | #if CHECK_DEVNO_TOO |
167 | && (sv[i].dev == s.st_dev) |
168 | #endif |
169 | ) { |
170 | if (sv[i].pid == 0) /* restart if it has died */ |
171 | goto run_ith_sv; |
172 | sv[i].isgone = 0; /* "we still see you" */ |
173 | goto next_dentry; |
174 | } |
175 | } |
176 | { /* Not found, make new service */ |
177 | struct service *svnew = realloc(sv, (i+1) * sizeof(*sv)); |
178 | if (!svnew) { |
179 | warn2_cannot("start runsv ", d->d_name); |
180 | need_rescan = 1; |
181 | continue; |
182 | } |
183 | sv = svnew; |
184 | svnum++; |
185 | #if CHECK_DEVNO_TOO |
186 | sv[i].dev = s.st_dev; |
187 | #endif |
188 | sv[i].ino = s.st_ino; |
189 | run_ith_sv: |
190 | sv[i].pid = runsv(d->d_name); |
191 | sv[i].isgone = 0; |
192 | } |
193 | next_dentry: ; |
194 | } |
195 | i = errno; |
196 | closedir(dir); |
197 | if (i) { /* readdir failed */ |
198 | warn2_cannot("read directory ", svdir); |
199 | return 1; /* need to rescan again soon */ |
200 | } |
201 | |
202 | /* Send SIGTERM to runsv whose directories |
203 | * were no longer found (-> must have been removed) */ |
204 | for (i = 0; i < svnum; i++) { |
205 | if (!sv[i].isgone) |
206 | continue; |
207 | if (sv[i].pid) |
208 | kill(sv[i].pid, SIGTERM); |
209 | svnum--; |
210 | sv[i] = sv[svnum]; |
211 | i--; /* so that we don't skip new sv[i] (bug was here!) */ |
212 | } |
213 | return need_rescan; |
214 | } |
215 | |
216 | int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
217 | int runsvdir_main(int argc UNUSED_PARAM, char **argv) |
218 | { |
219 | struct stat s; |
220 | dev_t last_dev = last_dev; /* for gcc */ |
221 | ino_t last_ino = last_ino; /* for gcc */ |
222 | time_t last_mtime = 0; |
223 | int wstat; |
224 | int curdir; |
225 | pid_t pid; |
226 | unsigned deadline; |
227 | unsigned now; |
228 | unsigned stampcheck; |
229 | int i; |
230 | int need_rescan = 1; |
231 | char *opt_s_argv[3]; |
232 | |
233 | INIT_G(); |
234 | |
235 | opt_complementary = "-1"; |
236 | opt_s_argv[0] = NULL; |
237 | opt_s_argv[2] = NULL; |
238 | getopt32(argv, "Ps:", &opt_s_argv[0]); |
239 | argv += optind; |
240 | |
241 | bb_signals(0 |
242 | | (1 << SIGTERM) |
243 | | (1 << SIGHUP) |
244 | /* For busybox's init, SIGTERM == reboot, |
245 | * SIGUSR1 == halt |
246 | * SIGUSR2 == poweroff |
247 | * so we need to intercept SIGUSRn too. |
248 | * Note that we do not implement actual reboot |
249 | * (killall(TERM) + umount, etc), we just pause |
250 | * respawing and avoid exiting (-> making kernel oops). |
251 | * The user is responsible for the rest. */ |
252 | | (getpid() == 1 ? ((1 << SIGUSR1) | (1 << SIGUSR2)) : 0) |
253 | , record_signo); |
254 | svdir = *argv++; |
255 | |
256 | #if ENABLE_FEATURE_RUNSVDIR_LOG |
257 | /* setup log */ |
258 | if (*argv) { |
259 | rplog = *argv; |
260 | rploglen = strlen(rplog); |
261 | if (rploglen < 7) { |
262 | warnx("log must have at least seven characters"); |
263 | } else if (piped_pair(logpipe)) { |
264 | warnx("can't create pipe for log"); |
265 | } else { |
266 | close_on_exec_on(logpipe.rd); |
267 | close_on_exec_on(logpipe.wr); |
268 | ndelay_on(logpipe.rd); |
269 | ndelay_on(logpipe.wr); |
270 | if (dup2(logpipe.wr, 2) == -1) { |
271 | warnx("can't set filedescriptor for log"); |
272 | } else { |
273 | pfd[0].fd = logpipe.rd; |
274 | pfd[0].events = POLLIN; |
275 | stamplog = monotonic_sec(); |
276 | goto run; |
277 | } |
278 | } |
279 | rplog = NULL; |
280 | warnx("log service disabled"); |
281 | } |
282 | run: |
283 | #endif |
284 | curdir = open(".", O_RDONLY|O_NDELAY); |
285 | if (curdir == -1) |
286 | fatal2_cannot("open current directory", ""); |
287 | close_on_exec_on(curdir); |
288 | |
289 | stampcheck = monotonic_sec(); |
290 | |
291 | for (;;) { |
292 | /* collect children */ |
293 | for (;;) { |
294 | pid = wait_any_nohang(&wstat); |
295 | if (pid <= 0) |
296 | break; |
297 | for (i = 0; i < svnum; i++) { |
298 | if (pid == sv[i].pid) { |
299 | /* runsv has died */ |
300 | sv[i].pid = 0; |
301 | need_rescan = 1; |
302 | } |
303 | } |
304 | } |
305 | |
306 | now = monotonic_sec(); |
307 | if ((int)(now - stampcheck) >= 0) { |
308 | /* wait at least a second */ |
309 | stampcheck = now + 1; |
310 | |
311 | if (stat(svdir, &s) != -1) { |
312 | if (need_rescan || s.st_mtime != last_mtime |
313 | || s.st_ino != last_ino || s.st_dev != last_dev |
314 | ) { |
315 | /* svdir modified */ |
316 | if (chdir(svdir) != -1) { |
317 | last_mtime = s.st_mtime; |
318 | last_dev = s.st_dev; |
319 | last_ino = s.st_ino; |
320 | /* if the svdir changed this very second, wait until the |
321 | * next second, because we won't be able to detect more |
322 | * changes within this second */ |
323 | while (time(NULL) == last_mtime) |
324 | usleep(100000); |
325 | need_rescan = do_rescan(); |
326 | while (fchdir(curdir) == -1) { |
327 | warn2_cannot("change directory, pausing", ""); |
328 | sleep(5); |
329 | } |
330 | } else { |
331 | warn2_cannot("change directory to ", svdir); |
332 | } |
333 | } |
334 | } else { |
335 | warn2_cannot("stat ", svdir); |
336 | } |
337 | } |
338 | |
339 | #if ENABLE_FEATURE_RUNSVDIR_LOG |
340 | if (rplog) { |
341 | if ((int)(now - stamplog) >= 0) { |
342 | write(logpipe.wr, ".", 1); |
343 | stamplog = now + 900; |
344 | } |
345 | } |
346 | pfd[0].revents = 0; |
347 | #endif |
348 | deadline = (need_rescan ? 1 : 5); |
349 | sig_block(SIGCHLD); |
350 | #if ENABLE_FEATURE_RUNSVDIR_LOG |
351 | if (rplog) |
352 | poll(pfd, 1, deadline*1000); |
353 | else |
354 | #endif |
355 | sleep(deadline); |
356 | sig_unblock(SIGCHLD); |
357 | |
358 | #if ENABLE_FEATURE_RUNSVDIR_LOG |
359 | if (pfd[0].revents & POLLIN) { |
360 | char ch; |
361 | while (read(logpipe.rd, &ch, 1) > 0) { |
362 | if (ch < ' ') |
363 | ch = ' '; |
364 | for (i = 6; i < rploglen; i++) |
365 | rplog[i-1] = rplog[i]; |
366 | rplog[rploglen-1] = ch; |
367 | } |
368 | } |
369 | #endif |
370 | if (!bb_got_signal) |
371 | continue; |
372 | |
373 | /* -s SCRIPT: useful if we are init. |
374 | * In this case typically script never returns, |
375 | * it halts/powers off/reboots the system. */ |
376 | if (opt_s_argv[0]) { |
377 | /* Single parameter: signal# */ |
378 | opt_s_argv[1] = utoa(bb_got_signal); |
379 | pid = spawn(opt_s_argv); |
380 | if (pid > 0) { |
381 | /* Remembering to wait for _any_ children, |
382 | * not just pid */ |
383 | while (wait(NULL) != pid) |
384 | continue; |
385 | } |
386 | } |
387 | |
388 | if (bb_got_signal == SIGHUP) { |
389 | for (i = 0; i < svnum; i++) |
390 | if (sv[i].pid) |
391 | kill(sv[i].pid, SIGTERM); |
392 | } |
393 | /* SIGHUP or SIGTERM (or SIGUSRn if we are init) */ |
394 | /* Exit unless we are init */ |
395 | if (getpid() != 1) |
396 | return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS; |
397 | |
398 | /* init continues to monitor services forever */ |
399 | bb_got_signal = 0; |
400 | } /* for (;;) */ |
401 | } |
402 |