diff options
author | Karel Zak <kzak@redhat.com> | 2010-12-10 17:28:32 +0100 |
---|---|---|
committer | Karel Zak <kzak@redhat.com> | 2010-12-10 17:28:32 +0100 |
commit | 5502d92b28e442c8b9294608eaf67df9127d11e9 (patch) | |
tree | 924b81cc9b93c860c1f72298b0afbe4100a50052 /simpleinit/shutdown.c | |
parent | cda8458b4d1e6f2772b77a448b342806057f0a3a (diff) | |
download | util-linux-old-5502d92b28e442c8b9294608eaf67df9127d11e9.tar.gz |
simpleinit: move old init stuff to separate directory
Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'simpleinit/shutdown.c')
-rw-r--r-- | simpleinit/shutdown.c | 747 |
1 files changed, 747 insertions, 0 deletions
diff --git a/simpleinit/shutdown.c b/simpleinit/shutdown.c new file mode 100644 index 00000000..9bb121cf --- /dev/null +++ b/simpleinit/shutdown.c @@ -0,0 +1,747 @@ +/* shutdown.c - shutdown a Linux system + * Initially written by poe@daimi.aau.dk + * Currently maintained at ftp://ftp.daimi.aau.dk/pub/Software/Linux/ + */ + +/* + * 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) + * + * Changes by Richard Gooch <rgooch@atnf.csiro.au> (butchered by aeb) + * introducing shutdown.conf. + * + * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * + * 2000-03-02 Richard Gooch <rgooch@atnf.csiro.au> + * - pause forever if (pid == 1) and send SIGQUIT to pid = 1 + * + * 2000-11-04 Richard Gooch <rgooch@atnf.csiro.au> + * - continue reaping if (pid == 1) + * + * 2000-11-06 Richard Gooch <rgooch@atnf.csiro.au> + * - shut down "finalprog" from /etc/inittab + * - kill normal user (non-root and non-daemon) processes first with SIGTERM + * + * 2000-11-08 Richard Gooch <rgooch@atnf.csiro.au> + * - rollback services + * - do not unmount devfs (otherwise get harmless but annoying messages) + * - created syncwait() for faster shutting down + * - kill getty processes + * 2001-05-12 Richard Gooch <rgooch@atnf.csiro.au> + * - unblock all signals (sigmask from simpleinit(8) stopped sleep(3)) + * - close all files + */ + +#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 <errno.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 <sys/types.h> +#include <dirent.h> +#include <sys/stat.h> +#include <sys/utsname.h> +#include "linux_reboot.h" +#include "pathnames.h" +#include "strutils.h" +#include "nls.h" +#include "usleep.h" + +static void usage(void), int_handler(int), write_user(struct utmp *); +static void wall(void), write_wtmp(void), unmount_disks(void); +static void unmount_disks_ourselves(void); +static void swap_off(void), do_halt(char *); +static void kill_mortals (int sig); +static void stop_finalprog (void); +static void syncwait (int timeval); + + +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 */ +int opt_msgset = 0; /* message set on command line */ + /* change 1 to 0 if no file is to be used by default */ +int opt_use_config_file = 1; /* read _PATH_SHUTDOWN_CONF */ +char halt_action[256]; /* to find out what to do upon halt */ + +/* #define DEBUGGING */ + +#define WR(s) write(fd, s, strlen(s)) +#define WRCRLF write(fd, "\r\n", 2) +#define ERRSTRING strerror(errno) + +#define UMOUNT_ARGS "umount", "-a", "-t", "nodevfs,devtmpfs" +#define SWAPOFF_ARGS "swapoff", "-a" + +void +usage(void) +{ + fprintf(stderr, + _("Usage: shutdown [-h|-r] [-fqs] [now|hh:ss|+mins]\n")); + exit(EXIT_FAILURE); +} + +static void +my_puts(char *s) +{ + /* Use a fresh stdout after forking */ + freopen(_PATH_CONSOLE, "w", stdout); + puts(s); + fflush(stdout); +} + +void +int_handler(int sig) +{ + unlink(_PATH_NOLOGIN); + signal(SIGINT, SIG_DFL); + my_puts(_("Shutdown process aborted")); + _exit(EXIT_FAILURE); +} + +static int +iswhitespace(int a) { + return (a == ' ' || a == '\t'); +} + +int +main(int argc, char *argv[]) +{ + int c, i, fd; + char *ptr; + + i = getdtablesize (); + for (fd = 3; fd < i; fd++) close (fd); + if (getpid () == 1) + { + for (fd = 0; fd < 3; fd++) close (fd); + while (1) wait (NULL); /* Grim reaper never stops */ + } + sigsetmask (0); /* simpleinit(8) blocks all signals: undo for ALRM */ + for (i = 1; i < NSIG; i++) signal (i, SIG_DFL); + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + +#ifndef DEBUGGING + if(setreuid (0, 0)) + errx(EXIT_FAILURE, _("only root can shut a system down.")); +#endif + + if(*argv[0] == '-') argv[0]++; /* allow shutdown as login shell */ + prog = argv[0]; + if((ptr = strrchr(argv[0], '/'))) prog = ++ptr; + + /* All names (halt, reboot, fasthalt, fastboot, shutdown) + refer to the same program with the same options, + only the defaults differ. */ + 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; + } else if(!strcmp("fastboot", prog)) { + opt_reboot = 1; + opt_quiet = 1; + opt_fast = 1; + timeout = 0; + } 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 'C': + opt_use_config_file = 1; + break; + 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; + + default: + usage(); + } + } + } else if(!strcmp("now", argv[c])) { + timeout = 0; + } else if(argv[c][0] == '+') { + timeout = 60 * atoi(&argv[c][1]); + } else if (isdigit(argv[c][0])) { + 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) + errx(EXIT_FAILURE, _("that must be tomorrow, " + "can't you wait till then?")); + } else { + xstrncpy(message, argv[c], sizeof(message)); + opt_msgset = 1; + } + } + + halt_action[0] = 0; + + /* No doubt we shall want to extend this some day + and register a series of commands to be executed + at various points during the shutdown sequence, + and to define the number of milliseconds to sleep, etc. */ + if (opt_use_config_file) { + char line[256], *p; + FILE *fp; + + /* Read and parse the config file */ + halt_action[0] = '\0'; + if ((fp = fopen (_PATH_SHUTDOWN_CONF, "r")) != NULL) { + if (fgets (line, sizeof(line), fp) != NULL && + strncasecmp (line, "HALT_ACTION", 11) == 0 && + iswhitespace(line[11])) { + p = strchr(line, '\n'); + if (p) + *p = 0; /* strip final '\n' */ + p = line+11; + while(iswhitespace(*p)) + p++; + strcpy(halt_action, p); + } + fclose (fp); + } + } + + if(!opt_quiet && !opt_msgset) { + /* 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 if (!opt_msgset) { + 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) whom = "ghost"; + if(strlen(whom) > 40) whom[40] = 0; /* see write_user() */ + + 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, 0644)) >= 0) { + /* keep xgettext happy and leave \r\n outside strings */ + WRCRLF; + WR(_("The system is being shut down within 5 minutes")); + WRCRLF; + write(fd, message, strlen(message)); + WRCRLF; + WR(_("Login is therefore prohibited.")); + WRCRLF; + 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); + if (opt_reboot) + syslog(LOG_NOTICE, _("rebooted by %s: %s"), + whom, message); + else + syslog(LOG_NOTICE, _("halted by %s: %s"), + whom, message); + closelog(); + + if(opt_fast) + if((fd = open("/fastboot", O_WRONLY|O_CREAT, 0644)) >= 0) + close(fd); + + kill(1, SIGTSTP); /* tell init not to spawn more getty's */ + write_wtmp(); + if(opt_single) + if((fd = open(_PATH_SINGLE, O_CREAT|O_WRONLY, 0644)) >= 0) + close(fd); + + 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_mortals (SIGTERM); + for (fd = 0; fd < 3; fd++) close (fd); + stop_finalprog (); + sleep (1); /* Time for saves to start */ + kill (1, SIGTERM); /* Tell init to kill spawned gettys */ + usleep (100000); /* Wait for gettys to die */ + my_puts (""); /* Get past the login prompt */ + system ("/sbin/initctl -r"); /* Roll back services */ + syncwait (1); + my_puts ("Sending SIGTERM to all remaining processes..."); + kill (-1, SIGTERM); + sleep (2); /* Default 2, some people need 5 */ + + kill (-1, SIGKILL); /* Now use brute force... */ + + /* turn off accounting */ + acct(NULL); +#endif + /* RedHat and SuSE like to remove /etc/nologin. + Perhaps the usual sequence is + touch nologin; shutdown -h; fiddle with hardware; + boot; fiddle with software; rm nologin + and removing it here will be counterproductive. + Let us see whether people complain. */ + unlink(_PATH_NOLOGIN); + + /* Tell init(8) to exec so that the old inode may be freed cleanly if + required. Need to sleep before remounting root read-only */ + kill (1, SIGQUIT); + + sleep (1); /* Time for processes to die and close files */ + syncwait (2); + + /* remove swap files and partitions using swapoff */ + swap_off(); + + /* unmount disks... */ + unmount_disks(); + syncwait (1); + + if(opt_reboot) { + my_reboot(LINUX_REBOOT_CMD_RESTART); /* RB_AUTOBOOT */ + my_puts(_("\nWhy am I still alive after reboot?")); + } else { + my_puts(_("\nNow you can turn off the power...")); + + /* allow C-A-D now, faith@cs.unc.edu, re-fixed 8-Jul-96 */ + my_reboot(LINUX_REBOOT_CMD_CAD_ON); /* RB_ENABLE_CAD */ + sleep (1); /* Wait for devices to finish writing to media */ + do_halt(halt_action); + } + /* NOTREACHED */ + exit(EXIT_SUCCESS); /* to quiet gcc */ +} + +/*** end of main() ***/ + +void +do_halt(char *action) { + if (strcasecmp (action, "power_off") == 0) { + printf(_("Calling kernel power-off facility...\n")); + fflush(stdout); + my_reboot(LINUX_REBOOT_CMD_POWER_OFF); + printf(_("Error powering off\t%s\n"), ERRSTRING); + fflush(stdout); + sleep (2); + } else + + /* This should be improved; e.g. Mike Jagdis wants "/sbin/mdstop -a" */ + /* Maybe we should also fork and wait */ + if (action[0] == '/') { + printf(_("Executing the program \"%s\" ...\n"), action); + fflush(stdout); + execl(action, action, NULL); + printf(_("Error executing\t%s\n"), ERRSTRING); + fflush(stdout); + sleep (2); + } + + my_reboot(LINUX_REBOOT_CMD_HALT); /* RB_HALT_SYSTEM */ +} + +void +write_user(struct utmp *ut) +{ + int fd; + int minutes, hours; + char term[40] = {'/','d','e','v','/',0}; + char msg[100]; + + minutes = timeout / 60; + hours = minutes / 60; + minutes %= 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_WRONLY|O_NONBLOCK)) < 0) + return; + + msg[0] = '\007'; /* gettext crashes on \a */ + sprintf(msg+1, _("URGENT: broadcast message from %s:"), whom); + WRCRLF; + WR(msg); + WRCRLF; + + if (hours > 1) + sprintf(msg, _("System going down in %d hours %d minutes"), + hours, minutes); + else if (hours == 1) + sprintf(msg, _("System going down in 1 hour %d minutes"), + minutes); + else if (minutes > 1) + sprintf(msg, _("System going down in %d minutes\n"), + minutes); + else if (minutes == 1) + sprintf(msg, _("System going down in 1 minute\n")); + else + sprintf(msg, _("System going down IMMEDIATELY!\n")); + + WR(msg); + WRCRLF; + + sprintf(msg, _("\t... %s ...\n"), message); + WR(msg); + WRCRLF; + + close(fd); +} + +void +wall(void) +{ + /* 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(void) +{ + /* 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, 0644)) >= 0) { + write(fd, (char *)&ut, sizeof(ut)); + close(fd); + } +} + +void +swap_off(void) +{ + /* swapoff esp. swap FILES so the underlying partition can be + unmounted. It you don't have swapoff(1) or use mount to + add swapspace, this may not be necessary, but I guess it + won't hurt */ + + int pid; + int result; + int status; + + sync(); + if ((pid = fork()) < 0) { + my_puts(_("Cannot fork for swapoff. Shrug!")); + return; + } + if (!pid) { + execl("/sbin/swapoff", SWAPOFF_ARGS, NULL); + execl("/etc/swapoff", SWAPOFF_ARGS, NULL); + execl("/bin/swapoff", SWAPOFF_ARGS, NULL); + execlp("swapoff", SWAPOFF_ARGS, NULL); + my_puts(_("Cannot exec swapoff, " + "hoping umount will do the trick.")); + exit(EXIT_SUCCESS); + } + while ((result = wait(&status)) != -1 && result != pid) + ; +} + +void +unmount_disks(void) +{ + /* better to use umount directly because it may be smarter than us */ + + int pid; + int result; + int status; + + sync(); + if ((pid = fork()) < 0) { + my_puts(_("Cannot fork for umount, trying manually.")); + unmount_disks_ourselves(); + return; + } + if (!pid) { + execl(_PATH_UMOUNT, UMOUNT_ARGS, NULL); + + /* need my_printf instead of my_puts here */ + freopen(_PATH_CONSOLE, "w", stdout); + printf(_("Cannot exec %s, trying umount.\n"), _PATH_UMOUNT); + fflush(stdout); + + execlp("umount", UMOUNT_ARGS, NULL); + my_puts(_("Cannot exec umount, giving up on umount.")); + exit(EXIT_SUCCESS); + } + while ((result = wait(&status)) != -1 && result != pid) + ; + my_puts(_("Unmounting any remaining filesystems...")); + unmount_disks_ourselves(); +} + +void +unmount_disks_ourselves(void) +{ + /* unmount all disks */ + + FILE *mtab; + struct mntent *mnt; + char *mntlist[128]; + int i; + int n; + char *filesys; + + sync(); + if (!(mtab = setmntent(_PATH_MOUNTED, "r"))) { + my_puts("shutdown: Cannot open " _PATH_MOUNTED "."); + return; + } + n = 0; + while (n < 100 && (mnt = getmntent(mtab))) { + /* + * Neil Phillips: trying to unmount temporary / kernel + * filesystems is pointless and may cause error messages; + * /dev can be a ramfs managed by udev. + */ + if (strcmp(mnt->mnt_type, "devfs") == 0 || + strcmp(mnt->mnt_type, "proc") == 0 || + strcmp(mnt->mnt_type, "sysfs") == 0 || + strcmp(mnt->mnt_type, "ramfs") == 0 || + strcmp(mnt->mnt_type, "tmpfs") == 0 || + strcmp(mnt->mnt_type, "devpts") == 0) + continue; + mntlist[n++] = strdup(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(_("shutdown: Couldn't umount %s: %s\n"), + filesys, ERRSTRING); +#endif + } +} + +static void kill_mortals (int sig) +{ + int npids = 0; + int index = 0; + int pid; + struct stat statbuf; + DIR *dp; + struct dirent *de; + pid_t *pids = NULL; + char path[256]; + + if ( ( dp = opendir ("/proc") ) == NULL ) return; + while ( ( de = readdir (dp) ) != NULL ) + { + if ( !isdigit (de->d_name[0]) ) continue; + pid = atoi (de->d_name); + sprintf (path, "/proc/%d", pid); + if (stat (path, &statbuf) != 0) continue; + if (statbuf.st_uid < 100) continue; + if (index <= npids) + { + pids = realloc (pids, npids + 16384); + if (pids == NULL) return; + npids += 16384; + } + pids[index++] = pid; + } + fputs ("Sending SIGTERM to mortals...", stderr); + for (--index; index >= 0; --index) kill (pids[index], sig); + free (pids); + closedir (dp); +} /* End Function kill_mortals */ + +static void stop_finalprog (void) +{ + char *p1, *p2; + FILE *fp; + char line[256]; + + if ( ( fp = fopen (_PATH_INITTAB, "r") ) == NULL ) return; + while (fgets (line, 256, fp) != NULL) + { + pid_t pid; + + line[strlen (line) - 1] = '\0'; + p1 = line; + while ( isspace (*p1) ) ++p1; + if (strncmp (p1, "finalprog", 9) != 0) continue; + if ( ( p1 = strchr (p1 + 9, '=') ) == NULL ) continue; + for (++p1; isspace (*p1); ++p1); + if (*p1 == '\0') continue; + for (p2 = p1; !isspace (*p2); ++p2); + *p2 = '\0'; + switch ( pid = fork () ) + { + case 0: /* Child */ + execl (p1, p1, "stop", NULL); + break; + case -1: /* Error */ + break; + default: /* Parent */ + waitpid (pid, NULL, 0); + break; + } + fclose (fp); + return; + } + fclose (fp); +} /* End Function stop_finalprog */ + +static void syncwait (int timeval) +{ + static int do_wait = 0; + static int first_time = 1; + + sync (); + /* Kernel version 1.3.20 and after are supposed to wait automatically */ + if (first_time) + { + struct utsname uts; + + first_time = 0; + uname (&uts); + if (uts.release[0] < '2') do_wait = 1; + } + if (do_wait) sleep (timeval); +} /* End Function syncwait */ |