/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* University Copyright- Copyright (c) 1982, 1986, 1988
* The Regents of the University of California
* All Rights Reserved
*
* University Acknowledgment- Portions of this document are derived from
* software developed by the University of California, Berkeley, and its
* contributors.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <ctype.h>
#include <setjmp.h>
#include <utmpx.h>
#include <pwd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/mount.h>
#include <rpcsvc/rwall.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <locale.h>
#include <sys/syslog.h>
#include <zone.h>
#include <signal.h>
/*
* /usr/etc/shutdown when [messages]
*
* allow super users to tell users and remind users
* of iminent shutdown of unix
* and shut it down automatically
* and even reboot or halt the machine if they desire
*/
#define EPATH "PATH=/usr/ucb:/usr/bin:/usr/sbin:"
#define REBOOT "/usr/sbin/reboot"
#define HALT "/usr/sbin/halt"
#define MAXINTS 20
#define HOURS *3600
#define MINUTES *60
#define SECONDS
#define NLOG 600 /* no of bytes possible for message */
#define NOLOGTIME 5 MINUTES
#define IGNOREUSER "sleeper"
struct hostlist {
char *host;
struct hostlist *nxt;
} *hostlist;
char hostname[MAXHOSTNAMELEN];
char mbuf[BUFSIZ];
extern char *malloc();
extern char *ctime();
extern struct tm *localtime();
extern char *strcpy();
extern char *strncat();
extern off_t lseek();
struct utmpx *utmpx;
int sint;
int stogo;
char tpath[] = "/dev/";
int nlflag = 1; /* nolog yet to be done */
int killflg = 1;
int doreboot = 0;
int halt = 0;
int fast = 0;
char *nosync = NULL;
char nosyncflag[] = "-n";
char term[sizeof tpath + sizeof (utmpx->ut_line)];
char tbuf[BUFSIZ];
char nolog1[] = "\n\nNO LOGINS: System going down at %5.5s\n\n";
char mesg[NLOG+1];
#ifdef DEBUG
char fastboot[] = "fastboot";
#else
char fastboot[] = "/fastboot";
#endif
char nologin[] = "/etc/nologin";
time_t nowtime;
jmp_buf alarmbuf;
struct interval {
int stogo;
int sint;
} interval[] = {
4 HOURS, 1 HOURS,
2 HOURS, 30 MINUTES,
1 HOURS, 15 MINUTES,
30 MINUTES, 10 MINUTES,
15 MINUTES, 5 MINUTES,
10 MINUTES, 5 MINUTES,
5 MINUTES, 3 MINUTES,
2 MINUTES, 1 MINUTES,
1 MINUTES, 30 SECONDS,
0 SECONDS, 0 SECONDS
};
char *msg1 = "shutdown: '%c' - unknown flag\n";
char *msg2 = "Usage: shutdown [ -krhfn ] shutdowntime [ message ]\n";
char *msg3 = "Usage: shutdown [ -krhfn ] shutdowntime [ message ]";
char *msg4 = "Usage: shutdown [ -krhfn ] shutdowntime [ message ]\n";
char *msg5 = "Usage: shutdown [ -krhfn ] shutdowntime [ message ]";
char *msg6 = "\n\007\007System shutdown time has arrived\007\007\n";
char *msg7 = "but you'll have to do it yourself\n";
char *msg8 = "but you'll have to do it yourself";
char *msg9 = "-l (without fsck's)\n";
char *msg10 = "-l %s\n";
char *msg11 = " (without fsck's)\n";
char *msg12 = "That must be tomorrow\nCan't you wait till then?\n";
char *msg13 = "That must be tomorrow";
char *msg14 = "Can't you wait till then?";
char *msg15 = "\007\007\t*** %sSystem shutdown message from %s@%s ***\r\n\n";
char *msg16 = "System going down at %5.5s\r\n";
char *msg17 = "System going down in %d minute%s\r\n";
char *msg18 = "System going down in %d second%s\r\n";
char *msg19 = "System going down IMMEDIATELY\r\n";
char *msg20 = "Can't get PID for init\n";
char *shutter, *getlogin();
static void timeout(void);
static void gethostlist(void);
static void finish(char *, char *, int);
static void nolog(time_t);
static void rprintf(char *, char *);
static void rwarn(char *, time_t, time_t, char *, int);
static void doitfast(void);
static void warn(FILE *, time_t, time_t, char *, int);
static time_t getsdt(char *);
pid_t
get_initpid(void)
{
pid_t init_pid;
if (zone_getattr(getzoneid(), ZONE_ATTR_INITPID, &init_pid,
sizeof (init_pid)) != sizeof (init_pid)) {
(void) fprintf(stderr, gettext(msg20));
exit(1);
}
return (init_pid);
}
int
main(int argc, char **argv)
{
int i;
char *f;
char *ts;
time_t sdt;
int h, m;
int first;
void finish_sig();
FILE *termf;
struct passwd *pw, *getpwuid();
extern char *strcat();
extern uid_t geteuid();
struct hostlist *hl;
char *shutdown_program;
char *shutdown_action;
int fd;
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
audit_shutdown_setup(argc, argv);
shutter = getlogin();
if (shutter == 0 && (pw = getpwuid(getuid())))
shutter = pw->pw_name;
if (shutter == 0)
shutter = "???";
(void) gethostname(hostname, sizeof (hostname));
openlog("shutdown", 0, LOG_AUTH);
argc--, argv++;
while (argc > 0 && (f = argv[0], *f++ == '-')) {
while (i = *f++) {
switch (i) {
case 'k':
killflg = 0;
continue;
case 'n':
nosync = nosyncflag;
continue;
case 'f':
fast = 1;
continue;
case 'r':
doreboot = 1;
continue;
case 'h':
halt = 1;
continue;
default:
(void) fprintf(stderr, gettext(msg1),
i);
(void) fprintf(stderr, gettext(msg2));
finish(gettext(msg3), "", 1);
}
}
argc--, argv++;
}
if (argc < 1) {
(void) fprintf(stderr, gettext(msg4));
finish(gettext(msg5), "", 1);
}
if (doreboot && halt) {
(void) fprintf(stderr,
gettext("shutdown: Incompatible switches '-r' & '-h'\n"));
finish(gettext("shutdown: Incompatible switches '-r' & '-h'"),
"", 1);
}
if (fast && (nosync == nosyncflag)) {
(void) fprintf(stderr,
gettext("shutdown: Incompatible switches '-f' & '-n'\n"));
finish(gettext("shutdown: Incompatible switches '-f' & '-n'"),
"", 1);
}
if (geteuid()) {
(void) fprintf(stderr, gettext("shutdown: NOT super-user\n"));
finish(gettext("shutdown: NOT super-user"), "", 1);
}
gethostlist();
nowtime = time((time_t *)NULL);
sdt = getsdt(argv[0]);
argc--, argv++;
mesg[0] = '\0';
i = 0;
while (argc-- > 0) {
if (i + strlen(*argv) > NLOG)
break; /* no more room for the message */
i += strlen(*argv) + 1;
(void) strcat(mesg, *argv++);
(void) strcat(mesg, " ");
}
if (i != 0)
mesg[i - 1] = '\0'; /* remove trailing blank */
m = ((stogo = sdt - nowtime) + 30)/60;
h = m/60;
m %= 60;
ts = ctime(&sdt);
(void) printf(gettext("Shutdown at %5.5s (in "), ts+11);
if (h > 0)
(void) printf("%d hour%s ", h, h != 1 ? "s" : "");
(void) printf("%d minute%s) ", m, m != 1 ? "s" : "");
#ifndef DEBUG
(void) signal(SIGHUP, SIG_IGN);
(void) signal(SIGQUIT, SIG_IGN);
(void) signal(SIGINT, SIG_IGN);
#endif
(void) signal(SIGTTOU, SIG_IGN);
(void) signal(SIGINT, finish_sig);
(void) signal(SIGALRM, (void(*)())timeout);
(void) setpriority(PRIO_PROCESS, 0, PRIO_MIN);
(void) fflush(stdout);
#ifndef DEBUG
if (i = fork()) {
(void) printf(gettext("[pid %d]\n"), i);
exit(0);
}
#else
(void) putc('\n', stdout);
#endif
sint = 1 HOURS;
f = "";
first = 1;
if (doreboot) {
shutdown_program = REBOOT;
shutdown_action = "reboot";
} else if (halt) {
shutdown_program = HALT;
shutdown_action = "halt";
} else {
shutdown_program = NULL;
shutdown_action = "shutdown";
}
for (;;) {
for (i = 0; stogo <= interval[i].stogo && interval[i].sint; i++)
sint = interval[i].sint;
if (stogo > 0 && (stogo-sint) < interval[i].stogo)
sint = stogo - interval[i].stogo;
if (stogo <= NOLOGTIME && nlflag) {
nlflag = 0;
nolog(sdt);
}
if (sint >= stogo || sint == 0)
f = "FINAL ";
nowtime = time((time_t *)NULL);
setutxent();
while ((utmpx = getutxent()) != NULL) {
if (utmpx->ut_name[0] &&
strncmp(utmpx->ut_name, IGNOREUSER,
sizeof (utmpx->ut_name))) {
/*
* don't write to pty's unless they're rlogin sessions
*/
if (utmpx->ut_type != USER_PROCESS &&
utmpx->ut_user[0] != '\0')
continue;
if (setjmp(alarmbuf))
continue;
(void) strcpy(term, tpath);
(void) strncat(term, utmpx->ut_line,
sizeof (utmpx->ut_line));
(void) alarm(5);
/* check if device is really a tty */
if ((fd = open(term, O_WRONLY|O_NOCTTY)) == -1) {
fprintf(stderr, gettext("Cannot open %s.\n"),
term);
(void) alarm(0);
continue;
} else {
if (!isatty(fd)) {
fprintf(stderr,
gettext("%.*s in utmpx is not a tty\n"),
sizeof (utmpx->ut_line), utmpx->ut_line);
syslog(LOG_CRIT, "%.*s in utmpx is not "
"a tty\n", sizeof (utmpx->ut_line),
utmpx->ut_line);
close(fd);
(void) alarm(0);
continue;
}
}
close(fd);
#ifdef DEBUG
if ((termf = stdout) != NULL)
#else
if ((termf = fopen(term, "w")) != NULL)
#endif
{
(void) alarm(0);
setbuf(termf, tbuf);
(void) fprintf(termf, "\n\r\n");
warn(termf, sdt, nowtime, f, first);
(void) alarm(5);
#ifdef DEBUG
(void) fflush(termf);
#else
(void) fclose(termf);
#endif
(void) alarm(0);
}
}
} /* while */
endutxent();
for (hl = hostlist; hl != NULL; hl = hl->nxt)
rwarn(hl->host, sdt, nowtime, f, first);
if (stogo <= 0) {
(void) printf(gettext(msg6));
if (*mesg)
syslog(LOG_CRIT, "%s by %s: %s",
shutdown_action, shutter, mesg);
else
syslog(LOG_CRIT, "%s by %s",
shutdown_action, shutter);
sleep(2);
(void) unlink(nologin);
if (!killflg) {
(void) printf(gettext(msg7));
finish(gettext(msg8), "", 0);
}
if (fast)
doitfast();
#ifndef DEBUG
(void) putenv(EPATH);
if (shutdown_program != NULL) {
audit_shutdown_success();
execlp(shutdown_program, shutdown_program,
"-l", nosync, (char *)0);
} else {
if (geteuid() == 0) {
audit_shutdown_success();
sleep(5);
}
if (getzoneid() == GLOBAL_ZONEID) {
(void) system(
"/sbin/bootadm -a update_all");
}
(void) kill(get_initpid(), SIGINT); /* sync */
(void) kill(get_initpid(), SIGINT); /* sync */
sleep(20);
}
#else
if (shutdown_program) {
(void) printf("%s ", shutdown_program);
if (fast)
(void) printf(gettext(msg9));
else if (nosync != NULL)
(void) printf(gettext(msg10), nosync);
else
(void) printf(gettext("-l\n"));
} else {
(void) printf("/sbin/bootadm -a update_all");
(void) printf("kill -INT 1");
if (fast)
(void) printf(gettext(msg11));
else
(void) printf("\n");
}
#endif
finish("", "", 0);
}
stogo = sdt - time((time_t *)NULL);
if (stogo > 0 && sint > 0)
sleep((unsigned)(sint < stogo ? sint : stogo));
stogo -= sint;
first = 0;
}
/* NOTREACHED */
}
static time_t
getsdt(char *s)
{
time_t t, t1, tim;
char c;
struct tm *lt;
int c_count;
if (strcmp(s, "now") == 0)
return (nowtime);
if (*s == '+') {
++s;
t = 0;
for (c_count = 1; ; c_count++) {
c = *s++;
if (!isdigit(c)) {
if (c_count == 1) {
goto badform;
} else {
break;
}
}
t = t * 10 + c - '0';
}
if (t <= 0)
t = 5;
t *= 60;
tim = time((time_t *)NULL) + t;
return (tim);
}
t = 0;
while (strlen(s) > 2 && isdigit(*s))
t = t * 10 + *s++ - '0';
if (*s == ':')
s++;
if (t > 23)
goto badform;
tim = t*60;
t = 0;
while (isdigit(*s))
t = t * 10 + *s++ - '0';
if (t > 59)
goto badform;
tim += t;
tim *= 60;
t1 = time((time_t *)NULL);
lt = localtime(&t1);
t = lt->tm_sec + lt->tm_min*60 + lt->tm_hour*3600;
if (tim < t || tim >= (24*3600)) {
/* before now or after midnight */
(void) printf(gettext(msg12));
finish(gettext(msg13), gettext(msg14), 0);
}
return (t1 + tim - t);
badform:
(void) printf(gettext("Bad time format\n"));
finish(gettext("Bad time format"), "", 0);
return (0);
/* NOTREACHED */
}
static void
warn(FILE *termf, time_t sdt, time_t now, char *type, int first)
{
char *ts;
time_t delay = sdt - now;
if (delay > 8)
while (delay % 5)
delay++;
(void) fprintf(termf, gettext(msg15), type, shutter, hostname);
ts = ctime(&sdt);
if (delay > 10 MINUTES)
(void) fprintf(termf, gettext(msg16), ts+11);
else if (delay > 95 SECONDS) {
(void) fprintf(termf, gettext(msg17), (delay+30)/60,
(delay+30)/60 != 1 ? "s" : "");
} else if (delay > 0) {
(void) fprintf(termf, gettext(msg18), delay,
delay != 1 ? "s" : "");
} else
(void) fprintf(termf, gettext(msg19));
if (first || sdt - now > 1 MINUTES) {
if (*mesg)
(void) fprintf(termf, "\t...%s\r\n", mesg);
}
}
static void
doitfast(void)
{
FILE *fastd;
if ((fastd = fopen(fastboot, "w")) != NULL) {
(void) putc('\n', fastd);
(void) fclose(fastd);
}
}
static void
rwarn(char *host, time_t sdt, time_t now, char *type, int first)
{
char *ts;
time_t delay = sdt - now;
char *bufp;
if (delay > 8)
while (delay % 5)
delay++;
(void) sprintf(mbuf,
"\007\007\t*** %sShutdown message for %s from %s@%s ***\r\n\n",
type, hostname, shutter, hostname);
ts = ctime(&sdt);
bufp = mbuf + strlen(mbuf);
if (delay > 10 MINUTES) {
(void) sprintf(bufp, "%s going down at %5.5s\r\n", hostname,
ts+11);
} else if (delay > 95 SECONDS) {
(void) sprintf(bufp, "%s going down in %d minute%s\r\n",
hostname, (delay+30)/60, (delay+30)/60 != 1 ? "s" : "");
} else if (delay > 0) {
(void) sprintf(bufp, "%s going down in %d second%s\r\n",
hostname, delay, delay != 1 ? "s" : "");
} else {
(void) sprintf(bufp, "%s going down IMMEDIATELY\r\n",
hostname);
}
bufp = mbuf + strlen(mbuf);
if (first || sdt - now > 1 MINUTES) {
if (*mesg)
(void) sprintf(bufp, "\t...%s\r\n", mesg);
}
rprintf(host, mbuf);
}
static void
rprintf(char *host, char *bufp)
{
int err;
#ifdef DEBUG
(void) fprintf(stderr, gettext("about to call %s\n"), host);
#endif
if (err = callrpcfast(host, (rpcprog_t)WALLPROG, (rpcvers_t)WALLVERS,
(rpcproc_t)WALLPROC_WALL, xdr_dirpath, (char *)&bufp, xdr_void,
(char *)NULL)) {
#ifdef DEBUG
(void) fprintf(stderr, gettext("couldn't make rpc call: "));
clnt_perrno(err);
(void) fprintf(stderr, "\n");
#endif
}
}
static void
nolog(time_t sdt)
{
FILE *nologf;
(void) unlink(nologin); /* in case linked to std file */
if ((nologf = fopen(nologin, "w")) != NULL) {
(void) fprintf(nologf, nolog1, (ctime(&sdt)) + 11);
if (*mesg)
(void) fprintf(nologf, "\t%s\n", mesg);
(void) fclose(nologf);
}
}
void
finish_sig(void)
{
finish("SIGINT", "", 1);
}
static void
finish(char *s1, char *s2, int exitcode)
{
(void) signal(SIGINT, SIG_IGN);
exit(exitcode);
}
static void
timeout(void)
{
(void) signal(SIGALRM, (void(*)())timeout);
longjmp(alarmbuf, 1);
}
static void
gethostlist(void)
{
int s;
struct mountbody *ml;
struct hostlist *hl;
struct sockaddr_in addr;
CLIENT *cl;
static struct timeval TIMEOUT = { 25, 0 };
/*
* check for portmapper
*/
get_myaddress(&addr);
s = socket(AF_INET, SOCK_STREAM, 0);
if (s < 0)
return;
if (connect(s, (struct sockaddr *)&addr, sizeof (addr)) < 0)
return;
(void) close(s);
/*
* First try tcp, then drop back to udp if
* tcp is unavailable (an old version of mountd perhaps)
* Using tcp is preferred because it can handle
* arbitrarily long export lists.
*/
cl = clnt_create(hostname, (ulong_t)MOUNTPROG, (ulong_t)MOUNTVERS,
"tcp");
if (cl == NULL) {
cl = clnt_create(hostname, (ulong_t)MOUNTPROG,
(ulong_t)MOUNTVERS, "udp");
if (cl == NULL) {
if (rpc_createerr.cf_stat != RPC_PROGNOTREGISTERED) {
clnt_pcreateerror("shutdown warning");
}
return;
}
}
ml = NULL;
if (clnt_call(cl, MOUNTPROC_DUMP,
xdr_void, 0, xdr_mountlist, (char *)&ml, TIMEOUT) != RPC_SUCCESS) {
clnt_perror(cl, "shutdown warning");
return;
}
for (; ml != NULL; ml = ml->ml_next) {
for (hl = hostlist; hl != NULL; hl = hl->nxt)
if (strcmp(ml->ml_hostname, hl->host) == 0)
goto again;
hl = (struct hostlist *)malloc(sizeof (struct hostlist));
hl->host = ml->ml_hostname;
hl->nxt = hostlist;
hostlist = hl;
again:;
}
}
/*
* Don't want to wait for usual portmapper timeout you get with
* callrpc or clnt_call, so use rmtcall instead. Use timeout
* of 8 secs, based on the per try timeout of 3 secs for rmtcall
*/
int
callrpcfast(char *host, rpcprog_t prognum, rpcprog_t versnum,
rpcprog_t procnum, xdrproc_t inproc, xdrproc_t outproc,
char *in, char *out)
{
struct sockaddr_in server_addr;
struct hostent *hp;
struct timeval rpctimeout;
rpcport_t port;
if ((hp = gethostbyname(host)) == NULL)
return ((int)RPC_UNKNOWNHOST);
bcopy(hp->h_addr, (char *)&server_addr.sin_addr, hp->h_length);
server_addr.sin_family = AF_INET;
server_addr.sin_port = 0;
rpctimeout.tv_sec = 8;
rpctimeout.tv_usec = 0;
return ((int)pmap_rmtcall(&server_addr, prognum, versnum, procnum,
inproc, in, outproc, out, rpctimeout, &port));
}