]>
Commit | Line | Data |
---|---|---|
6dbe3af9 KZ |
1 | /* shutdown.c - shutdown a Linux system |
2 | * Initially written by poe@daimi.aau.dk | |
5c36a0eb | 3 | * Currently maintained at ftp://ftp.daimi.aau.dk/pub/Software/Linux/ |
6dbe3af9 KZ |
4 | */ |
5 | ||
6 | /* | |
7 | * Modified by jrs@world.std.com to try to exec "umount -a" and if | |
8 | * that doesn't work, then umount filesystems ourselves in reverse | |
9 | * order. The old-way was in forward order. Also if the device | |
10 | * field of the mtab does not start with a "/" then give umount | |
11 | * the mount point instead. This is needed for the nfs and proc | |
12 | * filesystems and yet is compatible with older systems. | |
13 | * | |
14 | * We also use the mntent library interface to read the mtab file | |
15 | * instead of trying to parse it directly and no longer give a | |
16 | * warning about not being able to umount the root. | |
17 | * | |
18 | * The reason "umount -a" should be tried first is because it may do | |
19 | * special processing for some filesystems (such as informing an | |
20 | * nfs server about nfs umounts) that we don't want to cope with here. | |
21 | */ | |
22 | ||
23 | /* | |
24 | * Various changes and additions to resemble SunOS 4 shutdown/reboot/halt(8) | |
25 | * more closely by Scott Telford (s.telford@ed.ac.uk) 93/05/18. | |
26 | * (I butchered Scotts patches somewhat. - poe) | |
5c36a0eb KZ |
27 | * |
28 | * Changes by Richard Gooch <rgooch@atnf.csiro.au> (butchered by aeb) | |
29 | * introducing shutdown.conf. | |
6dbe3af9 KZ |
30 | */ |
31 | ||
32 | #include <stdio.h> | |
33 | #include <fcntl.h> | |
34 | #include <unistd.h> | |
35 | #include <stdlib.h> | |
36 | #include <utmp.h> | |
37 | #include <time.h> | |
38 | #include <string.h> | |
39 | #include <ctype.h> | |
40 | #include <signal.h> | |
5c36a0eb | 41 | #include <errno.h> |
6dbe3af9 KZ |
42 | #include <sys/param.h> |
43 | #include <termios.h> | |
44 | #include <mntent.h> | |
45 | #include <sys/mount.h> | |
46 | #include <sys/wait.h> | |
47 | #include <syslog.h> | |
48 | #include <sys/resource.h> | |
5c36a0eb | 49 | #include "linux_reboot.h" |
6dbe3af9 KZ |
50 | #include "pathnames.h" |
51 | ||
5c36a0eb KZ |
52 | static void usage(), int_handler(), write_user(struct utmp *); |
53 | static void wall(), write_wtmp(), unmount_disks(), unmount_disks_ourselves(); | |
54 | static void swap_off(), do_halt(char *); | |
6dbe3af9 KZ |
55 | |
56 | char *prog; /* name of the program */ | |
57 | int opt_reboot; /* true if -r option or reboot command */ | |
58 | int timeout; /* number of seconds to shutdown */ | |
59 | int opt_quiet; /* true if no message is wanted */ | |
60 | int opt_fast; /* true if fast boot */ | |
61 | char message[90]; /* reason for shutdown if any... */ | |
62 | int opt_single = 0; /* true is we want to boot singleuser */ | |
63 | char *whom; /* who is shutting the system down */ | |
fd6b7a7f | 64 | int opt_msgset = 0; /* message set on command line */ |
5c36a0eb KZ |
65 | /* change 1 to 0 if no file is to be used by default */ |
66 | int opt_use_config_file = 1; /* read _PATH_SHUTDOWN_CONF */ | |
67 | char halt_action[256]; /* to find out what to do upon halt */ | |
6dbe3af9 KZ |
68 | |
69 | /* #define DEBUGGING */ | |
70 | ||
71 | #define WR(s) write(fd, s, strlen(s)) | |
5c36a0eb KZ |
72 | #define ERRSTRING sys_errlist[errno] |
73 | ||
6dbe3af9 KZ |
74 | |
75 | void | |
76 | usage() | |
77 | { | |
78 | fprintf(stderr, | |
79 | "Usage: shutdown [-h|-r] [-fqs] [now|hh:ss|+mins]\n"); | |
80 | exit(1); | |
81 | } | |
82 | ||
5c36a0eb KZ |
83 | void |
84 | my_puts(char *s) | |
85 | { | |
86 | /* Use a fresh stdout after forking */ | |
87 | freopen(_PATH_CONSOLE, "w", stdout); | |
88 | puts(s); | |
89 | fflush(stdout); | |
90 | } | |
91 | ||
6dbe3af9 KZ |
92 | void |
93 | int_handler() | |
94 | { | |
95 | unlink(_PATH_NOLOGIN); | |
96 | signal(SIGINT, SIG_DFL); | |
5c36a0eb | 97 | my_puts("Shutdown process aborted"); |
6dbe3af9 KZ |
98 | exit(1); |
99 | } | |
100 | ||
101 | int | |
5c36a0eb KZ |
102 | iswhitespace(int a) { |
103 | return (a == ' ' || a == '\t'); | |
104 | } | |
105 | ||
106 | int | |
107 | main(int argc, char *argv[]) | |
6dbe3af9 KZ |
108 | { |
109 | int c,i; | |
110 | int fd; | |
111 | char *ptr; | |
112 | ||
113 | #ifndef DEBUGGING | |
114 | if(geteuid()) { | |
5c36a0eb KZ |
115 | fprintf(stderr, "%s: Only root can shut a system down.\n", |
116 | argv[0]); | |
6dbe3af9 KZ |
117 | exit(1); |
118 | } | |
119 | #endif | |
120 | ||
121 | if(*argv[0] == '-') argv[0]++; /* allow shutdown as login shell */ | |
122 | prog = argv[0]; | |
726f69e2 | 123 | if((ptr = strrchr(argv[0], '/'))) prog = ++ptr; |
5c36a0eb KZ |
124 | |
125 | /* All names (halt, reboot, fasthalt, fastboot, shutdown) | |
126 | refer to the same program with the same options, | |
127 | only the defaults differ. */ | |
6dbe3af9 KZ |
128 | if(!strcmp("halt", prog)) { |
129 | opt_reboot = 0; | |
130 | opt_quiet = 1; | |
131 | opt_fast = 0; | |
132 | timeout = 0; | |
133 | } else if(!strcmp("fasthalt", prog)) { | |
134 | opt_reboot = 0; | |
135 | opt_quiet = 1; | |
136 | opt_fast = 1; | |
137 | timeout = 0; | |
138 | } else if(!strcmp("reboot", prog)) { | |
139 | opt_reboot = 1; | |
140 | opt_quiet = 1; | |
141 | opt_fast = 0; | |
142 | timeout = 0; | |
6dbe3af9 KZ |
143 | } else if(!strcmp("fastboot", prog)) { |
144 | opt_reboot = 1; | |
145 | opt_quiet = 1; | |
146 | opt_fast = 1; | |
147 | timeout = 0; | |
6dbe3af9 KZ |
148 | } else { |
149 | /* defaults */ | |
150 | opt_reboot = 0; | |
151 | opt_quiet = 0; | |
152 | opt_fast = 0; | |
153 | timeout = 2*60; | |
5c36a0eb | 154 | } |
6dbe3af9 | 155 | |
5c36a0eb KZ |
156 | c = 0; |
157 | while(++c < argc) { | |
158 | if(argv[c][0] == '-') { | |
159 | for(i = 1; argv[c][i]; i++) { | |
6dbe3af9 | 160 | switch(argv[c][i]) { |
5c36a0eb KZ |
161 | case 'C': |
162 | opt_use_config_file = 1; | |
163 | break; | |
164 | case 'h': | |
165 | opt_reboot = 0; | |
166 | break; | |
167 | case 'r': | |
168 | opt_reboot = 1; | |
169 | break; | |
170 | case 'f': | |
171 | opt_fast = 1; | |
172 | break; | |
173 | case 'q': | |
174 | opt_quiet = 1; | |
175 | break; | |
176 | case 's': | |
177 | opt_single = 1; | |
178 | break; | |
6dbe3af9 | 179 | |
5c36a0eb KZ |
180 | default: |
181 | usage(); | |
6dbe3af9 | 182 | } |
5c36a0eb KZ |
183 | } |
184 | } else if(!strcmp("now", argv[c])) { | |
185 | timeout = 0; | |
186 | } else if(argv[c][0] == '+') { | |
187 | timeout = 60 * atoi(&argv[c][1]); | |
188 | } else if (isdigit(argv[c][0])) { | |
189 | char *colon; | |
190 | int hour = 0; | |
191 | int minute = 0; | |
192 | time_t tics; | |
193 | struct tm *tt; | |
194 | int now, then; | |
6dbe3af9 | 195 | |
5c36a0eb KZ |
196 | if((colon = strchr(argv[c], ':'))) { |
197 | *colon = '\0'; | |
198 | hour = atoi(argv[c]); | |
199 | minute = atoi(++colon); | |
200 | } else usage(); | |
6dbe3af9 | 201 | |
5c36a0eb KZ |
202 | (void) time(&tics); |
203 | tt = localtime(&tics); | |
6dbe3af9 | 204 | |
5c36a0eb KZ |
205 | now = 3600 * tt->tm_hour + 60 * tt->tm_min; |
206 | then = 3600 * hour + 60 * minute; | |
207 | timeout = then - now; | |
208 | if(timeout < 0) { | |
209 | fprintf(stderr, "That must be tomorrow, " | |
210 | "can't you wait till then?\n"); | |
211 | exit(1); | |
212 | } | |
213 | } else { | |
214 | strncpy(message, argv[c], sizeof(message)); | |
215 | message[sizeof(message)-1] = '\0'; | |
216 | opt_msgset = 1; | |
217 | } | |
218 | } | |
219 | ||
220 | halt_action[0] = 0; | |
221 | ||
222 | /* No doubt we shall want to extend this some day | |
223 | and register a series of commands to be executed | |
224 | at various points during the shutdown sequence, | |
225 | and to define the number of milliseconds to sleep, etc. */ | |
226 | if (opt_use_config_file) { | |
227 | char line[256], *p; | |
228 | FILE *fp; | |
229 | ||
230 | /* Read and parse the config file */ | |
231 | halt_action[0] = '\0'; | |
232 | if ((fp = fopen (_PATH_SHUTDOWN_CONF, "r")) != NULL) { | |
233 | if (fgets (line, sizeof(line), fp) != NULL && | |
234 | strncasecmp (line, "HALT_ACTION", 11) == 0 && | |
235 | iswhitespace(line[11])) { | |
236 | p = line+11; | |
237 | while(iswhitespace(*p)) | |
238 | p++; | |
239 | strcpy(halt_action, p); | |
6dbe3af9 | 240 | } |
5c36a0eb | 241 | fclose (fp); |
6dbe3af9 KZ |
242 | } |
243 | } | |
244 | ||
fd6b7a7f | 245 | if(!opt_quiet && !opt_msgset) { |
6dbe3af9 KZ |
246 | /* now ask for message, gets() is insecure */ |
247 | int cnt = sizeof(message)-1; | |
248 | char *ptr; | |
249 | ||
250 | printf("Why? "); fflush(stdout); | |
251 | ||
252 | ptr = message; | |
253 | while(--cnt >= 0 && (*ptr = getchar()) && *ptr != '\n') { | |
254 | ptr++; | |
255 | } | |
256 | *ptr = '\0'; | |
fd6b7a7f | 257 | } else if (!opt_msgset) { |
6dbe3af9 | 258 | strcpy(message, "for maintenance; bounce, bounce"); |
fd6b7a7f | 259 | } |
6dbe3af9 KZ |
260 | |
261 | #ifdef DEBUGGING | |
262 | printf("timeout = %d, quiet = %d, reboot = %d\n", | |
263 | timeout, opt_quiet, opt_reboot); | |
264 | #endif | |
265 | ||
266 | /* so much for option-processing, now begin termination... */ | |
267 | if(!(whom = getlogin())) whom = "ghost"; | |
5c36a0eb | 268 | if(strlen(whom) > 40) whom[40] = 0; /* see write_user() */ |
6dbe3af9 KZ |
269 | |
270 | setpriority(PRIO_PROCESS, 0, PRIO_MIN); | |
271 | signal(SIGINT, int_handler); | |
272 | signal(SIGHUP, int_handler); | |
273 | signal(SIGQUIT, int_handler); | |
274 | signal(SIGTERM, int_handler); | |
275 | ||
276 | chdir("/"); | |
277 | ||
278 | if(timeout > 5*60) { | |
279 | sleep(timeout - 5*60); | |
280 | timeout = 5*60; | |
281 | } | |
282 | ||
283 | ||
fd6b7a7f | 284 | if((fd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT, 0644)) >= 0) { |
6dbe3af9 KZ |
285 | WR("\r\nThe system is being shut down within 5 minutes\r\n"); |
286 | write(fd, message, strlen(message)); | |
287 | WR("\r\nLogin is therefore prohibited.\r\n"); | |
288 | close(fd); | |
289 | } | |
290 | ||
291 | signal(SIGPIPE, SIG_IGN); | |
292 | ||
293 | if(timeout > 0) { | |
294 | wall(); | |
295 | sleep(timeout); | |
296 | } | |
297 | ||
298 | timeout = 0; | |
299 | wall(); | |
300 | sleep(3); | |
301 | ||
302 | /* now there's no turning back... */ | |
303 | signal(SIGINT, SIG_IGN); | |
304 | ||
305 | /* do syslog message... */ | |
306 | openlog(prog, LOG_CONS, LOG_AUTH); | |
307 | syslog(LOG_NOTICE, "%s by %s: %s", | |
308 | opt_reboot ? "rebooted" : "halted", whom, message); | |
309 | closelog(); | |
310 | ||
311 | if(opt_fast) | |
fd6b7a7f | 312 | if((fd = open("/fastboot", O_WRONLY|O_CREAT, 0644)) >= 0) |
6dbe3af9 KZ |
313 | close(fd); |
314 | ||
315 | kill(1, SIGTSTP); /* tell init not to spawn more getty's */ | |
316 | write_wtmp(); | |
317 | if(opt_single) | |
5c36a0eb KZ |
318 | if((fd = open(_PATH_SINGLE, O_CREAT|O_WRONLY, 0644)) >= 0) |
319 | close(fd); | |
6dbe3af9 KZ |
320 | |
321 | sync(); | |
322 | ||
323 | signal(SIGTERM, SIG_IGN); | |
324 | if(fork() > 0) sleep(1000); /* the parent will die soon... */ | |
325 | setpgrp(); /* so the shell wont kill us in the fall */ | |
326 | ||
327 | #ifndef DEBUGGING | |
328 | /* a gentle kill of all other processes except init */ | |
329 | kill(-1, SIGTERM); | |
5c36a0eb | 330 | sleep(2); /* default 2, some people need 5 */ |
6dbe3af9 KZ |
331 | |
332 | /* now use brute force... */ | |
333 | kill(-1, SIGKILL); | |
334 | ||
335 | /* turn off accounting */ | |
336 | acct(NULL); | |
337 | #endif | |
338 | sync(); | |
339 | sleep(2); | |
340 | ||
726f69e2 KZ |
341 | /* remove swap files and partitions using swapoff */ |
342 | swap_off(); | |
343 | ||
6dbe3af9 KZ |
344 | /* unmount disks... */ |
345 | unmount_disks(); | |
346 | sync(); | |
347 | sleep(1); | |
348 | ||
349 | if(opt_reboot) { | |
5c36a0eb KZ |
350 | my_reboot(LINUX_REBOOT_CMD_RESTART); /* RB_AUTOBOOT */ |
351 | my_puts("\nWhy am I still alive after reboot?"); | |
6dbe3af9 | 352 | } else { |
5c36a0eb KZ |
353 | my_puts("\nNow you can turn off the power..."); |
354 | ||
fd6b7a7f | 355 | /* allow C-A-D now, faith@cs.unc.edu, re-fixed 8-Jul-96 */ |
5c36a0eb KZ |
356 | my_reboot(LINUX_REBOOT_CMD_CAD_ON); /* RB_ENABLE_CAD */ |
357 | do_halt(halt_action); | |
6dbe3af9 KZ |
358 | } |
359 | /* NOTREACHED */ | |
360 | exit(0); /* to quiet gcc */ | |
361 | } | |
362 | ||
363 | /*** end of main() ***/ | |
364 | ||
5c36a0eb KZ |
365 | void |
366 | do_halt(char *action) { | |
367 | if (strcasecmp (action, "power_off") == 0) { | |
368 | printf("Calling kernel power-off facility...\n"); | |
369 | fflush(stdout); | |
370 | my_reboot(LINUX_REBOOT_CMD_POWER_OFF); | |
371 | printf("Error powering off\t%s\n", ERRSTRING); | |
372 | fflush(stdout); | |
373 | sleep (2); | |
374 | } else | |
375 | ||
376 | /* This should be improved; e.g. Mike Jagdis wants "/sbin/mdstop -a" */ | |
377 | /* Maybe we should also fork and wait */ | |
378 | if (action[0] == '/') { | |
379 | printf("Executing the program \"%s\" ...\n", action); | |
380 | fflush(stdout); | |
381 | execl(action, action, NULL); | |
382 | printf("Error executing\t%s\n", ERRSTRING); | |
383 | fflush(stdout); | |
384 | sleep (2); | |
385 | } | |
386 | ||
387 | my_reboot(LINUX_REBOOT_CMD_HALT); /* RB_HALT_SYSTEM */ | |
388 | } | |
389 | ||
6dbe3af9 KZ |
390 | void |
391 | write_user(struct utmp *ut) | |
392 | { | |
393 | int fd; | |
394 | int minutes, hours; | |
395 | char term[40] = {'/','d','e','v','/',0}; | |
396 | char msg[100]; | |
397 | ||
398 | minutes = timeout / 60; | |
399 | (void) strncat(term, ut->ut_line, sizeof(ut->ut_line)); | |
400 | ||
401 | /* try not to get stuck on a mangled ut_line entry... */ | |
5c36a0eb | 402 | if((fd = open(term, O_WRONLY|O_NONBLOCK)) < 0) |
6dbe3af9 KZ |
403 | return; |
404 | ||
405 | sprintf(msg, "\007\r\nURGENT: broadcast message from %s:\r\n", whom); | |
406 | WR(msg); | |
407 | ||
408 | if(minutes == 0) { | |
409 | sprintf(msg, "System going down IMMEDIATELY!\r\n\n"); | |
410 | } else if(minutes > 60) { | |
411 | hours = minutes / 60; | |
412 | sprintf(msg, "System going down in %d hour%s %d minutes\r\n", | |
413 | hours, hours == 1 ? "" : "s", minutes - 60*hours); | |
414 | } else { | |
415 | sprintf(msg, "System going down in %d minute%s\r\n\n", | |
416 | minutes, minutes == 1 ? "" : "s"); | |
417 | } | |
418 | WR(msg); | |
419 | ||
420 | sprintf(msg, "\t... %s ...\r\n\n", message); | |
421 | WR(msg); | |
422 | ||
423 | close(fd); | |
424 | } | |
425 | ||
426 | void | |
427 | wall() | |
428 | { | |
429 | /* write to all users, that the system is going down. */ | |
430 | struct utmp *ut; | |
431 | ||
432 | utmpname(_PATH_UTMP); | |
433 | setutent(); | |
434 | ||
726f69e2 | 435 | while((ut = getutent())) { |
6dbe3af9 KZ |
436 | if(ut->ut_type == USER_PROCESS) |
437 | write_user(ut); | |
438 | } | |
439 | endutent(); | |
440 | } | |
441 | ||
442 | void | |
443 | write_wtmp() | |
444 | { | |
445 | /* write in wtmp that we are dying */ | |
446 | int fd; | |
447 | struct utmp ut; | |
448 | ||
449 | memset((char *)&ut, 0, sizeof(ut)); | |
450 | strcpy(ut.ut_line, "~"); | |
451 | memcpy(ut.ut_name, "shutdown", sizeof(ut.ut_name)); | |
452 | ||
453 | time(&ut.ut_time); | |
454 | ut.ut_type = BOOT_TIME; | |
455 | ||
fd6b7a7f | 456 | if((fd = open(_PATH_WTMP, O_WRONLY|O_APPEND, 0644)) >= 0) { |
6dbe3af9 KZ |
457 | write(fd, (char *)&ut, sizeof(ut)); |
458 | close(fd); | |
459 | } | |
460 | } | |
461 | ||
726f69e2 KZ |
462 | void |
463 | swap_off() | |
464 | { | |
465 | /* swapoff esp. swap FILES so the underlying partition can be | |
466 | unmounted. It you don't have swapoff(1) or use mount to | |
467 | add swapspace, this may not be necessary, but I guess it | |
468 | won't hurt */ | |
469 | ||
470 | int pid; | |
471 | int result; | |
472 | int status; | |
473 | ||
474 | sync(); | |
475 | if ((pid = fork()) < 0) { | |
5c36a0eb | 476 | my_puts("Cannot fork for swapoff. Shrug!"); |
726f69e2 KZ |
477 | return; |
478 | } | |
479 | if (!pid) { | |
480 | execl("/sbin/swapoff", SWAPOFF_ARGS, NULL); | |
481 | execl("/etc/swapoff", SWAPOFF_ARGS, NULL); | |
482 | execl("/bin/swapoff", SWAPOFF_ARGS, NULL); | |
483 | execlp("swapoff", SWAPOFF_ARGS, NULL); | |
5c36a0eb KZ |
484 | my_puts("Cannot exec swapoff, " |
485 | "hoping umount will do the trick."); | |
726f69e2 KZ |
486 | exit(0); |
487 | } | |
488 | while ((result = wait(&status)) != -1 && result != pid) | |
489 | ; | |
490 | } | |
491 | ||
6dbe3af9 KZ |
492 | void |
493 | unmount_disks() | |
494 | { | |
495 | /* better to use umount directly because it may be smarter than us */ | |
496 | ||
497 | int pid; | |
498 | int result; | |
499 | int status; | |
500 | ||
501 | sync(); | |
502 | if ((pid = fork()) < 0) { | |
5c36a0eb | 503 | my_puts("Cannot fork for umount, trying manually."); |
6dbe3af9 KZ |
504 | unmount_disks_ourselves(); |
505 | return; | |
506 | } | |
507 | if (!pid) { | |
508 | execl(_PATH_UMOUNT, UMOUNT_ARGS, NULL); | |
5c36a0eb | 509 | my_puts("Cannot exec " _PATH_UMOUNT ", trying umount."); |
6dbe3af9 | 510 | execlp("umount", UMOUNT_ARGS, NULL); |
5c36a0eb | 511 | my_puts("Cannot exec umount, giving up on umount."); |
6dbe3af9 KZ |
512 | exit(0); |
513 | } | |
514 | while ((result = wait(&status)) != -1 && result != pid) | |
515 | ; | |
5c36a0eb | 516 | my_puts("Unmounting any remaining filesystems..."); |
726f69e2 | 517 | unmount_disks_ourselves(); |
6dbe3af9 KZ |
518 | } |
519 | ||
520 | void | |
521 | unmount_disks_ourselves() | |
522 | { | |
523 | /* unmount all disks */ | |
524 | ||
525 | FILE *mtab; | |
526 | struct mntent *mnt; | |
527 | char *mntlist[128]; | |
528 | int i; | |
529 | int n; | |
530 | char *filesys; | |
531 | ||
532 | sync(); | |
533 | if (!(mtab = setmntent(_PATH_MTAB, "r"))) { | |
5c36a0eb | 534 | my_puts("shutdown: Cannot open " _PATH_MTAB "."); |
6dbe3af9 KZ |
535 | return; |
536 | } | |
537 | n = 0; | |
538 | while (n < 100 && (mnt = getmntent(mtab))) { | |
539 | mntlist[n++] = strdup(mnt->mnt_fsname[0] == '/' ? | |
540 | mnt->mnt_fsname : mnt->mnt_dir); | |
541 | } | |
542 | endmntent(mtab); | |
543 | ||
544 | /* we are careful to do this in reverse order of the mtab file */ | |
545 | ||
546 | for (i = n - 1; i >= 0; i--) { | |
547 | filesys = mntlist[i]; | |
548 | #ifdef DEBUGGING | |
549 | printf("umount %s\n", filesys); | |
550 | #else | |
551 | if (umount(mntlist[i]) < 0) | |
726f69e2 | 552 | printf("shutdown: Couldn't umount %s\n", filesys); |
6dbe3af9 KZ |
553 | #endif |
554 | } | |
555 | } |