]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/shutdown/shutdown.c
Remove spurious newline
[FreeBSD/FreeBSD.git] / sbin / shutdown / shutdown.c
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1988, 1990, 1993
5  *      The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31
32 #if 0
33 #ifndef lint
34 static const char copyright[] =
35 "@(#) Copyright (c) 1988, 1990, 1993\n\
36         The Regents of the University of California.  All rights reserved.\n";
37 #endif /* not lint */
38
39 #ifndef lint
40 static char sccsid[] = "@(#)shutdown.c  8.4 (Berkeley) 4/28/95";
41 #endif /* not lint */
42 #endif
43 #include <sys/cdefs.h>
44 __FBSDID("$FreeBSD$");
45
46 #include <sys/param.h>
47 #include <sys/time.h>
48 #include <sys/resource.h>
49 #include <sys/syslog.h>
50
51 #include <ctype.h>
52 #include <err.h>
53 #include <errno.h>
54 #include <fcntl.h>
55 #include <paths.h>
56 #include <pwd.h>
57 #include <setjmp.h>
58 #include <signal.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <unistd.h>
63
64 #ifdef DEBUG
65 #undef _PATH_NOLOGIN
66 #define _PATH_NOLOGIN   "./nologin"
67 #endif
68
69 #define H               *60*60
70 #define M               *60
71 #define S               *1
72 #define NOLOG_TIME      5*60
73 static struct interval {
74         int timeleft, timetowait;
75 } tlist[] = {
76         { 10 H,  5 H },
77         {  5 H,  3 H },
78         {  2 H,  1 H },
79         {  1 H, 30 M },
80         { 30 M, 10 M },
81         { 20 M, 10 M },
82         { 10 M,  5 M },
83         {  5 M,  3 M },
84         {  2 M,  1 M },
85         {  1 M, 30 S },
86         { 30 S, 30 S },
87         {  0  ,  0   }
88 };
89 #undef H
90 #undef M
91 #undef S
92
93 static time_t offset, shuttime;
94 static int docycle, dohalt, dopower, doreboot, killflg, mbuflen, oflag;
95 static char mbuf[BUFSIZ];
96 static const char *nosync, *whom;
97
98 static void badtime(void);
99 static void die_you_gravy_sucking_pig_dog(void);
100 static void finish(int);
101 static void getoffset(char *);
102 static void loop(void);
103 static void nolog(void);
104 static void timeout(int);
105 static void timewarn(int);
106 static void usage(const char *);
107
108 extern const char **environ;
109
110 int
111 main(int argc, char **argv)
112 {
113         char *p, *endp;
114         struct passwd *pw;
115         int arglen, ch, len, readstdin;
116
117 #ifndef DEBUG
118         if (geteuid())
119                 errx(1, "NOT super-user");
120 #endif
121
122         nosync = NULL;
123         readstdin = 0;
124
125         /*
126          * Test for the special case where the utility is called as
127          * "poweroff", for which it runs 'shutdown -p now'.
128          */
129         if ((p = strrchr(argv[0], '/')) == NULL)
130                 p = argv[0];
131         else
132                 ++p;
133         if (strcmp(p, "poweroff") == 0) {
134                 if (getopt(argc, argv, "") != -1)
135                         usage((char *)NULL);
136                 argc -= optind;
137                 argv += optind;
138                 if (argc != 0)
139                         usage((char *)NULL);
140                 dopower = 1;
141                 offset = 0;
142                 (void)time(&shuttime);
143                 goto poweroff;
144         }
145
146         while ((ch = getopt(argc, argv, "-chknopr")) != -1)
147                 switch (ch) {
148                 case '-':
149                         readstdin = 1;
150                         break;
151                 case 'c':
152                         docycle = 1;
153                         break;
154                 case 'h':
155                         dohalt = 1;
156                         break;
157                 case 'k':
158                         killflg = 1;
159                         break;
160                 case 'n':
161                         nosync = "-n";
162                         break;
163                 case 'o':
164                         oflag = 1;
165                         break;
166                 case 'p':
167                         dopower = 1;
168                         break;
169                 case 'r':
170                         doreboot = 1;
171                         break;
172                 case '?':
173                 default:
174                         usage((char *)NULL);
175                 }
176         argc -= optind;
177         argv += optind;
178
179         if (argc < 1)
180                 usage((char *)NULL);
181
182         if (killflg + doreboot + dohalt + dopower + docycle > 1)
183                 usage("incompatible switches -c, -h, -k, -p and -r");
184
185         if (oflag && !(dohalt || dopower || doreboot || docycle))
186                 usage("-o requires -c, -h, -p or -r");
187
188         if (nosync != NULL && !oflag)
189                 usage("-n requires -o");
190
191         getoffset(*argv++);
192
193 poweroff:
194         if (*argv) {
195                 for (p = mbuf, len = sizeof(mbuf); *argv; ++argv) {
196                         arglen = strlen(*argv);
197                         if ((len -= arglen) <= 2)
198                                 break;
199                         if (p != mbuf)
200                                 *p++ = ' ';
201                         memmove(p, *argv, arglen);
202                         p += arglen;
203                 }
204                 *p = '\n';
205                 *++p = '\0';
206         }
207
208         if (readstdin) {
209                 p = mbuf;
210                 endp = mbuf + sizeof(mbuf) - 2;
211                 for (;;) {
212                         if (!fgets(p, endp - p + 1, stdin))
213                                 break;
214                         for (; *p &&  p < endp; ++p);
215                         if (p == endp) {
216                                 *p = '\n';
217                                 *++p = '\0';
218                                 break;
219                         }
220                 }
221         }
222         mbuflen = strlen(mbuf);
223
224         if (offset)
225                 (void)printf("Shutdown at %.24s.\n", ctime(&shuttime));
226         else
227                 (void)printf("Shutdown NOW!\n");
228
229         if (!(whom = getlogin()))
230                 whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
231
232 #ifdef DEBUG
233         (void)putc('\n', stdout);
234 #else
235         (void)setpriority(PRIO_PROCESS, 0, PRIO_MIN);
236         {
237                 int forkpid;
238
239                 forkpid = fork();
240                 if (forkpid == -1)
241                         err(1, "fork");
242                 if (forkpid)
243                         errx(0, "[pid %d]", forkpid);
244         }
245         setsid();
246 #endif
247         openlog("shutdown", LOG_CONS, LOG_AUTH);
248         loop();
249         return(0);
250 }
251
252 static void
253 loop(void)
254 {
255         struct interval *tp;
256         u_int sltime;
257         int logged;
258
259         if (offset <= NOLOG_TIME) {
260                 logged = 1;
261                 nolog();
262         }
263         else
264                 logged = 0;
265         tp = tlist;
266         if (tp->timeleft < offset)
267                 (void)sleep((u_int)(offset - tp->timeleft));
268         else {
269                 while (tp->timeleft && offset < tp->timeleft)
270                         ++tp;
271                 /*
272                  * Warn now, if going to sleep more than a fifth of
273                  * the next wait time.
274                  */
275                 if ((sltime = offset - tp->timeleft)) {
276                         if (sltime > (u_int)(tp->timetowait / 5))
277                                 timewarn(offset);
278                         (void)sleep(sltime);
279                 }
280         }
281         for (;; ++tp) {
282                 timewarn(tp->timeleft);
283                 if (!logged && tp->timeleft <= NOLOG_TIME) {
284                         logged = 1;
285                         nolog();
286                 }
287                 (void)sleep((u_int)tp->timetowait);
288                 if (!tp->timeleft)
289                         break;
290         }
291         die_you_gravy_sucking_pig_dog();
292 }
293
294 static jmp_buf alarmbuf;
295
296 static const char *restricted_environ[] = {
297         "PATH=" _PATH_STDPATH,
298         NULL
299 };
300
301 static void
302 timewarn(int timeleft)
303 {
304         static int first;
305         static char hostname[MAXHOSTNAMELEN + 1];
306         FILE *pf;
307         char wcmd[MAXPATHLEN + 4];
308
309         if (!first++)
310                 (void)gethostname(hostname, sizeof(hostname));
311
312         /* undoc -n option to wall suppresses normal wall banner */
313         (void)snprintf(wcmd, sizeof(wcmd), "%s -n", _PATH_WALL);
314         environ = restricted_environ;
315         if (!(pf = popen(wcmd, "w"))) {
316                 syslog(LOG_ERR, "shutdown: can't find %s: %m", _PATH_WALL);
317                 return;
318         }
319
320         (void)fprintf(pf,
321             "\007*** %sSystem shutdown message from %s@%s ***\007\n",
322             timeleft ? "": "FINAL ", whom, hostname);
323
324         if (timeleft > 10*60)
325                 (void)fprintf(pf, "System going down at %5.5s\n\n",
326                     ctime(&shuttime) + 11);
327         else if (timeleft > 59)
328                 (void)fprintf(pf, "System going down in %d minute%s\n\n",
329                     timeleft / 60, (timeleft > 60) ? "s" : "");
330         else if (timeleft)
331                 (void)fprintf(pf, "System going down in %s30 seconds\n\n",
332                     (offset > 0 && offset < 30 ? "less than " : ""));
333         else
334                 (void)fprintf(pf, "System going down IMMEDIATELY\n\n");
335
336         if (mbuflen)
337                 (void)fwrite(mbuf, sizeof(*mbuf), mbuflen, pf);
338
339         /*
340          * play some games, just in case wall doesn't come back
341          * probably unnecessary, given that wall is careful.
342          */
343         if (!setjmp(alarmbuf)) {
344                 (void)signal(SIGALRM, timeout);
345                 (void)alarm((u_int)30);
346                 (void)pclose(pf);
347                 (void)alarm((u_int)0);
348                 (void)signal(SIGALRM, SIG_DFL);
349         }
350 }
351
352 static void
353 timeout(int signo __unused)
354 {
355         longjmp(alarmbuf, 1);
356 }
357
358 static void
359 die_you_gravy_sucking_pig_dog(void)
360 {
361         char *empty_environ[] = { NULL };
362
363         syslog(LOG_NOTICE, "%s by %s: %s",
364             doreboot ? "reboot" : dohalt ? "halt" : dopower ? "power-down" :
365             docycle ? "power-cycle" : "shutdown", whom, mbuf);
366
367         (void)printf("\r\nSystem shutdown time has arrived\007\007\r\n");
368         if (killflg) {
369                 (void)printf("\rbut you'll have to do it yourself\r\n");
370                 exit(0);
371         }
372 #ifdef DEBUG
373         if (doreboot)
374                 (void)printf("reboot");
375         else if (docycle)
376                 (void)printf("power-cycle");
377         else if (dohalt)
378                 (void)printf("halt");
379         else if (dopower)
380                 (void)printf("power-down");
381         if (nosync != NULL)
382                 (void)printf(" no sync");
383         (void)printf("\nkill -HUP 1\n");
384 #else
385         if (!oflag) {
386                 (void)kill(1, doreboot ? SIGINT :       /* reboot */
387                               dohalt ? SIGUSR1 :        /* halt */
388                               dopower ? SIGUSR2 :       /* power-down */
389                               docycle ? SIGWINCH :      /* power-cycle */
390                               SIGTERM);                 /* single-user */
391         } else {
392                 if (doreboot) {
393                         execle(_PATH_REBOOT, "reboot", "-l", nosync, 
394                                 (char *)NULL, empty_environ);
395                         syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
396                                 _PATH_REBOOT);
397                         warn(_PATH_REBOOT);
398                 }
399                 else if (dohalt) {
400                         execle(_PATH_HALT, "halt", "-l", nosync,
401                                 (char *)NULL, empty_environ);
402                         syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
403                                 _PATH_HALT);
404                         warn(_PATH_HALT);
405                 }
406                 else if (dopower) {
407                         execle(_PATH_HALT, "halt", "-l", "-p", nosync,
408                                 (char *)NULL, empty_environ);
409                         syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
410                                 _PATH_HALT);
411                         warn(_PATH_HALT);
412                 }
413                 else if (docycle) {
414                         execle(_PATH_HALT, "halt", "-l", "-c", nosync,
415                                 (char *)NULL, empty_environ);
416                         syslog(LOG_ERR, "shutdown: can't exec %s: %m.",
417                                 _PATH_HALT);
418                         warn(_PATH_HALT);
419                 }
420                 (void)kill(1, SIGTERM);         /* to single-user */
421         }
422 #endif
423         finish(0);
424 }
425
426 #define ATOI2(p)        (p[0] - '0') * 10 + (p[1] - '0'); p += 2;
427
428 static void
429 getoffset(char *timearg)
430 {
431         struct tm *lt;
432         char *p;
433         time_t now;
434         int maybe_today, this_year;
435         char *timeunit;
436
437         (void)time(&now);
438
439         if (!strcasecmp(timearg, "now")) {              /* now */
440                 offset = 0;
441                 shuttime = now;
442                 return;
443         }
444
445         if (*timearg == '+') {                          /* +minutes */
446                 if (!isdigit(*++timearg))
447                         badtime();
448                 errno = 0;
449                 offset = strtol(timearg, &timeunit, 10);
450                 if (offset < 0 || offset == LONG_MAX || errno != 0)
451                         badtime();
452                 if (timeunit[0] == '\0' || strcasecmp(timeunit, "m") == 0 ||
453                     strcasecmp(timeunit, "min") == 0 ||
454                     strcasecmp(timeunit, "mins") == 0) {
455                         offset *= 60;
456                 } else if (strcasecmp(timeunit, "h") == 0 ||
457                     strcasecmp(timeunit, "hour") == 0 ||
458                     strcasecmp(timeunit, "hours") == 0) {
459                         offset *= 60 * 60;
460                 } else if (strcasecmp(timeunit, "s") == 0 ||
461                     strcasecmp(timeunit, "sec") == 0 ||
462                     strcasecmp(timeunit, "secs") == 0) {
463                         offset *= 1;
464                 } else {
465                         badtime();
466                 }
467                 shuttime = now + offset;
468                 return;
469         }
470
471         /* handle hh:mm by getting rid of the colon */
472         for (p = timearg; *p; ++p)
473                 if (!isascii(*p) || !isdigit(*p)) {
474                         if (*p == ':' && strlen(p) == 3) {
475                                 p[0] = p[1];
476                                 p[1] = p[2];
477                                 p[2] = '\0';
478                         }
479                         else
480                                 badtime();
481                 }
482
483         unsetenv("TZ");                                 /* OUR timezone */
484         lt = localtime(&now);                           /* current time val */
485         maybe_today = 1;
486
487         switch(strlen(timearg)) {
488         case 10:
489                 this_year = lt->tm_year;
490                 lt->tm_year = ATOI2(timearg);
491                 /*
492                  * check if the specified year is in the next century.
493                  * allow for one year of user error as many people will
494                  * enter n - 1 at the start of year n.
495                  */
496                 if (lt->tm_year < (this_year % 100) - 1)
497                         lt->tm_year += 100;
498                 /* adjust for the year 2000 and beyond */
499                 lt->tm_year += (this_year - (this_year % 100));
500                 /* FALLTHROUGH */
501         case 8:
502                 lt->tm_mon = ATOI2(timearg);
503                 if (--lt->tm_mon < 0 || lt->tm_mon > 11)
504                         badtime();
505                 /* FALLTHROUGH */
506         case 6:
507                 maybe_today = 0;
508                 lt->tm_mday = ATOI2(timearg);
509                 if (lt->tm_mday < 1 || lt->tm_mday > 31)
510                         badtime();
511                 /* FALLTHROUGH */
512         case 4:
513                 lt->tm_hour = ATOI2(timearg);
514                 if (lt->tm_hour < 0 || lt->tm_hour > 23)
515                         badtime();
516                 lt->tm_min = ATOI2(timearg);
517                 if (lt->tm_min < 0 || lt->tm_min > 59)
518                         badtime();
519                 lt->tm_sec = 0;
520                 if ((shuttime = mktime(lt)) == -1)
521                         badtime();
522
523                 if ((offset = shuttime - now) < 0) {
524                         if (!maybe_today)
525                                 errx(1, "that time is already past.");
526
527                         /*
528                          * If the user only gave a time, assume that
529                          * any time earlier than the current time
530                          * was intended to be that time tomorrow.
531                          */
532                         lt->tm_mday++;
533                         if ((shuttime = mktime(lt)) == -1)
534                                 badtime();
535                         if ((offset = shuttime - now) < 0) {
536                                 errx(1, "tomorrow is before today?");
537                         }
538                 }
539                 break;
540         default:
541                 badtime();
542         }
543 }
544
545 #define NOMSG   "\n\nNO LOGINS: System going down at "
546 static void
547 nolog(void)
548 {
549         int logfd;
550         char *ct;
551
552         (void)unlink(_PATH_NOLOGIN);    /* in case linked to another file */
553         (void)signal(SIGINT, finish);
554         (void)signal(SIGHUP, finish);
555         (void)signal(SIGQUIT, finish);
556         (void)signal(SIGTERM, finish);
557         if ((logfd = open(_PATH_NOLOGIN, O_WRONLY|O_CREAT|O_TRUNC,
558             0664)) >= 0) {
559                 (void)write(logfd, NOMSG, sizeof(NOMSG) - 1);
560                 ct = ctime(&shuttime);
561                 (void)write(logfd, ct + 11, 5);
562                 (void)write(logfd, "\n\n", 2);
563                 (void)write(logfd, mbuf, strlen(mbuf));
564                 (void)close(logfd);
565         }
566 }
567
568 static void
569 finish(int signo __unused)
570 {
571         if (!killflg)
572                 (void)unlink(_PATH_NOLOGIN);
573         exit(0);
574 }
575
576 static void
577 badtime(void)
578 {
579         errx(1, "bad time format");
580 }
581
582 static void
583 usage(const char *cp)
584 {
585         if (cp != NULL)
586                 warnx("%s", cp);
587         (void)fprintf(stderr,
588             "usage: shutdown [-] [-c | -h | -p | -r | -k] [-o [-n]] time [warning-message ...]\n"
589             "       poweroff\n");
590         exit(1);
591 }