From 391d12db48754861b5cecac92ee3321597ee02c1 Mon Sep 17 00:00:00 2001 From: William Hubbs Date: Wed, 6 Apr 2022 10:51:55 -0500 Subject: migrate fully to meson build system - drop old build system - move shared include and source files to common directory - drop "rc-" prefix from shared include and source files - move executable-specific code to individual directories under src - adjust top-level .gitignore file for new build system This closes #489. --- src/openrc-shutdown/broadcast.c | 211 ++++++++++++++++++++ src/openrc-shutdown/broadcast.h | 16 ++ src/openrc-shutdown/meson.build | 10 + src/openrc-shutdown/openrc-shutdown.c | 358 ++++++++++++++++++++++++++++++++++ src/openrc-shutdown/rc-sysvinit.c | 102 ++++++++++ src/openrc-shutdown/rc-sysvinit.h | 72 +++++++ 6 files changed, 769 insertions(+) create mode 100644 src/openrc-shutdown/broadcast.c create mode 100644 src/openrc-shutdown/broadcast.h create mode 100644 src/openrc-shutdown/meson.build create mode 100644 src/openrc-shutdown/openrc-shutdown.c create mode 100644 src/openrc-shutdown/rc-sysvinit.c create mode 100644 src/openrc-shutdown/rc-sysvinit.h (limited to 'src/openrc-shutdown') diff --git a/src/openrc-shutdown/broadcast.c b/src/openrc-shutdown/broadcast.c new file mode 100644 index 00000000..402a9fb9 --- /dev/null +++ b/src/openrc-shutdown/broadcast.c @@ -0,0 +1,211 @@ +/* + * broadcast.c + * broadcast a message to every logged in user + */ + +/* + * Copyright 2018 Sony Interactive Entertainment Inc. + * + * This file is part of OpenRC. It is subject to the license terms in + * the LICENSE file found in the top-level directory of this + * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE + * This file may not be copied, modified, propagated, or distributed + * except according to the terms contained in the LICENSE file. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "broadcast.h" +#include "helpers.h" + +#ifndef _PATH_DEV +# define _PATH_DEV "/dev/" +#endif + +static sigjmp_buf jbuf; + +/* + * Alarm handler + */ +/*ARGSUSED*/ +# ifdef __GNUC__ +static void handler(int arg __attribute__((unused))) +# else +static void handler(int arg) +# endif +{ + siglongjmp(jbuf, 1); +} + +static void getuidtty(char **userp, char **ttyp) +{ + struct passwd *pwd; + uid_t uid; + char *tty; + static char uidbuf[32]; + char *ttynm = NULL; + + uid = getuid(); + if ((pwd = getpwuid(uid)) != NULL) { + uidbuf[0] = 0; + strncat(uidbuf, pwd->pw_name, sizeof(uidbuf) - 1); + } else { + if (uid) + sprintf(uidbuf, "uid %d", (int) uid); + else + sprintf(uidbuf, "root"); + } + + if ((tty = ttyname(0)) != NULL) { + const size_t plen = strlen(_PATH_DEV); + if (strncmp(tty, _PATH_DEV, plen) == 0) { + tty += plen; + if (tty[0] == '/') + tty++; + } + xasprintf(&ttynm, "(%s) ", tty); + } + + *userp = uidbuf; + *ttyp = ttynm; +} + +/* + * Check whether the given filename looks like a tty device. + */ +static int file_isatty(const char *fname) +{ + struct stat st; + int major; + + if (stat(fname, &st) < 0) + return 0; + + if (st.st_nlink != 1 || !S_ISCHR(st.st_mode)) + return 0; + + /* + * It would be an impossible task to list all major/minors + * of tty devices here, so we just exclude the obvious + * majors of which just opening has side-effects: + * printers and tapes. + */ + major = major(st.st_dev); + if (major == 1 || major == 2 || major == 6 || major == 9 || + major == 12 || major == 16 || major == 21 || major == 27 || + major == 37 || major == 96 || major == 97 || major == 206 || + major == 230) + return 0; + return 1; +} + +/* + * broadcast function. + * + * NB: Not multithread safe. + */ +void broadcast(char *text) +{ + char *tty; + char *user; + struct utsname name; + time_t t; + char *date; + char *p; + char *line = NULL; + struct sigaction sa; + int flags; + char *term = NULL; + struct utmpx *utmp; + /* + * These are set across the sigsetjmp call, so they can't be stored on + * the stack, otherwise they might be clobbered. + */ + static int fd; + static FILE *tp; + + getuidtty(&user, &tty); + + /* + * Get and report current hostname, to make it easier to find out + * which machine is being shut down. + */ + uname(&name); + + /* Get the time */ + time(&t); + date = ctime(&t); + p = strchr(date, '\n'); + if (p) + *p = 0; + + xasprintf(&line, "\007\r\nBroadcast message from %s@%s %s(%s):\r\n\r\n", + user, name.nodename, tty, date); + free(tty); + + /* + * Fork to avoid hanging in a write() + */ + if (fork() != 0) + return; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sigemptyset(&sa.sa_mask); + sigaction(SIGALRM, &sa, NULL); + + setutxent(); + + while ((utmp = getutxent()) != NULL) { + if (utmp->ut_type != USER_PROCESS || utmp->ut_user[0] == 0) + continue; + if (strncmp(utmp->ut_line, _PATH_DEV, strlen(_PATH_DEV)) == 0) + xasprintf(&term, "%s", utmp->ut_line); + else + xasprintf(&term, "%s%s", _PATH_DEV, utmp->ut_line); + if (strstr(term, "/../")) { + free(term); + continue; + } + + /* + * Open it non-delay + */ + if (sigsetjmp(jbuf, 1) == 0) { + alarm(2); + flags = O_WRONLY|O_NDELAY|O_NOCTTY; + if (file_isatty(term) && (fd = open(term, flags)) >= 0) { + if (isatty(fd) && (tp = fdopen(fd, "w")) != NULL) { + fputs(line, tp); + fputs(text, tp); + fflush(tp); + } + } + } + alarm(0); + if (fd >= 0) + close(fd); + if (tp != NULL) + fclose(tp); + free(term); + } + endutxent(); + free(line); + exit(0); +} diff --git a/src/openrc-shutdown/broadcast.h b/src/openrc-shutdown/broadcast.h new file mode 100644 index 00000000..2255fe67 --- /dev/null +++ b/src/openrc-shutdown/broadcast.h @@ -0,0 +1,16 @@ +/* + * Copyright 2018 Sony Interactive Entertainment Inc. + * + * This file is part of OpenRC. It is subject to the license terms in + * the LICENSE file found in the top-level directory of this + * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE + * This file may not be copied, modified, propagated, or distributed + * except according to the terms contained in the LICENSE file. + */ + +#ifndef BROADCAST_H +#define BROADCAST_H + +void broadcast(char *text); + +#endif diff --git a/src/openrc-shutdown/meson.build b/src/openrc-shutdown/meson.build new file mode 100644 index 00000000..bdd58338 --- /dev/null +++ b/src/openrc-shutdown/meson.build @@ -0,0 +1,10 @@ +if os == 'Linux' + executable('openrc-shutdown', + ['openrc-shutdown.c', 'broadcast.c', 'rc-sysvinit.c', misc_c, + usage_c, wtmp_c, version_h], + c_args : cc_branding_flags, + include_directories: [incdir, einfo_incdir, rc_incdir], + link_with: [libeinfo, librc], + install: true, + install_dir: sbindir) +endif diff --git a/src/openrc-shutdown/openrc-shutdown.c b/src/openrc-shutdown/openrc-shutdown.c new file mode 100644 index 00000000..1234dcfc --- /dev/null +++ b/src/openrc-shutdown/openrc-shutdown.c @@ -0,0 +1,358 @@ +/* + * openrc-shutdown.c + * If you are using OpenRC's provided init, this will shut down or + * reboot your system. + * + * This is based on code written by James Hammons , so + * I would like to publically thank him for his work. + */ + +/* + * Copyright 2017 The OpenRC Authors. + * See the Authors file at the top-level directory of this distribution and + * https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS + * + * This file is part of OpenRC. It is subject to the license terms in + * the LICENSE file found in the top-level directory of this + * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE + * This file may not be copied, modified, propagated, or distributed + * except according to the terms contained in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "broadcast.h" +#include "einfo.h" +#include "rc.h" +#include "helpers.h" +#include "misc.h" +#include "rc-sysvinit.h" +#include "wtmp.h" +#include "_usage.h" + +const char *applet = NULL; +const char *extraopts = NULL; +const char getoptstring[] = "cdDfFHKpRrsw" getoptstring_COMMON; +const struct option longopts[] = { + { "cancel", no_argument, NULL, 'c'}, + { "no-write", no_argument, NULL, 'd'}, + { "dry-run", no_argument, NULL, 'D'}, + { "halt", no_argument, NULL, 'H'}, + { "kexec", no_argument, NULL, 'K'}, + { "poweroff", no_argument, NULL, 'p'}, + { "reexec", no_argument, NULL, 'R'}, + { "reboot", no_argument, NULL, 'r'}, + { "single", no_argument, NULL, 's'}, + { "write-only", no_argument, NULL, 'w'}, + longopts_COMMON +}; +const char * const longopts_help[] = { + "cancel a pending shutdown", + "do not write wtmp record", + "print actions instead of executing them", + "halt the system", + "reboot the system using kexec", + "power off the system", + "re-execute init (use after upgrading)", + "reboot the system", + "single user mode", + "write wtmp boot record and exit", + longopts_help_COMMON +}; +const char *usagestring = "" \ + "Usage: openrc-shutdown -c | --cancel\n" \ + " or: openrc-shutdown -R | --reexec\n" \ + " or: openrc-shutdown -w | --write-only\n" \ + " or: openrc-shutdown -H | --halt time\n" \ + " or: openrc-shutdown -K | --kexec time\n" \ + " or: openrc-shutdown -p | --poweroff time\n" \ + " or: openrc-shutdown -r | --reboot time\n" \ + " or: openrc-shutdown -s | --single time"; +const char *exclusive = "Select one of " + "--cancel, --halt, --kexec, --poweroff, --reexec, --reboot, --single or \n" + "--write-only"; +const char *nologin_file = RC_SYSCONFDIR"/nologin"; +const char *shutdown_pid = "/run/openrc-shutdown.pid"; + +static bool do_cancel = false; +static bool do_dryrun = false; +static bool do_halt = false; +static bool do_kexec = false; +static bool do_poweroff = false; +static bool do_reboot = false; +static bool do_reexec = false; +static bool do_single = false; +static bool do_wtmp = true; +static bool do_wtmp_only = false; + +static void cancel_shutdown(void) +{ + pid_t pid; + + pid = get_pid(applet, shutdown_pid); + if (pid <= 0) + eerrorx("%s: Unable to cancel shutdown", applet); + + if (kill(pid, SIGTERM) != -1) + einfo("%s: shutdown canceled", applet); + else + eerrorx("%s: Unable to cancel shutdown", applet); +} + +/* + * Create the nologin file. + */ +static void create_nologin(int mins) +{ + FILE *fp; + time_t t; + + time(&t); + t += 60 * mins; + + if ((fp = fopen(nologin_file, "w")) != NULL) { + fprintf(fp, "\rThe system is going down on %s\r\n", ctime(&t)); + fclose(fp); + } +} + +/* + * Send a command to our init + */ +static void send_cmd(const char *cmd) +{ + FILE *fifo; + size_t ignored; + + if (do_dryrun) { + einfo("Would send %s to init", cmd); + return; + } + if (do_wtmp && (do_halt || do_kexec || do_reboot || do_poweroff)) + log_wtmp("shutdown", "~~", 0, RUN_LVL, "~~"); + fifo = fopen(RC_INIT_FIFO, "w"); + if (!fifo) { + perror("fopen"); + return; + } + + ignored = fwrite(cmd, 1, strlen(cmd), fifo); + if (ignored != strlen(cmd)) + printf("Error writing to init fifo\n"); + fclose(fifo); +} + +/* + * sleep without being interrupted. + * The idea for this code came from sysvinit. + */ +static void sleep_no_interrupt(int seconds) +{ + struct timespec duration; + struct timespec remaining; + + duration.tv_sec = seconds; + duration.tv_nsec = 0; + + while (nanosleep(&duration, &remaining) < 0 && errno == EINTR) + duration = remaining; +} + +static void stop_shutdown(int sig) +{ + (void) sig; + unlink(nologin_file); + unlink(shutdown_pid); +einfo("Shutdown canceled"); +exit(0); +} + +int main(int argc, char **argv) +{ + char *ch = NULL; + int opt; + int cmd_count = 0; + int hour = 0; + int min = 0; + int shutdown_delay = 0; + struct sigaction sa; + struct tm *lt; + time_t tv; + bool need_warning = false; + char *msg = NULL; + char *state = NULL; + char *time_arg = NULL; + FILE *fp; + + applet = basename_c(argv[0]); + while ((opt = getopt_long(argc, argv, getoptstring, + longopts, (int *) 0)) != -1) + { + switch (opt) { + case 'c': + do_cancel = true; + cmd_count++; + break; + case 'd': + do_wtmp = false; + break; + case 'D': + do_dryrun = true; + break; + case 'H': + do_halt = true; + xasprintf(&state, "%s", "halt"); + cmd_count++; + break; + case 'K': + do_kexec = true; + xasprintf(&state, "%s", "reboot"); + cmd_count++; + break; + case 'p': + do_poweroff = true; + xasprintf(&state, "%s", "power off"); + cmd_count++; + break; + case 'R': + do_reexec = true; + cmd_count++; + break; + case 'r': + do_reboot = true; + xasprintf(&state, "%s", "reboot"); + cmd_count++; + break; + case 's': + do_single = true; + xasprintf(&state, "%s", "go down for maintenance"); + cmd_count++; + break; + case 'w': + do_wtmp_only = true; + cmd_count++; + break; + case_RC_COMMON_GETOPT + } + } + if (geteuid() != 0) + eerrorx("%s: you must be root\n", applet); + if (cmd_count != 1) { + eerror("%s: %s\n", applet, exclusive); + usage(EXIT_FAILURE); + } + + if (do_cancel) { + cancel_shutdown(); + exit(EXIT_SUCCESS); + } else if (do_reexec) { + send_cmd("reexec"); + exit(EXIT_SUCCESS); + } else if (do_wtmp_only) { + log_wtmp("shutdown", "~~", 0, RUN_LVL, "~~"); + exit(EXIT_SUCCESS); + } + + if (optind >= argc) { + eerror("%s: No shutdown time specified", applet); + usage(EXIT_FAILURE); + } + time_arg = argv[optind]; + if (*time_arg == '+') + time_arg++; + if (strcasecmp(time_arg, "now") == 0) + strcpy(time_arg, "0"); + for (ch=time_arg; *ch; ch++) + if ((*ch < '0' || *ch > '9') && *ch != ':') { + eerror("%s: invalid time %s", applet, time_arg); + usage(EXIT_FAILURE); + } + if (strchr(time_arg, ':')) { + if ((sscanf(time_arg, "%2d:%2d", &hour, &min) != 2) || + (hour > 23) || (min > 59)) { + eerror("%s: invalid time %s", applet, time_arg); + usage(EXIT_FAILURE); + } + time(&tv); + lt = localtime(&tv); + shutdown_delay = (hour * 60 + min) - (lt->tm_hour * 60 + lt->tm_min); + if (shutdown_delay < 0) + shutdown_delay += 1440; + } else { + shutdown_delay = atoi(time_arg); + } + + fp = fopen(shutdown_pid, "w"); + if (!fp) + eerrorx("%s: fopen `%s': %s", applet, shutdown_pid, strerror(errno)); + fprintf(fp, "%d\n", getpid()); + fclose(fp); + + openlog(applet, LOG_PID, LOG_DAEMON); + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = stop_shutdown; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTERM, &sa, NULL); + while (shutdown_delay > 0) { + need_warning = false; + if (shutdown_delay > 180) + need_warning = (shutdown_delay % 60 == 0); + else if (shutdown_delay > 60) + need_warning = (shutdown_delay % 30 == 0); + else if (shutdown_delay > 10) + need_warning = (shutdown_delay % 15 == 0); + else + need_warning = true; + if (shutdown_delay <= 5) + create_nologin(shutdown_delay); + if (need_warning) { + xasprintf(&msg, "\rThe system will %s in %d minutes\r\n", + state, shutdown_delay); + broadcast(msg); + free(msg); + } + sleep_no_interrupt(60); + shutdown_delay--; + } + xasprintf(&msg, "\rThe system will %s now\r\n", state); + broadcast(msg); + syslog(LOG_NOTICE, "The system will %s now", state); + unlink(nologin_file); + unlink(shutdown_pid); + if (do_halt) { + if (exists("/run/initctl")) { + sysvinit_setenv("INIT_HALT", "HALT"); + sysvinit_runlevel('0'); + } else + send_cmd("halt"); + } else if (do_kexec) + send_cmd("kexec"); + else if (do_poweroff) { + if (exists("/run/initctl")) { + sysvinit_setenv("INIT_HALT", "POWEROFF"); + sysvinit_runlevel('0'); + } else + send_cmd("poweroff"); + } else if (do_reboot) { + if (exists("/run/initctl")) + sysvinit_runlevel('6'); + else + send_cmd("reboot"); + } else if (do_single) { + if (exists("/run/initctl")) + sysvinit_runlevel('S'); + else + send_cmd("single"); + } + return 0; +} diff --git a/src/openrc-shutdown/rc-sysvinit.c b/src/openrc-shutdown/rc-sysvinit.c new file mode 100644 index 00000000..8d258b63 --- /dev/null +++ b/src/openrc-shutdown/rc-sysvinit.c @@ -0,0 +1,102 @@ +/* + * rc-sysvinit.c + * Helper to send a runlevel change to sysvinit + */ + +/* + * Copyright (c) 2019 The OpenRC Authors. + * See the Authors file at the top-level directory of this distribution and + * https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS + * + * This file is part of OpenRC. It is subject to the license terms in + * the LICENSE file found in the top-level directory of this + * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE + * This file may not be copied, modified, propagated, or distributed + * except according to the terms contained in the LICENSE file. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "einfo.h" +#include "rc-sysvinit.h" + +static void sysvinit_send_cmd(struct init_request *request) +{ + int fd; + char *p; + size_t bytes; + ssize_t r; + + fd = open("/run/initctl", O_WRONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (errno != ENOENT) + eerror("Failed to open initctl fifo: %s", strerror(errno)); + return; + } + p = (char *) request; + bytes = sizeof(*request); + do { + r = write(fd, p, bytes); + if (r < 0) { + if ((errno == EAGAIN) || (errno == EINTR)) + continue; + eerror("Failed to write to /run/initctl: %s", strerror(errno)); + return; + } + p += r; + bytes -= r; + } while (bytes > 0); +} + +void sysvinit_runlevel(char rl) +{ + struct init_request request; + + if (!rl) + return; + + request = (struct init_request) { + .magic = INIT_MAGIC, + .sleeptime = 0, + .cmd = INIT_CMD_RUNLVL, + .runlevel = rl, + }; + sysvinit_send_cmd(&request); + return; +} + +/* + * Set environment variables in the init process. + */ +void sysvinit_setenv(const char *name, const char *value) +{ + struct init_request request; + size_t nl; + size_t vl; + + memset(&request, 0, sizeof(request)); + request.magic = INIT_MAGIC; + request.cmd = INIT_CMD_SETENV; + nl = strlen(name); + if (value) + vl = strlen(value); +else + vl = 0; + + if (nl + vl + 3 >= (int)sizeof(request.i.data)) + return; + + memcpy(request.i.data, name, nl); + if (value) { + request.i.data[nl] = '='; + memcpy(request.i.data + nl + 1, value, vl); + } + sysvinit_send_cmd(&request); + return; +} diff --git a/src/openrc-shutdown/rc-sysvinit.h b/src/openrc-shutdown/rc-sysvinit.h new file mode 100644 index 00000000..6142cdd6 --- /dev/null +++ b/src/openrc-shutdown/rc-sysvinit.h @@ -0,0 +1,72 @@ +/* + * rc-sysvinit.h - Interface to communicate with sysvinit via /run/initctl. + */ + +/* + * Copyright (c) 2019 The OpenRC Authors. + * See the Authors file at the top-level directory of this distribution and + * https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS + * + * This file is part of OpenRC. It is subject to the license terms in + * the LICENSE file found in the top-level directory of this + * distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE + * This file may not be copied, modified, propagated, or distributed + * except according to the terms contained in the LICENSE file. + */ + +#ifndef _RC_SYSVINIT_H +#define _RC_SYSVINIT_H + +/* + * The #defines and structures below are taken from initreq.h in + * sysvinit and must be used by any program wishing to communicate with + * it. + */ + +#define INIT_MAGIC 0x03091969 +#define INIT_CMD_START 0 +#define INIT_CMD_RUNLVL 1 +#define INIT_CMD_POWERFAIL 2 +#define INIT_CMD_POWERFAILNOW 3 +#define INIT_CMD_POWEROK 4 +#define INIT_CMD_BSD 5 +#define INIT_CMD_SETENV 6 +#define INIT_CMD_UNSETENV 7 + +/* + * This is what BSD 4.4 uses when talking to init. + * Linux doesn't use this right now. + */ +struct init_request_bsd { + char gen_id[8]; /* Beats me.. telnetd uses "fe" */ + char tty_id[16]; /* Tty name minus /dev/tty */ + char host[64]; /* Hostname */ + char term_type[16]; /* Terminal type */ + int signal; /* Signal to send */ + int pid; /* Process to send to */ + char exec_name[128]; /* Program to execute */ + char reserved[128]; /* For future expansion. */ +}; + +/* + * Because of legacy interfaces, "runlevel" and "sleeptime" + * aren't in a seperate struct in the union. + * + * The weird sizes are because init expects the whole + * struct to be 384 bytes. + */ +struct init_request { + int magic; /* Magic number */ + int cmd; /* What kind of request */ + int runlevel; /* Runlevel to change to */ + int sleeptime; /* Time between TERM and KILL */ + union { + struct init_request_bsd bsd; + char data[368]; + } i; +}; + +void sysvinit_runlevel(char rl); +void sysvinit_setenv(const char *name, const char *value); + +#endif -- cgit v1.2.3