freebsd-src/sbin/shutdown/shutdown.c
Warner Losh 0b8224d1cc Remove copyright strings ifdef'd out
We've ifdef'd out the copyright strings for some time now. Go ahead and
remove the ifdefs. Plus whatever other detritis was left over from other
recent removals. These copyright strings are present in the comments and
are largely from CSRG's attempt at adding their copyright to every
binary file (which modern interpretations of the license doesn't
require).

Sponsored by:		Netflix
2023-11-26 22:23:58 -07:00

591 lines
13 KiB
C

/*-
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 1988, 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/param.h>
#include <sys/boottrace.h>
#include <sys/resource.h>
#include <sys/syslog.h>
#include <sys/time.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <pwd.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef DEBUG
#undef _PATH_NOLOGIN
#define _PATH_NOLOGIN "./nologin"
#endif
#define H *60*60
#define M *60
#define S *1
#define NOLOG_TIME 5*60
static struct interval {
int timeleft, timetowait;
} tlist[] = {
{ 10 H, 5 H },
{ 5 H, 3 H },
{ 2 H, 1 H },
{ 1 H, 30 M },
{ 30 M, 10 M },
{ 20 M, 10 M },
{ 10 M, 5 M },
{ 5 M, 3 M },
{ 2 M, 1 M },
{ 1 M, 30 S },
{ 30 S, 30 S },
{ 0 , 0 }
};
#undef H
#undef M
#undef S
static time_t offset, shuttime;
static int docycle, dohalt, dopower, doreboot, killflg, mbuflen, oflag;
static char mbuf[BUFSIZ];
static const char *nosync, *whom;
static void badtime(void);
static void die_you_gravy_sucking_pig_dog(void);
static void finish(int);
static void getoffset(char *);
static void loop(void);
static void nolog(void);
static void timeout(int);
static void timewarn(int);
static void usage(const char *);
extern const char **environ;
int
main(int argc, char **argv)
{
char *p, *endp;
struct passwd *pw;
int arglen, ch, len, readstdin;
#ifndef DEBUG
if (geteuid())
errx(1, "NOT super-user");
#endif
nosync = NULL;
readstdin = 0;
/*
* Test for the special case where the utility is called as
* "poweroff", for which it runs 'shutdown -p now'.
*/
if ((p = strrchr(argv[0], '/')) == NULL)
p = argv[0];
else
++p;
if (strcmp(p, "poweroff") == 0) {
if (getopt(argc, argv, "") != -1)
usage((char *)NULL);
argc -= optind;
argv += optind;
if (argc != 0)
usage((char *)NULL);
dopower = 1;
offset = 0;
(void)time(&shuttime);
goto poweroff;
}
while ((ch = getopt(argc, argv, "-chknopr")) != -1)
switch (ch) {
case '-':
readstdin = 1;
break;
case 'c':
docycle = 1;
break;
case 'h':
dohalt = 1;
break;
case 'k':
killflg = 1;
break;
case 'n':
nosync = "-n";
break;
case 'o':
oflag = 1;
break;
case 'p':
dopower = 1;
break;
case 'r':
doreboot = 1;
break;
case '?':
default:
usage((char *)NULL);
}
argc -= optind;
argv += optind;
if (argc < 1)
usage((char *)NULL);
if (killflg + doreboot + dohalt + dopower + docycle > 1)
usage("incompatible switches -c, -h, -k, -p and -r");
if (oflag && !(dohalt || dopower || doreboot || docycle))
usage("-o requires -c, -h, -p or -r");
if (nosync != NULL && !oflag)
usage("-n requires -o");
getoffset(*argv++);
poweroff:
if (*argv) {
for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) {
arglen = strlen(*argv);
if ((len -= arglen) <= 2)
break;
if (p != mbuf)
*p++ = ' ';
memmove(p, *argv, arglen);
p += arglen;
}
*p = '\n';
*++p = '\0';
}
if (readstdin) {
p = mbuf;
endp = mbuf + sizeof(mbuf) - 2;
for (;;) {
if (!fgets(p, endp - p + 1, stdin))
break;
for (; *p && p < endp; ++p);
if (p == endp) {
*p = '\n';
*++p = '\0';
break;
}
}
}
mbuflen = strlen(mbuf);
if (offset) {
BOOTTRACE("Shutdown at %s", ctime(&shuttime));
(void)printf("Shutdown at %.24s.\n", ctime(&shuttime));
} else {
BOOTTRACE("Shutdown NOW!");
(void)printf("Shutdown NOW!\n");
}
if (!(whom = getlogin()))
whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
#ifdef DEBUG
(void)putc('\n', stdout);
#else
(void)setpriority(PRIO_PROCESS, 0, PRIO_MIN);
{
int forkpid;
forkpid = fork();
if (forkpid == -1)
err(1, "fork");
if (forkpid)
errx(0, "[pid %d]", forkpid);
}
setsid();
#endif
openlog("shutdown", LOG_CONS, LOG_AUTH);
loop();
return(0);
}
static void
loop(void)
{
struct interval *tp;
u_int sltime;
int logged;
if (offset <= NOLOG_TIME) {
logged = 1;
nolog();
}
else
logged = 0;
tp = tlist;
if (tp->timeleft < offset)
(void)sleep((u_int)(offset - tp->timeleft));
else {
while (tp->timeleft && offset < tp->timeleft)
++tp;
/*
* Warn now, if going to sleep more than a fifth of
* the next wait time.
*/
if ((sltime = offset - tp->timeleft)) {
if (sltime > (u_int)(tp->timetowait / 5))
timewarn(offset);
(void)sleep(sltime);
}
}
for (;; ++tp) {
timewarn(tp->timeleft);
if (!logged && tp->timeleft <= NOLOG_TIME) {
logged = 1;
nolog();
}
(void)sleep((u_int)tp->timetowait);
if (!tp->timeleft)
break;
}
die_you_gravy_sucking_pig_dog();
}
static jmp_buf alarmbuf;
static const char *restricted_environ[] = {
"PATH=" _PATH_STDPATH,
NULL
};
static void
timewarn(int timeleft)
{
static int first;
static char hostname[MAXHOSTNAMELEN + 1];
FILE *pf;
char wcmd[MAXPATHLEN + 4];
if (!first++)
(void)gethostname(hostname, sizeof(hostname));
/* undoc -n option to wall suppresses normal wall banner */
(void)snprintf(wcmd, sizeof(wcmd), "%s -n", _PATH_WALL);
environ = restricted_environ;
if (!(pf = popen(wcmd, "w"))) {
syslog(LOG_ERR, "shutdown: can't find %s: %m", _PATH_WALL);
return;
}
(void)fprintf(pf,
"\007*** %sSystem shutdown message from %s@%s ***\007\n",
timeleft ? "": "FINAL ", whom, hostname);
if (timeleft > 10*60)
(void)fprintf(pf, "System going down at %5.5s\n\n",
ctime(&shuttime) + 11);
else if (timeleft > 59)
(void)fprintf(pf, "System going down in %d minute%s\n\n",
timeleft / 60, (timeleft > 60) ? "s" : "");
else if (timeleft)
(void)fprintf(pf, "System going down in %s30 seconds\n\n",
(offset > 0 && offset < 30 ? "less than " : ""));
else
(void)fprintf(pf, "System going down IMMEDIATELY\n\n");
if (mbuflen)
(void)fwrite(mbuf, sizeof(*mbuf), mbuflen, pf);
/*
* play some games, just in case wall doesn't come back
* probably unnecessary, given that wall is careful.
*/
if (!setjmp(alarmbuf)) {
(void)signal(SIGALRM, timeout);
(void)alarm((u_int)30);
(void)pclose(pf);
(void)alarm((u_int)0);
(void)signal(SIGALRM, SIG_DFL);
}
}
static void
timeout(int signo __unused)
{
longjmp(alarmbuf, 1);
}
static void
die_you_gravy_sucking_pig_dog(void)
{
char *empty_environ[] = { NULL };
BOOTTRACE("%s by %s",
doreboot ? "reboot" : dohalt ? "halt" : dopower ? "power-down" :
docycle ? "power-cycle" : "shutdown", whom);
syslog(LOG_NOTICE, "%s by %s: %s",
doreboot ? "reboot" : dohalt ? "halt" : dopower ? "power-down" :
docycle ? "power-cycle" : "shutdown", whom, mbuf);
(void)printf("\r\nSystem shutdown time has arrived\007\007\r\n");
if (killflg) {
BOOTTRACE("fake shutdown...");
(void)printf("\rbut you'll have to do it yourself\r\n");
exit(0);
}
#ifdef DEBUG
if (doreboot)
(void)printf("reboot");
else if (docycle)
(void)printf("power-cycle");
else if (dohalt)
(void)printf("halt");
else if (dopower)
(void)printf("power-down");
if (nosync != NULL)
(void)printf(" no sync");
(void)printf("\nkill -HUP 1\n");
#else
if (!oflag) {
BOOTTRACE("signal to init(8)...");
(void)kill(1, doreboot ? SIGINT : /* reboot */
dohalt ? SIGUSR1 : /* halt */
dopower ? SIGUSR2 : /* power-down */
docycle ? SIGWINCH : /* power-cycle */
SIGTERM); /* single-user */
} else {
if (doreboot) {
BOOTTRACE("exec reboot(8) -l...");
execle(_PATH_REBOOT, "reboot", "-l", nosync,
(char *)NULL, empty_environ);
syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
_PATH_REBOOT);
warn(_PATH_REBOOT);
}
else if (dohalt) {
BOOTTRACE("exec halt(8) -l...");
execle(_PATH_HALT, "halt", "-l", nosync,
(char *)NULL, empty_environ);
syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
_PATH_HALT);
warn(_PATH_HALT);
}
else if (dopower) {
BOOTTRACE("exec halt(8) -l -p...");
execle(_PATH_HALT, "halt", "-l", "-p", nosync,
(char *)NULL, empty_environ);
syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
_PATH_HALT);
warn(_PATH_HALT);
}
else if (docycle) {
execle(_PATH_HALT, "halt", "-l", "-c", nosync,
(char *)NULL, empty_environ);
syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
_PATH_HALT);
warn(_PATH_HALT);
}
BOOTTRACE("SIGTERM to init(8)...");
(void)kill(1, SIGTERM); /* to single-user */
}
#endif
finish(0);
}
#define ATOI2(p) (p[0] - '0') * 10 + (p[1] - '0'); p += 2;
static void
getoffset(char *timearg)
{
struct tm *lt;
char *p;
time_t now;
int maybe_today, this_year;
char *timeunit;
(void)time(&now);
if (!strcasecmp(timearg, "now")) { /* now */
offset = 0;
shuttime = now;
return;
}
if (*timearg == '+') { /* +minutes */
if (!isdigit(*++timearg))
badtime();
errno = 0;
offset = strtol(timearg, &timeunit, 10);
if (offset < 0 || offset == LONG_MAX || errno != 0)
badtime();
if (timeunit[0] == '\0' || strcasecmp(timeunit, "m") == 0 ||
strcasecmp(timeunit, "min") == 0 ||
strcasecmp(timeunit, "mins") == 0) {
offset *= 60;
} else if (strcasecmp(timeunit, "h") == 0 ||
strcasecmp(timeunit, "hour") == 0 ||
strcasecmp(timeunit, "hours") == 0) {
offset *= 60 * 60;
} else if (strcasecmp(timeunit, "s") == 0 ||
strcasecmp(timeunit, "sec") == 0 ||
strcasecmp(timeunit, "secs") == 0) {
offset *= 1;
} else {
badtime();
}
shuttime = now + offset;
return;
}
/* handle hh:mm by getting rid of the colon */
for (p = timearg; *p; ++p)
if (!isascii(*p) || !isdigit(*p)) {
if (*p == ':' && strlen(p) == 3) {
p[0] = p[1];
p[1] = p[2];
p[2] = '\0';
}
else
badtime();
}
unsetenv("TZ"); /* OUR timezone */
lt = localtime(&now); /* current time val */
maybe_today = 1;
switch(strlen(timearg)) {
case 10:
this_year = lt->tm_year;
lt->tm_year = ATOI2(timearg);
/*
* check if the specified year is in the next century.
* allow for one year of user error as many people will
* enter n - 1 at the start of year n.
*/
if (lt->tm_year < (this_year % 100) - 1)
lt->tm_year += 100;
/* adjust for the year 2000 and beyond */
lt->tm_year += (this_year - (this_year % 100));
/* FALLTHROUGH */
case 8:
lt->tm_mon = ATOI2(timearg);
if (--lt->tm_mon < 0 || lt->tm_mon > 11)
badtime();
/* FALLTHROUGH */
case 6:
maybe_today = 0;
lt->tm_mday = ATOI2(timearg);
if (lt->tm_mday < 1 || lt->tm_mday > 31)
badtime();
/* FALLTHROUGH */
case 4:
lt->tm_hour = ATOI2(timearg);
if (lt->tm_hour < 0 || lt->tm_hour > 23)
badtime();
lt->tm_min = ATOI2(timearg);
if (lt->tm_min < 0 || lt->tm_min > 59)
badtime();
lt->tm_sec = 0;
if ((shuttime = mktime(lt)) == -1)
badtime();
if ((offset = shuttime - now) < 0) {
if (!maybe_today)
errx(1, "that time is already past.");
/*
* If the user only gave a time, assume that
* any time earlier than the current time
* was intended to be that time tomorrow.
*/
lt->tm_mday++;
if ((shuttime = mktime(lt)) == -1)
badtime();
if ((offset = shuttime - now) < 0) {
errx(1, "tomorrow is before today?");
}
}
break;
default:
badtime();
}
}
#define NOMSG "\n\nNO LOGINS: System going down at "
static void
nolog(void)
{
int logfd;
char *ct;
(void)unlink(_PATH_NOLOGIN); /* in case linked to another file */
(void)signal(SIGINT, finish);
(void)signal(SIGHUP, finish);
(void)signal(SIGQUIT, finish);
(void)signal(SIGTERM, finish);
if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC,
0664)) >= 0) {
(void)write(logfd, NOMSG, sizeof(NOMSG) - 1);
ct = ctime(&shuttime);
(void)write(logfd, ct + 11, 5);
(void)write(logfd, "\n\n", 2);
(void)write(logfd, mbuf, strlen(mbuf));
(void)close(logfd);
}
}
static void
finish(int signo __unused)
{
if (!killflg)
(void)unlink(_PATH_NOLOGIN);
exit(0);
}
static void
badtime(void)
{
errx(1, "bad time format");
}
static void
usage(const char *cp)
{
if (cp != NULL)
warnx("%s", cp);
(void)fprintf(stderr,
"usage: shutdown [-] [-c | -h | -p | -r | -k] [-o [-n]] time [warning-message ...]\n"
" poweroff\n");
exit(1);
}