diff options
Diffstat (limited to 'login-utils/shutdown.c')
-rw-r--r-- | login-utils/shutdown.c | 435 |
1 files changed, 435 insertions, 0 deletions
diff --git a/login-utils/shutdown.c b/login-utils/shutdown.c new file mode 100644 index 000000000..0ca303988 --- /dev/null +++ b/login-utils/shutdown.c @@ -0,0 +1,435 @@ +/* shutdown.c - shutdown a Linux system + * Initially written by poe@daimi.aau.dk + * Currently maintained at ftp://ftp.daimi.aau.dk/pub/linux/poe/ + */ + +/* + * Modified by jrs@world.std.com to try to exec "umount -a" and if + * that doesn't work, then umount filesystems ourselves in reverse + * order. The old-way was in forward order. Also if the device + * field of the mtab does not start with a "/" then give umount + * the mount point instead. This is needed for the nfs and proc + * filesystems and yet is compatible with older systems. + * + * We also use the mntent library interface to read the mtab file + * instead of trying to parse it directly and no longer give a + * warning about not being able to umount the root. + * + * The reason "umount -a" should be tried first is because it may do + * special processing for some filesystems (such as informing an + * nfs server about nfs umounts) that we don't want to cope with here. + */ + +/* + * Various changes and additions to resemble SunOS 4 shutdown/reboot/halt(8) + * more closely by Scott Telford (s.telford@ed.ac.uk) 93/05/18. + * (I butchered Scotts patches somewhat. - poe) + */ + +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <utmp.h> +#include <time.h> +#include <string.h> +#include <ctype.h> +#include <signal.h> +#include <sys/param.h> +#include <termios.h> +#include <mntent.h> +#include <sys/mount.h> +#include <sys/wait.h> +#include <syslog.h> +#include <sys/resource.h> +#include "pathnames.h" + +void usage(), int_handler(), write_user(struct utmp *); +void wall(), write_wtmp(), unmount_disks(), unmount_disks_ourselves(); + +char *prog; /* name of the program */ +int opt_reboot; /* true if -r option or reboot command */ +int timeout; /* number of seconds to shutdown */ +int opt_quiet; /* true if no message is wanted */ +int opt_fast; /* true if fast boot */ +char message[90]; /* reason for shutdown if any... */ +int opt_single = 0; /* true is we want to boot singleuser */ +char *whom; /* who is shutting the system down */ + +/* #define DEBUGGING */ + +#define WR(s) write(fd, s, strlen(s)) + +void +usage() +{ + fprintf(stderr, + "Usage: shutdown [-h|-r] [-fqs] [now|hh:ss|+mins]\n"); + exit(1); +} + +void +int_handler() +{ + unlink(_PATH_NOLOGIN); + signal(SIGINT, SIG_DFL); + puts("Shutdown process aborted\n"); + exit(1); +} + +int +main(argc, argv) + int argc; + char *argv[]; +{ + int c,i; + int fd; + char *ptr; + +#ifndef DEBUGGING + if(geteuid()) { + fprintf(stderr, "Only root can shut a system down.\n"); + exit(1); + } +#endif + + if(*argv[0] == '-') argv[0]++; /* allow shutdown as login shell */ + prog = argv[0]; + if(ptr = strrchr(argv[0], '/')) prog = ++ptr; + + if(!strcmp("halt", prog)) { + opt_reboot = 0; + opt_quiet = 1; + opt_fast = 0; + timeout = 0; + } else if(!strcmp("fasthalt", prog)) { + opt_reboot = 0; + opt_quiet = 1; + opt_fast = 1; + timeout = 0; + } else if(!strcmp("reboot", prog)) { + opt_reboot = 1; + opt_quiet = 1; + opt_fast = 0; + timeout = 0; + if(argc > 1 && !strcmp(argv[1], "-s")) opt_single = 1; + } else if(!strcmp("fastboot", prog)) { + opt_reboot = 1; + opt_quiet = 1; + opt_fast = 1; + timeout = 0; + if(argc > 1 && !strcmp(argv[1], "-s")) opt_single = 1; + } else { + /* defaults */ + opt_reboot = 0; + opt_quiet = 0; + opt_fast = 0; + timeout = 2*60; + + c = 0; + while(++c < argc) { + if(argv[c][0] == '-') { + for(i = 1; argv[c][i]; i++) { + switch(argv[c][i]) { + case 'h': + opt_reboot = 0; + break; + case 'r': + opt_reboot = 1; + break; + case 'f': + opt_fast = 1; + break; + case 'q': + opt_quiet = 1; + break; + case 's': + opt_single = 1; + break; + + default: + usage(); + } + } + } else if(!strcmp("now", argv[c])) { + timeout = 0; + } else if(argv[c][0] == '+') { + timeout = 60 * atoi(&argv[c][1]); + } else { + char *colon; + int hour = 0; + int minute = 0; + time_t tics; + struct tm *tt; + int now, then; + + if(colon = strchr(argv[c], ':')) { + *colon = '\0'; + hour = atoi(argv[c]); + minute = atoi(++colon); + } else usage(); + + (void) time(&tics); + tt = localtime(&tics); + + now = 3600 * tt->tm_hour + 60 * tt->tm_min; + then = 3600 * hour + 60 * minute; + timeout = then - now; + if(timeout < 0) { + fprintf(stderr, "That must be tomorrow, can't you wait till then?\n"); + exit(1); + } + } + } + } + + if(!opt_quiet) { + /* now ask for message, gets() is insecure */ + int cnt = sizeof(message)-1; + char *ptr; + + printf("Why? "); fflush(stdout); + + ptr = message; + while(--cnt >= 0 && (*ptr = getchar()) && *ptr != '\n') { + ptr++; + } + *ptr = '\0'; + } else + strcpy(message, "for maintenance; bounce, bounce"); + +#ifdef DEBUGGING + printf("timeout = %d, quiet = %d, reboot = %d\n", + timeout, opt_quiet, opt_reboot); +#endif + + /* so much for option-processing, now begin termination... */ + if(!(whom = getlogin())) whom = "ghost"; + + setpriority(PRIO_PROCESS, 0, PRIO_MIN); + signal(SIGINT, int_handler); + signal(SIGHUP, int_handler); + signal(SIGQUIT, int_handler); + signal(SIGTERM, int_handler); + + chdir("/"); + + if(timeout > 5*60) { + sleep(timeout - 5*60); + timeout = 5*60; + } + + + if((fd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT)) >= 0) { + WR("\r\nThe system is being shut down within 5 minutes\r\n"); + write(fd, message, strlen(message)); + WR("\r\nLogin is therefore prohibited.\r\n"); + close(fd); + } + + signal(SIGPIPE, SIG_IGN); + + if(timeout > 0) { + wall(); + sleep(timeout); + } + + timeout = 0; + wall(); + sleep(3); + + /* now there's no turning back... */ + signal(SIGINT, SIG_IGN); + + /* do syslog message... */ + openlog(prog, LOG_CONS, LOG_AUTH); + syslog(LOG_NOTICE, "%s by %s: %s", + opt_reboot ? "rebooted" : "halted", whom, message); + closelog(); + + if(opt_fast) + if((fd = open("/fastboot", O_WRONLY|O_CREAT)) >= 0) + close(fd); + + kill(1, SIGTSTP); /* tell init not to spawn more getty's */ + write_wtmp(); + if(opt_single) + close(open(_PATH_SINGLE, O_CREAT|O_WRONLY)); + + sync(); + + signal(SIGTERM, SIG_IGN); + if(fork() > 0) sleep(1000); /* the parent will die soon... */ + setpgrp(); /* so the shell wont kill us in the fall */ + +#ifndef DEBUGGING + /* a gentle kill of all other processes except init */ + kill(-1, SIGTERM); + sleep(2); + + /* now use brute force... */ + kill(-1, SIGKILL); + + /* turn off accounting */ + acct(NULL); +#endif + sync(); + sleep(2); + + /* unmount disks... */ + unmount_disks(); + sync(); + sleep(1); + + if(opt_reboot) { + reboot(0xfee1dead, 672274793, 0x1234567); + } else { + printf("\nNow you can turn off the power...\n"); + /* allow C-A-D now, faith@cs.unc.edu */ + reboot(0xfee1dead, 672274793, 0x89abcdef); + } + /* NOTREACHED */ + exit(0); /* to quiet gcc */ +} + +/*** end of main() ***/ + +void +write_user(struct utmp *ut) +{ + int fd; + int minutes, hours; + char term[40] = {'/','d','e','v','/',0}; + char msg[100]; + + minutes = timeout / 60; + (void) strncat(term, ut->ut_line, sizeof(ut->ut_line)); + + /* try not to get stuck on a mangled ut_line entry... */ + if((fd = open(term, O_RDWR|O_NONBLOCK)) < 0) + return; + + sprintf(msg, "\007\r\nURGENT: broadcast message from %s:\r\n", whom); + WR(msg); + + if(minutes == 0) { + sprintf(msg, "System going down IMMEDIATELY!\r\n\n"); + } else if(minutes > 60) { + hours = minutes / 60; + sprintf(msg, "System going down in %d hour%s %d minutes\r\n", + hours, hours == 1 ? "" : "s", minutes - 60*hours); + } else { + sprintf(msg, "System going down in %d minute%s\r\n\n", + minutes, minutes == 1 ? "" : "s"); + } + WR(msg); + + sprintf(msg, "\t... %s ...\r\n\n", message); + WR(msg); + + close(fd); +} + +void +wall() +{ + /* write to all users, that the system is going down. */ + struct utmp *ut; + + utmpname(_PATH_UTMP); + setutent(); + + while(ut = getutent()) { + if(ut->ut_type == USER_PROCESS) + write_user(ut); + } + endutent(); +} + +void +write_wtmp() +{ + /* write in wtmp that we are dying */ + int fd; + struct utmp ut; + + memset((char *)&ut, 0, sizeof(ut)); + strcpy(ut.ut_line, "~"); + memcpy(ut.ut_name, "shutdown", sizeof(ut.ut_name)); + + time(&ut.ut_time); + ut.ut_type = BOOT_TIME; + + if((fd = open(_PATH_WTMP, O_WRONLY|O_APPEND)) > 0) { + write(fd, (char *)&ut, sizeof(ut)); + close(fd); + } +} + +void +unmount_disks() +{ + /* better to use umount directly because it may be smarter than us */ + + int pid; + int result; + int status; + + sync(); + if ((pid = fork()) < 0) { + printf("Cannot fork for umount, trying manually.\n"); + unmount_disks_ourselves(); + return; + } + if (!pid) { + execl(_PATH_UMOUNT, UMOUNT_ARGS, NULL); + printf("Cannot exec %s, trying umount.\n", _PATH_UMOUNT); + execlp("umount", UMOUNT_ARGS, NULL); + printf("Cannot exec umount, trying manually.\n"); + unmount_disks_ourselves(); + exit(0); + } + while ((result = wait(&status)) != -1 && result != pid) + ; + if (result == -1 || status) { + printf("Running umount failed, trying manually.\n"); + unmount_disks_ourselves(); + } +} + +void +unmount_disks_ourselves() +{ + /* unmount all disks */ + + FILE *mtab; + struct mntent *mnt; + char *mntlist[128]; + int i; + int n; + char *filesys; + + sync(); + if (!(mtab = setmntent(_PATH_MTAB, "r"))) { + printf("Cannot open %s.\n", _PATH_MTAB); + return; + } + n = 0; + while (n < 100 && (mnt = getmntent(mtab))) { + mntlist[n++] = strdup(mnt->mnt_fsname[0] == '/' ? + mnt->mnt_fsname : mnt->mnt_dir); + } + endmntent(mtab); + + /* we are careful to do this in reverse order of the mtab file */ + + for (i = n - 1; i >= 0; i--) { + filesys = mntlist[i]; +#ifdef DEBUGGING + printf("umount %s\n", filesys); +#else + if (umount(mntlist[i]) < 0) + printf("Couldn't umount %s\n", filesys); +#endif + } +} |