blob: a831c503d14accf641442dd714f497e211f5a8dc [file] [log] [blame]
Scott James Remnant47158482006-08-27 22:37:10 +01001/* upstart
2 *
Scott James Remnantbdcf3be2010-02-04 00:33:40 -08003 * Copyright © 2010 Canonical Ltd.
Scott James Remnant7d5b2ea2009-05-22 15:20:12 +02004 * Author: Scott James Remnant <scott@netsplit.com>.
Scott James Remnant47158482006-08-27 22:37:10 +01005 *
Scott James Remnant0c0c5a52009-06-23 10:29:35 +01006 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2, as
Scott James Remnant75123022009-05-22 13:27:56 +02008 * published by the Free Software Foundation.
Scott James Remnant47158482006-08-27 22:37:10 +01009 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
Scott James Remnant0c0c5a52009-06-23 10:29:35 +010015 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Scott James Remnant47158482006-08-27 22:37:10 +010018 */
19
20#ifdef HAVE_CONFIG_H
21# include <config.h>
22#endif /* HAVE_CONFIG_H */
23
24
Scott James Remnantf24e55d2009-07-08 19:58:37 +010025#include <dbus/dbus.h>
26
Scott James Remnant4a9245f2006-08-30 19:13:00 +010027#include <sys/types.h>
28#include <sys/stat.h>
Scott James Remnantd4cdaca2006-08-31 00:47:11 +010029#include <sys/param.h>
Scott James Remnant4a9245f2006-08-30 19:13:00 +010030
31#include <pwd.h>
Scott James Remnant4a9245f2006-08-30 19:13:00 +010032#include <time.h>
Scott James Remnantf24e55d2009-07-08 19:58:37 +010033#include <utmpx.h>
Scott James Remnant4a9245f2006-08-30 19:13:00 +010034#include <fcntl.h>
35#include <errno.h>
36#include <stdio.h>
37#include <limits.h>
38#include <signal.h>
39#include <stdlib.h>
40#include <string.h>
41#include <unistd.h>
42
Scott James Remnant47158482006-08-27 22:37:10 +010043#include <nih/macros.h>
Scott James Remnant4a9245f2006-08-30 19:13:00 +010044#include <nih/alloc.h>
45#include <nih/timer.h>
46#include <nih/string.h>
47#include <nih/signal.h>
Scott James Remnantda100272007-01-06 20:24:18 +000048#include <nih/io.h>
Scott James Remnant47158482006-08-27 22:37:10 +010049#include <nih/main.h>
50#include <nih/option.h>
51#include <nih/logging.h>
Scott James Remnant4a9245f2006-08-30 19:13:00 +010052#include <nih/error.h>
53
Scott James Remnantf24e55d2009-07-08 19:58:37 +010054#include <nih-dbus/dbus_error.h>
55#include <nih-dbus/errors.h>
56
57#include "sysv.h"
Scott James Remnant4a9245f2006-08-30 19:13:00 +010058
59
60/**
Scott James Remnant4a9245f2006-08-30 19:13:00 +010061 * ETC_NOLOGIN:
62 *
63 * File we write to to prevent logins.
64 **/
65#define ETC_NOLOGIN "/etc/nologin"
66
67/**
68 * DEV:
69 *
70 * Directory containing tty device nodes.
71 **/
72#define DEV "/dev"
73
Scott James Remnant7e6bdb32006-09-14 10:57:03 +010074/**
75 * DEV_INITCTL:
76 *
77 * System V init control socket.
78 **/
79#ifndef DEV_INITCTL
80#define DEV_INITCTL "/dev/initctl"
81#endif
82
Scott James Remnant4a9245f2006-08-30 19:13:00 +010083
Scott James Remnantf24e55d2009-07-08 19:58:37 +010084/* Prototypes for option functions */
85static int runlevel_option (NihOption *option, const char *arg);
86
Scott James Remnant4a9245f2006-08-30 19:13:00 +010087/* Prototypes for static functions */
Scott James Remnantd4cdaca2006-08-31 00:47:11 +010088static void shutdown_now (void)
Scott James Remnant4a9245f2006-08-30 19:13:00 +010089 __attribute__ ((noreturn));
Scott James Remnantd4cdaca2006-08-31 00:47:11 +010090static void cancel_callback (void *data, NihSignal *signal)
Scott James Remnant4a9245f2006-08-30 19:13:00 +010091 __attribute__ ((noreturn));
Scott James Remnantd4cdaca2006-08-31 00:47:11 +010092static void timer_callback (const char *message);
93static char *warning_message (const char *message)
94 __attribute__ ((warn_unused_result));
95static void wall (const char *message);
Scott James Remnantb8280072006-09-07 03:19:00 +010096static void sysvinit_shutdown (void);
Scott James Remnant4a9245f2006-08-30 19:13:00 +010097
98
99/**
Scott James Remnante8d263c2007-02-08 03:20:30 +0000100 * runlevel:
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100101 *
Scott James Remnante8d263c2007-02-08 03:20:30 +0000102 * Runlevel to switch to.
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100103 **/
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100104static int runlevel = 0;
Scott James Remnante8d263c2007-02-08 03:20:30 +0000105
106/**
107 * init_halt:
108 *
109 * Value of init_halt environment variable for event.
110 **/
111static const char *init_halt = NULL;
112
113/**
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100114 * cancel:
115 *
Scott James Remnant3b734642006-10-11 17:22:33 +0100116 * TRUE if we should cancel an already running shutdown.
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100117 **/
118static int cancel = FALSE;
119
120/**
121 * warn_only:
122 *
Scott James Remnant3b734642006-10-11 17:22:33 +0100123 * TRUE if we should only send the warning, and not perform the actual
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100124 * shutdown.
125 **/
126static int warn_only = FALSE;
127
128/**
129 * when:
130 *
131 * Time to shutdown, parsed from the old -g argument.
132 **/
133static char *when = NULL;
134
135/**
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100136 * delay:
137 *
138 * How long until we shutdown.
139 **/
140static int delay = 0;
Scott James Remnant47158482006-08-27 22:37:10 +0100141
142
143/**
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100144 * runlevel_option:
145 * @option: option found in arguments,
146 * @arg: always NULL.
147 *
148 * This function is called whenever one of the -r, -h, -H or -P options
149 * is found in the argument list. It changes the runlevel to that implied
150 * by the option.
151 **/
152static int
153runlevel_option (NihOption *option,
154 const char *arg)
155{
156 int *value;
157
158 nih_assert (option != NULL);
159 nih_assert (option->value != NULL);
160 nih_assert (arg == NULL);
161
162 value = (int *)option->value;
163
164 switch (option->option) {
165 case 'r':
166 *value = '6';
167 init_halt = NULL;
168 break;
169 case 'h':
170 *value = '0';
171 init_halt = NULL;
172 break;
173 case 'H':
174 *value = '0';
175 init_halt = "HALT";
176 break;
177 case 'P':
178 *value = '0';
179 init_halt = "POWEROFF";
180 break;
181 }
182
183 return 0;
184}
185
186
187/**
Scott James Remnant47158482006-08-27 22:37:10 +0100188 * options:
189 *
190 * Command-line options accepted for all arguments.
191 **/
192static NihOption options[] = {
Scott James Remnanteff01f12006-10-13 13:32:12 +0100193 { 'r', NULL, N_("reboot after shutdown"),
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100194 NULL, NULL, &runlevel, runlevel_option },
Scott James Remnanteff01f12006-10-13 13:32:12 +0100195 { 'h', NULL, N_("halt or power off after shutdown"),
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100196 NULL, NULL, &runlevel, runlevel_option },
Scott James Remnanteff01f12006-10-13 13:32:12 +0100197 { 'H', NULL, N_("halt after shutdown (implies -h)"),
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100198 NULL, NULL, &runlevel, runlevel_option },
Scott James Remnanteff01f12006-10-13 13:32:12 +0100199 { 'P', NULL, N_("power off after shutdown (implies -h)"),
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100200 NULL, NULL, &runlevel, runlevel_option },
Scott James Remnanteff01f12006-10-13 13:32:12 +0100201 { 'c', NULL, N_("cancel a running shutdown"),
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100202 NULL, NULL, &cancel, NULL },
Scott James Remnanteff01f12006-10-13 13:32:12 +0100203 { 'k', NULL, N_("only send warnings, don't shutdown"),
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100204 NULL, NULL, &warn_only, NULL },
205
206 /* Compatibility option for specifying time */
207 { 'g', NULL, NULL, NULL, "TIME", &when, NULL },
208
209 /* Compatibility options, all ignored */
210 { 'a', NULL, NULL, NULL, NULL, NULL, NULL },
211 { 'n', NULL, NULL, NULL, NULL, NULL, NULL },
212 { 'f', NULL, NULL, NULL, NULL, NULL, NULL },
213 { 'F', NULL, NULL, NULL, NULL, NULL, NULL },
214 { 'i', NULL, NULL, NULL, "LEVEL", NULL, NULL },
215 { 't', NULL, NULL, NULL, "SECS", NULL, NULL },
216 { 'y', NULL, NULL, NULL, NULL, NULL, NULL },
217
Scott James Remnant47158482006-08-27 22:37:10 +0100218 NIH_OPTION_LAST
219};
220
221
222int
223main (int argc,
224 char *argv[])
225{
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100226 char ** args;
227 nih_local char *message = NULL;
228 size_t messagelen;
229 nih_local char *msg = NULL;
230 int arg;
231 pid_t pid = 0;
Scott James Remnant47158482006-08-27 22:37:10 +0100232
233 nih_main_init (argv[0]);
Scott James Remnant1ee0f482006-10-11 17:40:41 +0100234
235 nih_option_set_usage (_("TIME [MESSAGE]"));
Scott James Remnanteff01f12006-10-13 13:32:12 +0100236 nih_option_set_synopsis (_("Bring the system down."));
237 nih_option_set_help (
238 _("TIME may have different formats, the most common is simply "
239 "the word 'now' which will bring the system down "
240 "immediately. Other valid formats are +m, where m is the "
241 "number of minutes to wait until shutting down and hh:mm "
242 "which specifies the time on the 24hr clock.\n"
243 "\n"
244 "Logged in users are warned by a message sent to their "
245 "terminal, you may include an optional MESSAGE included "
246 "with this. Messages can be sent without actually "
247 "bringing the system down by using the -k option.\n"
248 "\n"
249 "If TIME is given, the command will remain in the "
250 "foreground until the shutdown occurs. It can be cancelled "
251 "by Control-C, or by another user using the -c option.\n"
252 "\n"
253 "The system is brought down into maintenance (single-user) "
254 "mode by default, you can change this with either the -r or "
255 "-h option which specify a reboot or system halt "
256 "respectively. The -h option can be further modified with "
257 "-H or -P to specify whether to halt the system, or to "
258 "power it off afterwards. The default is left up to the "
259 "shutdown scripts."));
Scott James Remnant47158482006-08-27 22:37:10 +0100260
261 args = nih_option_parser (NULL, argc, argv, options, FALSE);
262 if (! args)
263 exit (1);
264
Scott James Remnante8d263c2007-02-08 03:20:30 +0000265 /* If the runlevel wasn't given explicitly, set it to 1 so we go
266 * down into single-user mode.
267 */
268 if (! runlevel) {
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100269 runlevel = '1';
Scott James Remnante8d263c2007-02-08 03:20:30 +0000270 init_halt = NULL;
Scott James Remnante8d263c2007-02-08 03:20:30 +0000271 }
Scott James Remnant47158482006-08-27 22:37:10 +0100272
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100273
274 /* When may be specified with -g, or must be first argument */
275 if (! (cancel || when || args[0])) {
276 fprintf (stderr, _("%s: time expected\n"), program_name);
277 nih_main_suggest_help ();
278 exit (1);
279 } else if (! (cancel || when)) {
Scott James Remnantc5d3e1b2009-02-20 22:22:48 +0000280 when = NIH_MUST (nih_strdup (NULL, args[0]));
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100281 arg = 1;
282 } else {
283 arg = 0;
284 }
285
286 /* Parse the time argument */
287 if (when) {
288 if (! strcmp (when, "now")) {
289 /* "now" means, err, now */
290 delay = 0;
291 } else if (strchr (when, ':')) {
292 /* Clock time */
293 long hours, mins;
294 char *endptr;
295 struct tm *tm;
296 time_t now;
297
298 hours = strtoul (when, &endptr, 10);
299 if ((*endptr != ':') || (hours < 0) || (hours > 23)) {
300 fprintf (stderr, _("%s: illegal hour value\n"),
301 program_name);
302 nih_main_suggest_help ();
303 exit (1);
304 }
305
306 mins = strtoul (endptr + 1, &endptr, 10);
307 if (*endptr || (mins < 0) || (mins > 59)) {
308 fprintf (stderr,
309 _("%s: illegal minute value\n"),
310 program_name);
311 nih_main_suggest_help ();
312 exit (1);
313 }
314
315 /* Subtract the current time to get the delay.
316 * Add a whole day if we go negative */
317 now = time (NULL);
318 tm = localtime (&now);
319 delay = (((hours * 60) + mins)
320 - ((tm->tm_hour * 60) + tm->tm_min));
321 if (delay < 0)
322 delay += 1440;
323 } else {
324 /* Delay in minutes */
325 char *endptr;
326
327 delay = strtoul (when, &endptr, 10);
328 if (*endptr || (delay < 0)) {
329 fprintf (stderr, _("%s: illegal time value\n"),
330 program_name);
331 nih_main_suggest_help ();
332 exit (1);
333 }
334 }
335 nih_free (when);
336 }
337
338
339 /* The rest of the arguments are a message.
340 * Really this should be just the next argument, but that's not
341 * how this has been traditionally done *sigh*
342 */
Scott James Remnantc5d3e1b2009-02-20 22:22:48 +0000343 message = NIH_MUST (nih_strdup (NULL, ""));
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100344 messagelen = 0;
345 for (; args[arg]; arg++) {
Scott James Remnant7d59ed22009-02-20 22:28:03 +0000346 message = NIH_MUST (nih_realloc (
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100347 message, NULL,
348 messagelen + strlen(args[arg]) + 4));
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100349
350 strcat (message, args[arg]);
351 strcat (message, " ");
352 messagelen += strlen (args[arg]) + 1;
353 }
354
355 /* Terminate with \r\n */
356 if (messagelen)
357 strcat (message, "\r\n");
358
359
360 /* Check we're root, or setuid root */
361 setuid (geteuid ());
362 if (getuid ()) {
Scott James Remnant31421b72007-03-08 23:53:05 +0000363 nih_fatal (_("Need to be root"));
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100364 exit (1);
365 }
366
Scott James Remnantf9aec8c2007-06-12 12:18:15 +0100367 /* Look for an existing pid file and deal with the existing
368 * process if there is one.
369 */
370 pid = nih_main_read_pidfile ();
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100371 if (pid > 0) {
372 if (cancel) {
373 if (kill (pid, SIGINT) < 0) {
374 nih_error (_("Shutdown is not running"));
375 exit (1);
376 }
377
378 if (messagelen)
379 wall (message);
380
381 exit (0);
382 } else if (kill (pid, 0) == 0) {
383 nih_error (_("Another shutdown is already running"));
384 exit (1);
385 }
386 } else if (cancel) {
387 nih_error (_("Cannot find pid of running shutdown"));
388 exit (1);
389 }
390
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100391 /* Send an initial message */
Scott James Remnantc5d3e1b2009-02-20 22:22:48 +0000392 msg = NIH_MUST (warning_message (message));
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100393 wall (msg);
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100394
Scott James Remnantedcae302006-09-04 07:06:23 +0100395 if (warn_only)
396 exit (0);
397
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100398
399 /* Give us a sane environment */
Scott James Remnantb175de82009-07-09 12:50:19 +0100400 if (chdir ("/") < 0)
401 nih_warn ("%s: %s", _("Unable to change directory"),
402 strerror (errno));
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100403 umask (022);
404
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100405 /* Shutdown now? */
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100406 if (! delay)
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100407 shutdown_now ();
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100408
409 /* Save our pid so we can be interrupted later */
Scott James Remnantf9aec8c2007-06-12 12:18:15 +0100410 if (nih_main_write_pidfile (getpid ()) < 0) {
411 NihError *err;
412
413 err = nih_error_get ();
414 nih_warn ("%s: %s: %s", nih_main_get_pidfile(),
415 _("Unable to write pid file"), err->message);
416 nih_free (err);
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100417 }
418
419
420 /* Ignore a whole bunch of signals */
421 nih_signal_set_ignore (SIGCHLD);
422 nih_signal_set_ignore (SIGHUP);
423 nih_signal_set_ignore (SIGTSTP);
424 nih_signal_set_ignore (SIGTTIN);
425 nih_signal_set_ignore (SIGTTOU);
426
427 /* Catch the usual quit signals */
428 nih_signal_set_handler (SIGINT, nih_signal_handler);
Scott James Remnant36911572007-02-01 18:18:27 +0000429 NIH_MUST (nih_signal_add_handler (NULL, SIGINT,
430 cancel_callback, NULL));
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100431 nih_signal_set_handler (SIGQUIT, nih_signal_handler);
Scott James Remnant36911572007-02-01 18:18:27 +0000432 NIH_MUST (nih_signal_add_handler (NULL, SIGQUIT,
433 cancel_callback, NULL));
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100434 nih_signal_set_handler (SIGTERM, nih_signal_handler);
Scott James Remnant36911572007-02-01 18:18:27 +0000435 NIH_MUST (nih_signal_add_handler (NULL, SIGTERM,
436 cancel_callback, NULL));
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100437
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100438 /* Call a timer every minute until we shutdown */
Scott James Remnant36911572007-02-01 18:18:27 +0000439 NIH_MUST (nih_timer_add_periodic (NULL, 60,
440 (NihTimerCb)timer_callback,
441 message));
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100442
443 /* Hang around */
444 nih_main_loop ();
445
446 return 0;
447}
448
449
450/**
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100451 * shutdown_now:
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100452 *
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100453 * Send a signal to init to shut down the machine.
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100454 *
455 * This does not return.
456 **/
457static void
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100458shutdown_now (void)
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100459{
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100460 nih_local char **extra_env = NULL;
461 NihDBusError * dbus_err;
Petr Lautrbach18ff78d2010-12-20 09:46:30 +0000462 int exit_val = 0;
Scott James Remnante8d263c2007-02-08 03:20:30 +0000463
464 if (init_halt) {
465 char *e;
466
Scott James Remnantc5d3e1b2009-02-20 22:22:48 +0000467 e = NIH_MUST (nih_sprintf (NULL, "INIT_HALT=%s", init_halt));
Scott James Remnante8d263c2007-02-08 03:20:30 +0000468
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100469 extra_env = NIH_MUST (nih_str_array_new (NULL));
470 NIH_MUST (nih_str_array_addp (&extra_env, NULL, NULL, e));
Scott James Remnante8d263c2007-02-08 03:20:30 +0000471 }
472
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100473 if (sysv_change_runlevel (runlevel, extra_env, NULL, NULL) < 0) {
474 dbus_err = (NihDBusError *)nih_error_get ();
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100475
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100476 if ((dbus_err->number != NIH_DBUS_ERROR)
477 || strcmp (dbus_err->name, DBUS_ERROR_NO_SERVER)) {
478 nih_fatal ("%s", dbus_err->message);
Scott James Remnant6d610662010-02-03 20:00:11 -0800479 nih_free (dbus_err);
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100480 exit (1);
481 }
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100482
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100483 nih_free (dbus_err);
Scott James Remnantb8280072006-09-07 03:19:00 +0100484
485 /* Connection Refused means that init isn't running, this
486 * might mean we've just upgraded to upstart and haven't
487 * yet rebooted ... so try /dev/initctl
488 */
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100489 sysvinit_shutdown ();
Petr Lautrbach18ff78d2010-12-20 09:46:30 +0000490 nih_fatal ("Unable to shutdown system");
491 exit_val = 1;
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100492 }
493
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100494 unlink (ETC_NOLOGIN);
Scott James Remnantf9aec8c2007-06-12 12:18:15 +0100495 nih_main_unlink_pidfile ();
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100496
Petr Lautrbach18ff78d2010-12-20 09:46:30 +0000497 exit (exit_val);
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100498}
499
500/**
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100501 * cancel_callback:
502 * @data: not used,
503 * @signal: signal caught.
504 *
505 * This callback is run whenever one of the "cancel running shutdown"
506 * signals is sent to us.
507 *
508 * This does not return.
509 **/
510static void
511cancel_callback (void *data,
512 NihSignal *signal)
513{
514 nih_error (_("Shutdown cancelled"));
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100515 unlink (ETC_NOLOGIN);
Scott James Remnantf9aec8c2007-06-12 12:18:15 +0100516 nih_main_unlink_pidfile ();
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100517 exit (0);
518}
519
520/**
521 * timer_callback:
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100522 * @message: message to display.
523 *
524 * This callback is run every minute until we are ready to shutdown, it
525 * ensures regular warnings are sent to logged in users and handles
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100526 * preventing new logins. Once time is up, it handles shutting down.
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100527 *
528 * This will modify delay each time it is called.
529 **/
530static void
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100531timer_callback (const char *message)
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100532{
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100533 nih_local char *msg = NULL;
534 int warn = FALSE;
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100535
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100536 delay--;
Scott James Remnantc5d3e1b2009-02-20 22:22:48 +0000537 msg = NIH_MUST (warning_message (message));
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100538
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100539
540 /* Write /etc/nologin with less than 5 minutes remaining */
541 if (delay <= 5) {
542 FILE *nologin;
543
544 nologin = fopen (ETC_NOLOGIN, "w");
545 if (nologin) {
546 fputs (msg, nologin);
547 fclose (nologin);
548 }
549 }
550
551 /* Only warn at particular intervals */
552 if (delay < 10) {
553 warn = TRUE;
554 } else if (delay < 60) {
555 warn = (delay % 15 ? FALSE : TRUE);
556 } else if (delay < 180) {
557 warn = (delay % 30 ? FALSE : TRUE);
558 } else {
559 warn = (delay % 60 ? FALSE : TRUE);
560 }
561
562 if (warn)
563 wall (msg);
564
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100565 /* Shutdown the machine at zero */
566 if (! delay)
567 shutdown_now ();
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100568}
569
Scott James Remnant0b358ab2006-08-31 02:07:32 +0100570
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100571/**
572 * warning_message:
573 * @message: user message.
574 *
575 * Prefixes the message with details about how long until the shutdown
576 * completes.
577 *
578 * Returns: newly allocated string.
579 **/
580static char *
581warning_message (const char *message)
582{
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100583 nih_local char *banner = NULL;
584 char * msg;
Scott James Remnante371cab2006-09-18 16:16:00 +0100585
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100586 nih_assert (message != NULL);
587
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100588 if ((runlevel == '0')
589 && init_halt && (! strcmp (init_halt, "POWEROFF"))) {
590 if (delay) {
591 banner = nih_sprintf (
592 NULL, _n("The system is going down for "
593 "power off in %d minute!",
594 "The system is going down for "
595 "power off in %d minutes!",
596 delay), delay);
597 } else {
598 banner = nih_strdup (
599 NULL, _("The system is going down for "
600 "power off NOW!"));
601 }
602 } else if (runlevel == '0') {
603 if (delay) {
604 banner = nih_sprintf (
605 NULL, _n("The system is going down for "
606 "halt in %d minute!",
607 "The system is going down for "
608 "halt in %d minutes!",
609 delay), delay);
610 } else {
611 banner = nih_strdup (
612 NULL, _("The system is going down for "
613 "halt NOW!"));
614 }
615 } else if (runlevel == '1') {
616 if (delay) {
617 banner = nih_sprintf (
618 NULL, _n("The system is going down for "
619 "maintenance in %d minute!",
620 "The system is going down for "
621 "maintenance in %d minutes!",
622 delay), delay);
623 } else {
624 banner = nih_strdup (
625 NULL, _("The system is going down for "
626 "maintenance NOW!"));
627 }
628 } else if (runlevel == '6') {
629 if (delay) {
630 banner = nih_sprintf (
631 NULL, _n("The system is going down for "
632 "reboot in %d minute!",
633 "The system is going down for "
634 "reboot in %d minutes!",
635 delay), delay);
636 } else {
637 banner = nih_strdup (
638 NULL, _("The system is going down for "
639 "reboot NOW!"));
640 }
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100641 }
642
Scott James Remnante371cab2006-09-18 16:16:00 +0100643 if (! banner)
644 return NULL;
645
646 msg = nih_sprintf (NULL, "\r%s\r\n%s", banner, message);
Scott James Remnante371cab2006-09-18 16:16:00 +0100647
648 return msg;
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100649}
650
651/**
652 * alarm_handler:
653 * @signum: signal called.
654 *
655 * Empty function used to cause the ALRM signal to break a syscall.
656 **/
657static void
658alarm_handler (int signum)
659{
660}
661
662/**
663 * wall:
664 * @message: message to send.
665 *
666 * Send a message to all logged in users; based largely on the code from
667 * bsdutils. This is done in a child process to stop anything blocking.
668 **/
669static void
670wall (const char *message)
671{
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100672 struct sigaction act;
673 struct utmpx * ent;
674 pid_t pid;
675 time_t now;
676 struct tm * tm;
677 char * user;
678 char * tty;
679 char hostname[MAXHOSTNAMELEN];
680 char * banner1;
681 char * banner2;
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100682
683 pid = fork ();
684 if (pid < 0) {
685 nih_warn (_("Unable to fork child-process to warn users: %s"),
686 strerror (errno));
687 return;
688 } else if (pid > 0) {
689 return;
690 }
691
692 /* Break syscalls with SIGALRM */
693 act.sa_handler = alarm_handler;
694 act.sa_flags = 0;
695 sigemptyset (&act.sa_mask);
696 sigaction (SIGALRM, &act, NULL);
697
698
699 /* Get username for banner */
700 user = getlogin ();
701 if (! user) {
702 struct passwd *pw;
703
704 pw = getpwuid (getuid ());
705 if (pw)
706 user = pw->pw_name;
707 }
708 if (! user) {
709 if (getuid ()) {
Scott James Remnantc5d3e1b2009-02-20 22:22:48 +0000710 user = NIH_MUST (nih_sprintf (NULL, "uid %d",
Scott James Remnant0b358ab2006-08-31 02:07:32 +0100711 getuid ()));
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100712 } else {
713 user = "root";
714 }
715 }
716
Scott James Remnantd4cdaca2006-08-31 00:47:11 +0100717 /* Get hostname for banner */
718 gethostname (hostname, sizeof (hostname));
719
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100720 /* Get terminal for banner */
721 tty = ttyname (0);
722 if (! tty)
723 tty = "unknown";
724
725 /* Get time */
726 now = time (NULL);
727 tm = localtime (&now);
728
729 /* Construct banner */
Scott James Remnant136dc6f2006-12-14 12:36:43 +0000730 banner1 = nih_sprintf (NULL, _("Broadcast message from %s@%s"),
731 user, hostname);
732 banner2 = nih_sprintf (NULL, _("(%s) at %d:%02d ..."),
733 tty, tm->tm_hour, tm->tm_min);
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100734
735
736 /* Iterate entries in the utmp file */
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100737 setutxent ();
738 while ((ent = getutxent ()) != NULL) {
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100739 char dev[PATH_MAX + 1];
740 int fd;
741
742 /* Ignore entries without a name, or not a user process */
743 if ((ent->ut_type != USER_PROCESS)
744 || (! strlen (ent->ut_user)))
745 continue;
746
747 /* Construct the device path */
748 if (strncmp (ent->ut_line, DEV "/", 5)) {
749 snprintf (dev, sizeof (dev),
750 "%s/%s", DEV, ent->ut_line);
751 } else {
752 snprintf (dev, sizeof (dev), "%s", ent->ut_line);
753 }
754
755 alarm (2);
756 fd = open (dev, O_WRONLY | O_NDELAY | O_NOCTTY);
757 if ((fd >= 0) && isatty (fd)) {
758 FILE *term;
759
760 term = fdopen (fd, "w");
761 if (term) {
Scott James Remnant136dc6f2006-12-14 12:36:43 +0000762 fprintf (term, "\007\r\n%s\r\n\t%s\r\n\r\n",
763 banner1, banner2);
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100764 fputs (message, term);
765 fflush (term);
766 fclose (term);
767 }
768 }
769 alarm (0);
770 }
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100771 endutxent ();
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100772
Scott James Remnant136dc6f2006-12-14 12:36:43 +0000773 nih_free (banner1);
774 nih_free (banner2);
775
Scott James Remnant4a9245f2006-08-30 19:13:00 +0100776 exit (0);
Scott James Remnant47158482006-08-27 22:37:10 +0100777}
Scott James Remnantb8280072006-09-07 03:19:00 +0100778
779
780/**
781 * struct request:
782 *
783 * This is the structure passed across /dev/initctl.
784 **/
785struct request {
786 int magic;
787 int cmd;
788 int runlevel;
789 int sleeptime;
790 char data[368];
791};
792
793/**
794 * sysvinit_shutdown:
795 *
796 * Attempt to shutdown a running sysvinit /sbin/init using its /dev/initctl
797 * socket.
798 **/
799static void
800sysvinit_shutdown (void)
801{
802 struct sigaction act;
803 struct request request;
804 int fd;
805
806 /* Fill in the magic values */
807 memset (&request, 0, sizeof (request));
808 request.magic = 0x03091969;
809 request.sleeptime = 5;
810 request.cmd = 1;
811
812 /* Select a runlevel based on the event name */
Scott James Remnantf24e55d2009-07-08 19:58:37 +0100813 request.runlevel = runlevel;
Scott James Remnantb8280072006-09-07 03:19:00 +0100814
815
816 /* Break syscalls with SIGALRM */
817 act.sa_handler = alarm_handler;
818 act.sa_flags = 0;
819 sigemptyset (&act.sa_mask);
820 sigaction (SIGALRM, &act, NULL);
821
822 /* Try and open /dev/initctl */
823 alarm (3);
Scott James Remnant7e6bdb32006-09-14 10:57:03 +0100824 fd = open (DEV_INITCTL, O_WRONLY | O_NDELAY | O_NOCTTY);
Scott James Remnantb8280072006-09-07 03:19:00 +0100825 if (fd >= 0) {
826 if (write (fd, &request, sizeof (request)) == sizeof (request))
827 exit (0);
828
829 close (fd);
830 }
831
832 alarm (0);
833}