/* * shutdown.c Shut the system down. * * Usage: shutdown [-krhfnc] time [warning message] * -k: don't really shutdown, only warn. * -r: reboot after shutdown. * -h: halt after shutdown. * -f: do a 'fast' reboot (skip fsck). * -F: Force fsck on reboot. * -n: do not go through init but do it ourselves. * -c: cancel an already running shutdown. * -t secs: delay between SIGTERM and SIGKILL for init. * * Author: Miquel van Smoorenburg, miquels@cistron.nl * * Version: @(#)shutdown 2.86-1 31-Jul-2004 miquels@cistron.nl * * This file is part of the sysvinit suite, * Copyright (C) 1991-2004 Miquel van Smoorenburg. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "paths.h" #include "reboot.h" #include "initreq.h" char *Version = "@(#) shutdown 2.86-1 31-Jul-2004 miquels@cistron.nl"; #define MESSAGELEN 256 int dontshut = 0; /* Don't shutdown, only warn */ char down_level[2]; /* What runlevel to go to. */ int dosync = 1; /* Sync before reboot or halt */ int fastboot = 0; /* Do a 'fast' reboot */ int forcefsck = 0; /* Force fsck on reboot */ char message[MESSAGELEN]; /* Warning message */ char *sltime = 0; /* Sleep time */ char newstate[64]; /* What are we gonna do */ int doself = 0; /* Don't use init */ int got_alrm = 0; char *clean_env[] = { "HOME=/", "PATH=/bin:/usr/bin:/sbin:/usr/sbin", "TERM=dumb", NULL, }; /* From "wall.c" */ extern void wall(char *, int, int); /* From "utmp.c" */ extern void write_wtmp(char *user, char *id, int pid, int type, char *line); /* * Sleep without being interrupted. */ void hardsleep(int secs) { struct timespec ts, rem; ts.tv_sec = secs; ts.tv_nsec = 0; while(nanosleep(&ts, &rem) < 0 && errno == EINTR) ts = rem; } /* * Break off an already running shutdown. */ void stopit(int sig) { unlink(NOLOGIN); unlink(FASTBOOT); unlink(FORCEFSCK); unlink(SDPID); printf("\r\nShutdown cancelled.\r\n"); exit(0); } /* * Show usage message. */ void usage(void) { fprintf(stderr, "Usage:\t shutdown [-akrhHPfnc] [-t secs] time [warning message]\n" "\t\t -a: use /etc/shutdown.allow\n" "\t\t -k: don't really shutdown, only warn.\n" "\t\t -r: reboot after shutdown.\n" "\t\t -h: halt after shutdown.\n" "\t\t -P: halt action is to turn off power.\n" "\t\t -H: halt action is to just halt.\n" "\t\t -f: do a 'fast' reboot (skip fsck).\n" "\t\t -F: Force fsck on reboot.\n" "\t\t -n: do not go through \"init\" but go down real fast.\n" "\t\t -c: cancel a running shutdown.\n" "\t\t -t secs: delay between warning and kill signal.\n" "\t\t ** the \"time\" argument is mandatory! (try \"now\") **\n"); exit(1); } void alrm_handler(int sig) { got_alrm = sig; } /* * Set environment variables in the init process. */ int init_setenv(char *name, char *value) { struct init_request request; struct sigaction sa; int fd; int nl, vl; memset(&request, 0, sizeof(request)); request.magic = INIT_MAGIC; request.cmd = INIT_CMD_SETENV; nl = strlen(name); vl = value ? strlen(value) : 0; if (nl + vl + 3 >= sizeof(request.i.data)) return -1; memcpy(request.i.data, name, nl); if (value) { request.i.data[nl] = '='; memcpy(request.i.data + nl + 1, value, vl); } /* * Open the fifo and write the command. * Make sure we don't hang on opening /dev/initctl */ memset(&sa, 0, sizeof(sa)); sa.sa_handler = alrm_handler; sigaction(SIGALRM, &sa, NULL); got_alrm = 0; alarm(3); if ((fd = open(INIT_FIFO, O_WRONLY)) >= 0 && write(fd, &request, sizeof(request)) == sizeof(request)) { close(fd); alarm(0); return 0; } fprintf(stderr, "shutdown: "); if (got_alrm) { fprintf(stderr, "timeout opening/writing control channel %s\n", INIT_FIFO); } else { perror(INIT_FIFO); } return -1; } /* * Tell everyone the system is going down in 'mins' minutes. */ void warn(int mins) { char buf[MESSAGELEN + sizeof(newstate)]; int len; buf[0] = 0; strncat(buf, message, sizeof(buf) - 1); len = strlen(buf); if (mins == 0) snprintf(buf + len, sizeof(buf) - len, "\rThe system is going down %s NOW!\r\n", newstate); else snprintf(buf + len, sizeof(buf) - len, "\rThe system is going DOWN %s in %d minute%s!\r\n", newstate, mins, mins == 1 ? "" : "s"); wall(buf, 1, 0); } /* * Create the /etc/nologin file. */ void donologin(int min) { FILE *fp; time_t t; time(&t); t += 60 * min; if ((fp = fopen(NOLOGIN, "w")) != NULL) { fprintf(fp, "\rThe system is going down on %s\r\n", ctime(&t)); if (message[0]) fputs(message, fp); fclose(fp); } } /* * Spawn an external program. */ int spawn(int noerr, char *prog, ...) { va_list ap; pid_t pid, rc; int i; char *argv[8]; i = 0; while ((pid = fork()) < 0 && i < 10) { perror("fork"); sleep(5); i++; } if (pid < 0) return -1; if (pid > 0) { while((rc = wait(&i)) != pid) if (rc < 0 && errno == ECHILD) break; return (rc == pid) ? WEXITSTATUS(i) : -1; } if (noerr) fclose(stderr); argv[0] = prog; va_start(ap, prog); for (i = 1; i < 7 && (argv[i] = va_arg(ap, char *)) != NULL; i++) ; argv[i] = NULL; va_end(ap); chdir("/"); environ = clean_env; execvp(argv[0], argv); perror(argv[0]); exit(1); /*NOTREACHED*/ return 0; } /* * Kill all processes, call /etc/init.d/halt (if present) */ void fastdown() { int do_halt = (down_level[0] == '0'); int i; #if 0 char cmd[128]; char *script; /* * Currently, the halt script is either init.d/halt OR rc.d/rc.0, * likewise for the reboot script. Test for the presence * of either. */ if (do_halt) { if (access(HALTSCRIPT1, X_OK) == 0) script = HALTSCRIPT1; else script = HALTSCRIPT2; } else { if (access(REBOOTSCRIPT1, X_OK) == 0) script = REBOOTSCRIPT1; else script = REBOOTSCRIPT2; } #endif /* First close all files. */ for(i = 0; i < 3; i++) if (!isatty(i)) { close(i); open("/dev/null", O_RDWR); } for(i = 3; i < 20; i++) close(i); close(255); /* First idle init. */ if (kill(1, SIGTSTP) < 0) { fprintf(stderr, "shutdown: can't idle init.\r\n"); exit(1); } /* Kill all processes. */ fprintf(stderr, "shutdown: sending all processes the TERM signal...\r\n"); kill(-1, SIGTERM); sleep(sltime ? atoi(sltime) : 3); fprintf(stderr, "shutdown: sending all processes the KILL signal.\r\n"); (void) kill(-1, SIGKILL); #if 0 /* See if we can run /etc/init.d/halt */ if (access(script, X_OK) == 0) { spawn(1, cmd, "fast", NULL); fprintf(stderr, "shutdown: %s returned - falling back " "on default routines\r\n", script); } #endif /* script failed or not present: do it ourself. */ sleep(1); /* Give init the chance to collect zombies. */ /* Record the fact that we're going down */ write_wtmp("shutdown", "~~", 0, RUN_LVL, "~~"); /* This is for those who have quota installed. */ spawn(1, "accton", NULL); spawn(1, "quotaoff", "-a", NULL); sync(); fprintf(stderr, "shutdown: turning off swap\r\n"); spawn(0, "swapoff", "-a", NULL); fprintf(stderr, "shutdown: unmounting all file systems\r\n"); spawn(0, "umount", "-a", NULL); /* We're done, halt or reboot now. */ if (do_halt) { fprintf(stderr, "The system is halted. Press CTRL-ALT-DEL " "or turn off power\r\n"); init_reboot(BMAGIC_HALT); exit(0); } fprintf(stderr, "Please stand by while rebooting the system.\r\n"); init_reboot(BMAGIC_REBOOT); exit(0); } /* * Go to runlevel 0, 1 or 6. */ void shutdown(char *halttype) { char *args[8]; int argp = 0; int do_halt = (down_level[0] == '0'); /* Warn for the last time */ warn(0); if (dontshut) { hardsleep(1); stopit(0); } openlog("shutdown", LOG_PID, LOG_USER); if (do_halt) syslog(LOG_NOTICE, "shutting down for system halt"); else syslog(LOG_NOTICE, "shutting down for system reboot"); closelog(); /* See if we have to do it ourself. */ if (doself) fastdown(); /* Create the arguments for init. */ args[argp++] = INIT; if (sltime) { args[argp++] = "-t"; args[argp++] = sltime; } args[argp++] = down_level; args[argp] = (char *)NULL; unlink(SDPID); unlink(NOLOGIN); /* Now execute init to change runlevel. */ sync(); init_setenv("INIT_HALT", halttype); execv(INIT, args); /* Oops - failed. */ fprintf(stderr, "\rshutdown: cannot execute %s\r\n", INIT); unlink(FASTBOOT); unlink(FORCEFSCK); init_setenv("INIT_HALT", NULL); openlog("shutdown", LOG_PID, LOG_USER); syslog(LOG_NOTICE, "shutdown failed"); closelog(); exit(1); } /* * returns if a warning is to be sent for wt */ static int needwarning(int wt) { int ret; if (wt < 10) ret = 1; else if (wt < 60) ret = (wt % 15 == 0); else if (wt < 180) ret = (wt % 30 == 0); else ret = (wt % 60 == 0); return ret; } /* * Main program. * Process the options and do the final countdown. */ int main(int argc, char **argv) { FILE *fp; extern int getopt(); extern int optind; struct sigaction sa; struct tm *lt; struct stat st; struct utmp *ut; time_t t; uid_t realuid; char *halttype; char *downusers[32]; char buf[128]; char term[UT_LINESIZE + 6]; char *sp; char *when = NULL; int c, i, wt; int hours, mins; int didnolog = 0; int cancel = 0; int useacl = 0; int pid = 0; int user_ok = 0; /* We can be installed setuid root (executable for a special group) */ realuid = getuid(); setuid(geteuid()); if (getuid() != 0) { fprintf(stderr, "shutdown: you must be root to do that!\n"); exit(1); } strcpy(down_level, "1"); halttype = NULL; /* Process the options. */ while((c = getopt(argc, argv, "HPacqkrhnfFyt:g:i:")) != EOF) { switch(c) { case 'H': halttype = "HALT"; break; case 'P': halttype = "POWERDOWN"; break; case 'a': /* Access control. */ useacl = 1; break; case 'c': /* Cancel an already running shutdown. */ cancel = 1; break; case 'k': /* Don't really shutdown, only warn.*/ dontshut = 1; break; case 'r': /* Automatic reboot */ down_level[0] = '6'; break; case 'h': /* Halt after shutdown */ down_level[0] = '0'; break; case 'f': /* Don't perform fsck after next boot */ fastboot = 1; break; case 'F': /* Force fsck after next boot */ forcefsck = 1; break; case 'n': /* Don't switch runlevels. */ doself = 1; break; case 't': /* Delay between TERM and KILL */ sltime = optarg; break; case 'y': /* Ignored for sysV compatibility */ break; case 'g': /* sysv style to specify time. */ when = optarg; break; case 'i': /* Level to go to. */ if (!strchr("0156aAbBcCsS", optarg[0])) { fprintf(stderr, "shutdown: `%s': bad runlevel\n", optarg); exit(1); } down_level[0] = optarg[0]; break; default: usage(); break; } } if (NULL != halttype && down_level[0] != '0') { fprintf(stderr, "shutdown: -H and -P flags can only be used along with -h flag.\n"); usage(); exit(1); } /* Do we need to use the shutdown.allow file ? */ if (useacl && (fp = fopen(SDALLOW, "r")) != NULL) { /* Read /etc/shutdown.allow. */ i = 0; while(fgets(buf, 128, fp)) { if (buf[0] == '#' || buf[0] == '\n') continue; if (i > 31) continue; for(sp = buf; *sp; sp++) if (*sp == '\n') *sp = 0; downusers[i++] = strdup(buf); } if (i < 32) downusers[i] = 0; fclose(fp); /* Now walk through /var/run/utmp to find logged in users. */ while(!user_ok && (ut = getutent()) != NULL) { /* See if this is a user process on a VC. */ if (ut->ut_type != USER_PROCESS) continue; sprintf(term, "/dev/%.*s", UT_LINESIZE, ut->ut_line); if (stat(term, &st) < 0) continue; #ifdef major /* glibc */ if (major(st.st_rdev) != 4 || minor(st.st_rdev) > 63) continue; #else if ((st.st_rdev & 0xFFC0) != 0x0400) continue; #endif /* Root is always OK. */ if (strcmp(ut->ut_user, "root") == 0) { user_ok++; break; } /* See if this is an allowed user. */ for(i = 0; i < 32 && downusers[i]; i++) if (!strncmp(downusers[i], ut->ut_user, UT_NAMESIZE)) { user_ok++; break; } } endutent(); /* See if user was allowed. */ if (!user_ok) { if ((fp = fopen(CONSOLE, "w")) != NULL) { fprintf(fp, "\rshutdown: no authorized users " "logged in.\r\n"); fclose(fp); } exit(1); } } /* Read pid of running shutdown from a file */ if ((fp = fopen(SDPID, "r")) != NULL) { fscanf(fp, "%d", &pid); fclose(fp); } /* Read remaining words, skip time if needed. */ message[0] = 0; for(c = optind + (!cancel && !when); c < argc; c++) { if (strlen(message) + strlen(argv[c]) + 4 > MESSAGELEN) break; strcat(message, argv[c]); strcat(message, " "); } if (message[0]) strcat(message, "\r\n"); /* See if we want to run or cancel. */ if (cancel) { if (pid <= 0) { fprintf(stderr, "shutdown: cannot find pid " "of running shutdown.\n"); exit(1); } init_setenv("INIT_HALT", NULL); if (kill(pid, SIGINT) < 0) { fprintf(stderr, "shutdown: not running.\n"); exit(1); } if (message[0]) wall(message, 1, 0); exit(0); } /* Check syntax. */ if (when == NULL) { if (optind == argc) usage(); when = argv[optind++]; } /* See if we are already running. */ if (pid > 0 && kill(pid, 0) == 0) { fprintf(stderr, "\rshutdown: already running.\r\n"); exit(1); } /* Extra check. */ if (doself && down_level[0] != '0' && down_level[0] != '6') { fprintf(stderr, "shutdown: can use \"-n\" for halt or reboot only.\r\n"); exit(1); } /* Tell users what we're gonna do. */ switch(down_level[0]) { case '0': strcpy(newstate, "for system halt"); break; case '6': strcpy(newstate, "for reboot"); break; case '1': strcpy(newstate, "to maintenance mode"); break; default: sprintf(newstate, "to runlevel %s", down_level); break; } /* Create a new PID file. */ unlink(SDPID); umask(022); if ((fp = fopen(SDPID, "w")) != NULL) { fprintf(fp, "%d\n", getpid()); fclose(fp); } else if (errno != EROFS) fprintf(stderr, "shutdown: warning: cannot open %s\n", SDPID); /* * Catch some common signals. */ signal(SIGQUIT, SIG_IGN); signal(SIGCHLD, SIG_IGN); signal(SIGHUP, SIG_IGN); signal(SIGTSTP, SIG_IGN); signal(SIGTTIN, SIG_IGN); signal(SIGTTOU, SIG_IGN); memset(&sa, 0, sizeof(sa)); sa.sa_handler = stopit; sigaction(SIGINT, &sa, NULL); /* Go to the root directory */ chdir("/"); if (fastboot) close(open(FASTBOOT, O_CREAT | O_RDWR, 0644)); if (forcefsck) close(open(FORCEFSCK, O_CREAT | O_RDWR, 0644)); /* Alias now and take care of old '+mins' notation. */ if (!strcmp(when, "now")) strcpy(when, "0"); if (when[0] == '+') when++; /* Decode shutdown time. */ for (sp = when; *sp; sp++) { if (*sp != ':' && (*sp < '0' || *sp > '9')) usage(); } if (strchr(when, ':') == NULL) { /* Time in minutes. */ wt = atoi(when); if (wt == 0 && when[0] != '0') usage(); } else { /* Time in hh:mm format. */ if (sscanf(when, "%d:%2d", &hours, &mins) != 2) usage(); if (hours > 23 || mins > 59) usage(); time(&t); lt = localtime(&t); wt = (60*hours + mins) - (60*lt->tm_hour + lt->tm_min); if (wt < 0) wt += 1440; } /* Shutdown NOW if time == 0 */ if (wt == 0) shutdown(halttype); /* Give warnings on regular intervals and finally shutdown. */ if (wt < 15 && !needwarning(wt)) warn(wt); while(wt) { if (wt <= 5 && !didnolog) { donologin(wt); didnolog++; } if (needwarning(wt)) warn(wt); hardsleep(60); wt--; } shutdown(halttype); return 0; /* Never happens */ }