Logo Search packages:      
Sourcecode: util-linux version File versions  Download package

shutdown.c

/* 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 "xstrncpy.h"
#include "nls.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)


void
usage(void)
{
      fprintf(stderr,
            _("Usage: shutdown [-h|-r] [-fqs] [now|hh:ss|+mins]\n"));
      exit(1);
}

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(1);
}

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)) {
            fprintf(stderr, _("%s: Only root can shut a system down.\n"),
                  argv[0]);
            exit(1);
      }
#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;
                              break;
                            
                        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) {
                        fprintf(stderr, _("That must be tomorrow, "
                                        "can't you wait till then?\n"));
                        exit(1);
                  }
            } 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 = index(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(0); /* 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(0);
      }
      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(0);
      }
      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_MTAB, "r"))) {
            my_puts("shutdown: Cannot open " _PATH_MTAB ".");
            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  */

Generated by  Doxygen 1.6.0   Back to index