diff options
Diffstat (limited to 'src/state')
-rw-r--r-- | src/state/deps-exe/s6-linux-init-halt | 5 | ||||
-rw-r--r-- | src/state/deps-exe/s6-linux-init-poweroff | 5 | ||||
-rw-r--r-- | src/state/deps-exe/s6-linux-init-reboot | 5 | ||||
-rw-r--r-- | src/state/deps-exe/s6-linux-init-shutdown | 5 | ||||
-rw-r--r-- | src/state/deps-exe/s6-linux-init-shutdownd | 4 | ||||
-rw-r--r-- | src/state/deps-exe/s6-linux-init-single | 1 | ||||
-rw-r--r-- | src/state/deps-exe/s6-linux-init-telinit | 2 | ||||
-rw-r--r-- | src/state/deps-lib/hpr | 4 | ||||
-rw-r--r-- | src/state/hpr.c | 102 | ||||
-rw-r--r-- | src/state/hpr.h | 22 | ||||
-rw-r--r-- | src/state/hpr_fmttime.c | 52 | ||||
-rw-r--r-- | src/state/hpr_shutdown.c | 16 | ||||
-rw-r--r-- | src/state/hpr_wall.c | 47 | ||||
-rw-r--r-- | src/state/hpr_wall_seconds.c | 12 | ||||
-rw-r--r-- | src/state/s6-linux-init-halt.c | 7 | ||||
-rw-r--r-- | src/state/s6-linux-init-poweroff.c | 7 | ||||
-rw-r--r-- | src/state/s6-linux-init-reboot.c | 7 | ||||
-rw-r--r-- | src/state/s6-linux-init-shutdown.c | 269 | ||||
-rw-r--r-- | src/state/s6-linux-init-shutdownd.c | 278 | ||||
-rw-r--r-- | src/state/s6-linux-init-single.c | 54 | ||||
-rw-r--r-- | src/state/s6-linux-init-telinit.c | 42 |
21 files changed, 946 insertions, 0 deletions
diff --git a/src/state/deps-exe/s6-linux-init-halt b/src/state/deps-exe/s6-linux-init-halt new file mode 100644 index 0000000..0c4376c --- /dev/null +++ b/src/state/deps-exe/s6-linux-init-halt @@ -0,0 +1,5 @@ +libhpr.a.xyzzy +${LIBUTMPS} +-lskarnet +${TAINNOW_LIB} +${SOCKET_LIB} diff --git a/src/state/deps-exe/s6-linux-init-poweroff b/src/state/deps-exe/s6-linux-init-poweroff new file mode 100644 index 0000000..0c4376c --- /dev/null +++ b/src/state/deps-exe/s6-linux-init-poweroff @@ -0,0 +1,5 @@ +libhpr.a.xyzzy +${LIBUTMPS} +-lskarnet +${TAINNOW_LIB} +${SOCKET_LIB} diff --git a/src/state/deps-exe/s6-linux-init-reboot b/src/state/deps-exe/s6-linux-init-reboot new file mode 100644 index 0000000..0c4376c --- /dev/null +++ b/src/state/deps-exe/s6-linux-init-reboot @@ -0,0 +1,5 @@ +libhpr.a.xyzzy +${LIBUTMPS} +-lskarnet +${TAINNOW_LIB} +${SOCKET_LIB} diff --git a/src/state/deps-exe/s6-linux-init-shutdown b/src/state/deps-exe/s6-linux-init-shutdown new file mode 100644 index 0000000..0c4376c --- /dev/null +++ b/src/state/deps-exe/s6-linux-init-shutdown @@ -0,0 +1,5 @@ +libhpr.a.xyzzy +${LIBUTMPS} +-lskarnet +${TAINNOW_LIB} +${SOCKET_LIB} diff --git a/src/state/deps-exe/s6-linux-init-shutdownd b/src/state/deps-exe/s6-linux-init-shutdownd new file mode 100644 index 0000000..5a7d2fd --- /dev/null +++ b/src/state/deps-exe/s6-linux-init-shutdownd @@ -0,0 +1,4 @@ +${LIBUTMPS} +-lskarnet +${TAINNOW_LIB} +${SOCKET_LIB} diff --git a/src/state/deps-exe/s6-linux-init-single b/src/state/deps-exe/s6-linux-init-single new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/state/deps-exe/s6-linux-init-single @@ -0,0 +1 @@ +-lskarnet diff --git a/src/state/deps-exe/s6-linux-init-telinit b/src/state/deps-exe/s6-linux-init-telinit new file mode 100644 index 0000000..aeb24a6 --- /dev/null +++ b/src/state/deps-exe/s6-linux-init-telinit @@ -0,0 +1,2 @@ +libhpr.a.xyzzy +-lskarnet diff --git a/src/state/deps-lib/hpr b/src/state/deps-lib/hpr new file mode 100644 index 0000000..066efd2 --- /dev/null +++ b/src/state/deps-lib/hpr @@ -0,0 +1,4 @@ +hpr_fmttime.o +hpr_shutdown.o +hpr_wall.o +hpr_wall_seconds.o diff --git a/src/state/hpr.c b/src/state/hpr.c new file mode 100644 index 0000000..401cc97 --- /dev/null +++ b/src/state/hpr.c @@ -0,0 +1,102 @@ +/* ISC license. */ + +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <utmpx.h> +#include <sys/reboot.h> + +#include <skalibs/strerr2.h> +#include <skalibs/sgetopt.h> +#include <skalibs/sig.h> +#include <skalibs/tai.h> +#include <skalibs/djbunix.h> + +#include "defaults.h" +#include "hpr.h" + +#ifndef UT_NAMESIZE +#define UT_NAMESIZE 32 +#endif + +#ifndef UT_HOSTSIZE +#define UT_HOSTSIZE 256 +#endif + +#ifndef _PATH_WTMP +#define _PATH_WTMP "/dev/null/wtmp" +#endif + +#define USAGE PROGNAME " [ -h | -p | -r ] [ -d | -w ] [ -W ] [ -f ]" + +int main (int argc, char const *const *argv) +{ + int what = WHATDEFAULT ; + int force = 0 ; + int dowtmp = 1 ; + int dowall = 1 ; + PROG = PROGNAME ; + + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "hprfdwW", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'h' : what = 1 ; break ; + case 'p' : what = 2 ; break ; + case 'r' : what = 3 ; break ; + case 'f' : force = 1 ; break ; + case 'd' : dowtmp = 0 ; break ; + case 'w' : dowtmp = 2 ; break ; + case 'W' : dowall = 0 ; break ; + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= l.ind ; argv += l.ind ; + } + + if (geteuid()) + { + errno = EPERM ; + strerr_dief1sys(100, "nice try, peon") ; + } + + if (force) + { + reboot(what == 3 ? RB_AUTOBOOT : what == 2 ? RB_POWER_OFF : RB_HALT_SYSTEM) ; + strerr_diefu1sys(111, "reboot()") ; + } + + if (!tain_now_g()) strerr_warnw1sys("get current time") ; + if (dowtmp) + { + struct utmpx utx = + { + .ut_type = RUN_LVL, + .ut_pid = getpid(), + .ut_line = "~", + .ut_id = "", + .ut_session = getsid(0) + } ; + strncpy(utx.ut_user, what == 3 ? "reboot" : "shutdown", UT_NAMESIZE) ; + if (gethostname(utx.ut_host, UT_HOSTSIZE) < 0) + { + utx.ut_host[0] = 0 ; + strerr_warnwu1sys("gethostname") ; + } + else utx.ut_host[UT_HOSTSIZE - 1] = 0 ; + if (!timeval_from_tain(&utx.ut_tv, &STAMP)) + strerr_warnwu1sys("timeval_from_tain") ; + updwtmpx(_PATH_WTMP, &utx) ; + } + if (dowall) hpr_wall_seconds(0) ; + if (dowtmp < 2) + { + if (!hpr_shutdown(what, &STAMP, 0)) + strerr_diefu1sys(111, "notify s6-linux-init-shutdownd") ; + } + return 0 ; +} diff --git a/src/state/hpr.h b/src/state/hpr.h new file mode 100644 index 0000000..9deb6c4 --- /dev/null +++ b/src/state/hpr.h @@ -0,0 +1,22 @@ +/* ISC license. */ + +#ifndef HPR_H +#define HPR_H + +#include <stddef.h> + +#include <skalibs/tai.h> +#include <skalibs/djbunix.h> + +#include "initctl.h" + +#define HPR_WALL_BANNER "\n\n*** WARNING ***\nThe system is going down in " + +#define hpr_send(s, n) openwritenclose_unsafe(INITCTL, (s), n) +#define hpr_cancel() hpr_send("c", 1) +extern int hpr_shutdown (unsigned int, tain_t const *, unsigned int) ; +extern size_t hpr_fmttime (char *, size_t, unsigned int) ; +extern void hpr_wall (char const *) ; +extern void hpr_wall_seconds (unsigned int) ; + +#endif diff --git a/src/state/hpr_fmttime.c b/src/state/hpr_fmttime.c new file mode 100644 index 0000000..8038cfe --- /dev/null +++ b/src/state/hpr_fmttime.c @@ -0,0 +1,52 @@ +/* ISC license. */ + +#include <string.h> + +#include <skalibs/types.h> + +#include "hpr.h" + +size_t hpr_fmttime (char *buf, size_t max, unsigned int seconds) +{ + size_t m = 0 ; + unsigned int minutes = 0 ; + unsigned int hours = 0 ; + if (seconds >= 60) + { + minutes = seconds / 60 ; + seconds %= 60 ; + if (minutes >= 60) + { + hours = minutes / 60 ; + minutes %= 60 ; + } + } + m = sizeof(HPR_WALL_BANNER) - 1 ; + if (m > max) return 0 ; + memcpy(buf, HPR_WALL_BANNER, m) ; + if (hours) + { + if (m + UINT_FMT + 6 > max) return 0 ; + m += uint_fmt(buf + m, hours) ; + memcpy(buf + m, " hour", 5) ; + m += 5 ; + if (hours > 1) buf[m++] = 's' ; + buf[m++] = ' ' ; + } + if (minutes) + { + if (m + UINT_FMT + 8 > max) return 0 ; + m += uint_fmt(buf + m, minutes) ; + memcpy(buf + m, " minute", 7) ; + m += 7 ; + if (minutes > 1) buf[m++] = 's' ; + buf[m++] = ' ' ; + } + if (m + UINT_FMT + 8 > max) return 0 ; + m += uint_fmt(buf + m, seconds) ; + memcpy(buf + m, " second", 7) ; + m += 7 ; + if (seconds != 1) buf[m++] = 's' ; + buf[m++] = ' ' ; + return m ; +} diff --git a/src/state/hpr_shutdown.c b/src/state/hpr_shutdown.c new file mode 100644 index 0000000..9c9ff51 --- /dev/null +++ b/src/state/hpr_shutdown.c @@ -0,0 +1,16 @@ +/* ISC license. */ + +#include <stdint.h> + +#include <skalibs/uint32.h> +#include <skalibs/tai.h> + +#include "hpr.h" + +int hpr_shutdown (unsigned int what, tain_t const *when, unsigned int grace) +{ + char pack[5 + TAIN_PACK] = { " hpr"[what] } ; + tain_pack(pack+1, when) ; + uint32_pack_big(pack + 1 + TAIN_PACK, (uint32_t)grace) ; + return hpr_send(pack, 5 + TAIN_PACK) ; +} diff --git a/src/state/hpr_wall.c b/src/state/hpr_wall.c new file mode 100644 index 0000000..cc6da5e --- /dev/null +++ b/src/state/hpr_wall.c @@ -0,0 +1,47 @@ +/* ISC license. */ + +#include <string.h> +#include <errno.h> +#include <utmpx.h> + +#include <skalibs/posixishard.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/strerr2.h> +#include <skalibs/djbunix.h> + +#include "hpr.h" + +#ifndef UT_LINESIZE +#define UT_LINESIZE 32 +#endif + +void hpr_wall (char const *s) +{ + size_t n = strlen(s) ; + char tty[10 + UT_LINESIZE] = "/dev/" ; + char msg[n+1] ; + memcpy(msg, s, n) ; + msg[n++] = '\n' ; + setutxent() ; + for (;;) + { + size_t linelen, idlen ; + struct utmpx *utx ; + int fd ; + errno = 0 ; + utx = getutxent() ; + if (!utx) break ; + if (utx->ut_type != USER_PROCESS) continue ; + linelen = strnlen(utx->ut_line, UT_LINESIZE) ; + idlen = strnlen(utx->ut_id, 4) ; + memcpy(tty + 5, utx->ut_line, linelen) ; + memcpy(tty + 5 + linelen, utx->ut_id, idlen) ; + tty[5 + linelen + idlen] = 0 ; + fd = open_append(tty) ; + if (fd == -1) continue ; + allwrite(fd, msg, n) ; + fd_close(fd) ; + } + if (errno) strerr_warnwu1sys("getutxent") ; + endutxent() ; +} diff --git a/src/state/hpr_wall_seconds.c b/src/state/hpr_wall_seconds.c new file mode 100644 index 0000000..6c0363b --- /dev/null +++ b/src/state/hpr_wall_seconds.c @@ -0,0 +1,12 @@ +/* ISC license. */ + +#include "hpr.h" + +void hpr_wall_seconds (unsigned int secs) +{ + char buf[300] = HPR_WALL_BANNER ; + size_t n = sizeof(HPR_WALL_BANNER) - 1 ; + n += hpr_fmttime(buf + n, 300 - n, secs) ; + buf[n++] = 0 ; + hpr_wall(buf) ; +} diff --git a/src/state/s6-linux-init-halt.c b/src/state/s6-linux-init-halt.c new file mode 100644 index 0000000..4ccca1d --- /dev/null +++ b/src/state/s6-linux-init-halt.c @@ -0,0 +1,7 @@ +/* ISC license. */ + +#undef PROGNAME +#define PROGNAME "s6-linux-init-halt" +#undef WHATDEFAULT +#define WHATDEFAULT 1 +#include "hpr.c" diff --git a/src/state/s6-linux-init-poweroff.c b/src/state/s6-linux-init-poweroff.c new file mode 100644 index 0000000..d25dbb6 --- /dev/null +++ b/src/state/s6-linux-init-poweroff.c @@ -0,0 +1,7 @@ +/* ISC license. */ + +#undef PROGNAME +#define PROGNAME "s6-linux-init-poweroff" +#undef WHATDEFAULT +#define WHATDEFAULT 2 +#include "hpr.c" diff --git a/src/state/s6-linux-init-reboot.c b/src/state/s6-linux-init-reboot.c new file mode 100644 index 0000000..3612684 --- /dev/null +++ b/src/state/s6-linux-init-reboot.c @@ -0,0 +1,7 @@ +/* ISC license. */ + +#undef PROGNAME +#define PROGNAME "s6-linux-init-reboot" +#undef WHATDEFAULT +#define WHATDEFAULT 3 +#include "hpr.c" diff --git a/src/state/s6-linux-init-shutdown.c b/src/state/s6-linux-init-shutdown.c new file mode 100644 index 0000000..eb65d1d --- /dev/null +++ b/src/state/s6-linux-init-shutdown.c @@ -0,0 +1,269 @@ +/* ISC license. */ + +#include <stdint.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <sys/stat.h> +#include <errno.h> +#include <time.h> +#include <utmpx.h> +#include <sys/reboot.h> + +#include <skalibs/uint32.h> +#include <skalibs/types.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/strerr2.h> +#include <skalibs/sgetopt.h> +#include <skalibs/sig.h> +#include <skalibs/tai.h> +#include <skalibs/djbunix.h> +#include <skalibs/djbtime.h> + +#include "defaults.h" +#include "initctl.h" +#include "hpr.h" + +#ifndef UT_NAMESIZE +#define UT_NAMESIZE 32 +#endif + +#define USAGE "s6-linux-init-shutdown [ -h | -p | -r | -k ] [ -f | -F ] [ -a ] [ -t sec ] time [ message ] or s6-linux-init-shutdown -c [ message ]" +#define dieusage() strerr_dieusage(100, USAGE) + +#define AC_FILE "/etc/shutdown.allow" +#define AC_BUFSIZE 4096 +#define AC_MAX 64 +#define AC_SHORT_MESSAGE "no authorized users logged in\n" +#define AC_MESSAGE "s6-linux-init-shutdown: " AC_SHORT_MESSAGE + + + /* shutdown 01:23: date/time format parsing */ + +static inline void add_one_day (struct tm *tm) +{ + tm->tm_isdst = -1 ; + if (tm->tm_mday++ < 31) return ; + tm->tm_mday = 1 ; + if (tm->tm_mon++ < 11) return ; + tm->tm_mon = 0 ; + tm->tm_year++ ; +} + +static inline void parse_hourmin (tain_t *when, char const *s) +{ + tai_t taithen ; + struct tm tmnow, tmthen ; + unsigned int hour, minute ; + size_t len = uint_scan(s, &hour) ; + if (!len || len > 2 || s[len] != ':' || hour > 23) + strerr_dief1x(100, "invalid time format") ; + s += len+1 ; + len = uint0_scan(s, &minute) ; + if (!len || len != 2 || minute > 59) + strerr_dief1x(100, "invalid time format") ; + if (!localtm_from_tai(&tmnow, tain_secp(&STAMP), 1)) + strerr_diefu1sys(111, "break down current time into struct tm") ; + tmthen = tmnow ; + tmthen.tm_hour = hour ; + tmthen.tm_min = minute ; + if (!tai_from_localtm(&taithen, &tmthen)) + strerr_diefu1sys(111, "assemble broken-down time into tain_t") ; + if (tai_less(&taithen, tain_secp(&STAMP))) + { + add_one_day(&tmthen) ; + if (!tai_from_localtm(&taithen, &tmthen)) + strerr_diefu1sys(111, "assemble broken-down time into tain_t") ; + } + tai_sub(tain_secp(when), &taithen, tain_secp(&STAMP)) ; + when->nano = 0 ; +} + +static inline void parse_time (tain_t *when, char const *s) +{ + if (!strcmp(s, "now")) tain_copynow(when) ; + else if (s[0] == '+') + { + unsigned int mins ; + if (!uint0_scan(s+1, &mins)) dieusage() ; + tain_addsec_g(when, mins * 60) ; + } + else if (strchr(s, ':')) parse_hourmin(when, s) ; + else + { + unsigned int mins ; + if (!uint0_scan(s, &mins)) dieusage() ; + tain_addsec_g(when, mins * 60) ; + } +} + + + /* shutdown -a: access control */ + +static inline unsigned char cclass (unsigned char c) +{ + switch (c) + { + case 0 : return 0 ; + case '\n' : return 1 ; + case '#' : return 2 ; + default : return 3 ; + } +} + +static inline unsigned int parse_authorized_users (char *buf, char const **users, unsigned int max) +{ + static unsigned char const table[3][4] = + { + { 0x03, 0x00, 0x01, 0x12 }, + { 0x03, 0x00, 0x01, 0x01 }, + { 0x23, 0x20, 0x02, 0x02 } + } ; + size_t pos = 0 ; + size_t mark = 0 ; + unsigned int n = 0 ; + unsigned int state = 0 ; + for (; state < 3 ; pos++) + { + unsigned char what = table[state][cclass(buf[pos])] ; + state = what & 3 ; + if (what & 0x10) mark = pos ; + if (what & 0x20) + { + if (n >= max) + { + char fmt[UINT32_MAX] ; + fmt[uint32_fmt(fmt, AC_MAX)] = 0 ; + strerr_warnw4x(AC_FILE, " lists more than ", fmt, " authorized users - ignoring the extra ones") ; + break ; + } + buf[pos] = 0 ; + users[n++] = buf + mark ; + } + } + return n ; +} + +static inline int match_users_with_utmp (char const *const *users, unsigned int n) +{ + setutxent() ; + for (;;) + { + struct utmpx *utx ; + errno = 0 ; + utx = getutxent() ; + if (!utx) break ; + if (utx->ut_type != USER_PROCESS) continue ; + for (unsigned int i = 0 ; i < n ; i++) + if (!strncmp(utx->ut_user, users[i], UT_NAMESIZE)) goto yes ; + } + if (errno) strerr_warnwu1sys("getutxent") ; + endutxent() ; + return 0 ; + + yes: + endutxent() ; + return 1 ; +} + +static inline void access_control (void) +{ + char buf[AC_BUFSIZE] ; + char const *users[AC_MAX] ; + unsigned int n ; + struct stat st ; + int fd = open_readb(AC_FILE) ; + if (fd == -1) + { + if (errno == ENOENT) return ; + strerr_diefu2sys(111, "open ", AC_FILE) ; + } + if (fstat(fd, &st) == -1) + strerr_diefu2sys(111, "stat ", AC_FILE) ; + if (st.st_size >= AC_BUFSIZE) + { + char fmt[UINT32_FMT] ; + fmt[uint32_fmt(fmt, AC_BUFSIZE - 1)] = 0 ; + strerr_dief4x(1, AC_FILE, " is too big: it needs to be ", fmt, " bytes or less") ; + } + if (allread(fd, buf, st.st_size) < st.st_size) + strerr_diefu2sys(111, "read ", AC_FILE) ; + fd_close(fd) ; + buf[st.st_size] = 0 ; + n = parse_authorized_users(buf, users, AC_MAX) ; + if (!n || !match_users_with_utmp(users, n)) + { + fd = open_append("/dev/console") ; + if (fd == -1) + strerr_diefu1sys(111, "open /dev/console") ; + if (allwrite(fd, AC_MESSAGE, sizeof(AC_MESSAGE) - 1) < sizeof(AC_MESSAGE) - 1) + strerr_diefu1sys(111, "write to /dev/console") ; + strerr_dief1x(1, AC_SHORT_MESSAGE) ; + } +} + + + /* main */ + +int main (int argc, char const *const *argv) +{ + unsigned int gracetime = 0 ; + int what = 0 ; + int doactl = 0 ; + int docancel = 0 ; + tain_t when ; + PROG = "shutdown" ; + + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "hprkafFct:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'h' : what = 1 ; break ; + case 'p' : what = 2 ; break ; + case 'r' : what = 3 ; break ; + case 'k' : what = 4 ; break ; + case 'a' : doactl = 1 ; break ; + case 'f' : /* talk to the hand */ break ; + case 'F' : /* no, the other hand */ break ; + case 'c' : docancel = 1 ; break ; + case 't' : if (!uint0_scan(l.arg, &gracetime)) dieusage() ; break ; + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= l.ind ; argv += l.ind ; + } + + if (geteuid()) + { + errno = EPERM ; + strerr_diefu1sys(111, "shutdown") ; + } + if (doactl) access_control() ; + if (!tain_now_g()) strerr_warnw1sys("get current time") ; + if (docancel) + { + if (argv[0]) hpr_wall(argv[0]) ; + if (!hpr_cancel()) goto err ; + return 0 ; + } + if (!argc) dieusage() ; + parse_time(&when, argv[0]) ; + if (argv[1]) hpr_wall(argv[1]) ; + if (what < 4) + { + if (gracetime > 300) + { + gracetime = 300 ; + strerr_warnw1x("delay between SIGTERM and SIGKILL is capped to 300 seconds") ; + } + if (!hpr_shutdown(what, &when, gracetime * 1000)) goto err ; + } + return 0 ; + + err: + strerr_diefu2sys(111, "write to ", INITCTL) ; +} diff --git a/src/state/s6-linux-init-shutdownd.c b/src/state/s6-linux-init-shutdownd.c new file mode 100644 index 0000000..d4ecd4b --- /dev/null +++ b/src/state/s6-linux-init-shutdownd.c @@ -0,0 +1,278 @@ +/* ISC license. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <unistd.h> +#include <stdio.h> +#include <sys/wait.h> + +#include <skalibs/posixplz.h> +#include <skalibs/uint32.h> +#include <skalibs/types.h> +#include <skalibs/allreadwrite.h> +#include <skalibs/bytestr.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr2.h> +#include <skalibs/sgetopt.h> +#include <skalibs/stralloc.h> +#include <skalibs/sig.h> +#include <skalibs/tai.h> +#include <skalibs/djbunix.h> +#include <skalibs/iopause.h> +#include <skalibs/skamisc.h> + +#include "defaults.h" +#include "initctl.h" +#include "hpr.h" + +#define STAGE4_FILE "stage 4" + +#define USAGE "s6-linux-init-shutdownd [ -b bindir ] [ -c basedir ] [ -g gracetime ]" +#define dieusage() strerr_dieusage(100, USAGE) + +static char const *basedir = BASEDIR ; + +static inline void prepare_shutdown (char c, unsigned int *what, tain_t *deadline, unsigned int *grace_time) +{ + uint32_t u ; + char pack[TAIN_PACK] ; + ssize_t r = sanitize_read(buffer_get(buffer_0small, pack, TAIN_PACK)) ; + if (r == -1) strerr_diefu1sys(111, "read from pipe") ; + if (r < TAIN_PACK + 4) strerr_dief1x(101, "bad shutdown protocol") ; + tain_unpack(pack, deadline) ; + uint32_unpack_big(pack + TAIN_PACK, &u) ; + if (u && u <= 300000) *grace_time = u ; + *what = byte_chr(" hpr", c, 4) ; +} + +static inline void change_runlevels (char c) +{ + pid_t pid = doublefork() ; + if (pid == -1) + strerr_warnwu1sys("doublefork for runlevel change") ; + if (pid) + { + size_t basedirlen = strlen(basedir) ; + char fn[basedirlen + sizeof("/scripts/runlevel")] ; + char s[2] = { c, 0 } ; + char const *cargv[3] = { fn, s, 0 } ; + PROG = "s6-linux-init-shutdownd (grandchild)" ; + memcpy(fn, basedir, basedirlen) ; + memcpy(fn + basedirlen, "/scripts/runlevel", sizeof("/scripts/runlevel")) ; + xpathexec_run(cargv[0], cargv, (char const *const *)environ) ; + } +} + +static inline void handle_stdin (unsigned int *what, tain_t *deadline, unsigned int *grace_time) +{ + char rl = 0 ; + for (;;) + { + char c ; + ssize_t r = sanitize_read(buffer_get(buffer_0small, &c, 1)) ; + if (r == -1) strerr_diefu1sys(111, "read from pipe") ; + else if (!r) break ; + switch (c) + { + case 'h' : + case 'p' : + case 'r' : + prepare_shutdown(c, what, deadline, grace_time) ; + break ; + case 'c' : + *what = 0 ; + rl = 0 ; + tain_add_g(deadline, &tain_infinite_relative) ; + break ; + case 'S' : + *what = 4 ; + tain_copynow(deadline) ; + break ; + case '0' : + case '1' : + case '2' : + case '3' : + case '4' : + case '5' : + case '6' : + *what = 0 ; + tain_add_g(deadline, &tain_infinite_relative) ; + rl = c ; + break ; + default : + { + char s[2] = { c, 0 } ; + strerr_warnw2x("unknown command: ", s) ; + } + break ; + } + } + if (rl) change_runlevels(rl) ; +} + +static inline void prepare_stage4 (char const *bindir, char const *basedir, unsigned int what) +{ + static char const *table[4] = { "single", "halt", "poweroff", "reboot" } ; + stralloc sa = STRALLOC_ZERO ; + buffer b ; + char buf[512] ; + int fd ; + unlink_void(STAGE4_FILE ".new") ; + fd = open_excl(STAGE4_FILE ".new") ; + if (fd == -1) strerr_diefu3sys(111, "open ", STAGE4_FILE ".new", " for writing") ; + buffer_init(&b, &buffer_write, fd, buf, 512) ; + + if (buffer_puts(&b, "#!") == -1 + || buffer_puts(&b, bindir) == -1 + || buffer_puts(&b, "/execlineb -P\n\n") == -1) + strerr_diefu2sys(111, "write to ", STAGE4_FILE ".new") ; + if (!string_quote(&sa, basedir, strlen(basedir))) + strerr_diefu1sys(111, "string_quote") ; + if (buffer_put(&b, sa.s, sa.len) == -1 + || buffer_puts(&b, "/bin/" STAGE4 " ") == -1 + || buffer_puts(&b, table[what]) == -1 + || buffer_putsflush(&b, "\n") == -1) + strerr_diefu2sys(111, "write to ", STAGE4_FILE ".new") ; + stralloc_free(&sa) ; + if (fchmod(fd, S_IRWXU) == -1) + strerr_diefu2sys(111, "fchmod ", STAGE4_FILE ".new") ; + if (fsync(fd) == -1) + strerr_diefu2sys(111, "fsync ", STAGE4_FILE ".new") ; + fd_close(fd) ; + if (rename(STAGE4_FILE ".new", STAGE4_FILE) == -1) + strerr_diefu4sys(111, "rename ", STAGE4_FILE ".new", " to ", STAGE4_FILE) ; +} + +int main (int argc, char const *const *argv) +{ + char const *bindir = BINDIR ; + pid_t pid ; + tain_t deadline ; + unsigned int what = 0 ; + unsigned int grace_time = 3000 ; + int notif = 0 ; + PROG = "s6-linux-init-shutdownd" ; + + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "b:c:g:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'b' : bindir = l.arg ; break ; + case 'c' : basedir = l.arg ; break ; + case 'g' : if (!uint0_scan(l.arg, &grace_time)) dieusage() ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + if (bindir[0] != '/') + strerr_dief2x(100, "bindir", " must be an absolute path") ; + if (basedir[0] != '/') + strerr_dief2x(100, "basedir", " must be an absolute path") ; + if (grace_time > 300000) grace_time = 300000 ; + + /* if we're in stage 4, exec it immediately */ + { + char const *stage4_argv[2] = { STAGE4_FILE, 0 } ; + pathexec_run(stage4_argv[0], stage4_argv, (char const *const *)environ) ; + if (errno != ENOENT) + strerr_warnwu2sys("exec ", stage4_argv[0]) ; + } + + fd_close(1) ; + fd_close(0) ; + + if (open_read(SHUTDOWND_FIFO)) + strerr_diefu3sys(111, "open ", SHUTDOWND_FIFO, " for reading") ; + if (open_write(SHUTDOWND_FIFO) != 1) + strerr_diefu3sys(111, "open ", SHUTDOWND_FIFO, " for writing") ; + if (sig_ignore(SIGPIPE) == -1) + strerr_diefu1sys(111, "sig_ignore SIGPIPE") ; + tain_now_g() ; + tain_add_g(&deadline, &tain_infinite_relative) ; + + for (;;) + { + iopause_fd x = { .fd = 0, .events = IOPAUSE_READ } ; + int r = iopause_g(&x, 1, &deadline) ; + if (r == -1) strerr_diefu1sys(111, "iopause") ; + if (!r) + { + if (what) break ; + tain_add_g(&deadline, &tain_infinite_relative) ; + continue ; + } + if (x.revents & IOPAUSE_READ) + handle_stdin(&what, &deadline, &grace_time) ; + } + + if (what == 4) what = 0 ; + + /* Stage 3 */ + + fd_close(1) ; + fd_close(0) ; + if (open_readb("/dev/null")) + strerr_diefu1sys(111, "open /dev/null for reading") ; + if (open_write("/dev/console") != 1 || ndelay_off(1) == -1) + strerr_diefu1sys(111, "open /dev/console for writing") ; + { + size_t basedirlen = strlen(basedir) ; + char stage3[basedirlen + sizeof("/" STAGE3)] ; + char const *stage3_argv[2] = { stage3, 0 } ; + memcpy(stage3, basedir, basedirlen) ; + memcpy(stage3 + basedirlen, "/" STAGE3, sizeof("/" STAGE3)) ; + sig_block(SIGCHLD) ; + pid = child_spawn0(stage3_argv[0], stage3_argv, (char const *const *)environ) ; + if (!pid) strerr_warnwu2sys("spawn ", stage3) ; + sig_unblock(SIGCHLD) ; + if (pid) + { + int wstat ; + if (wait_pid(pid, &wstat) == -1) strerr_diefu1sys(111, "waitpid") ; + if (WIFSIGNALED(wstat)) + { + char fmt[UINT_FMT] ; + fmt[uint_fmt(fmt, WTERMSIG(wstat))] = 0 ; + strerr_warnw3x(stage3, " was killed by signal ", fmt) ; + } + else if (WEXITSTATUS(wstat)) + { + char fmt[UINT_FMT] ; + fmt[uint_fmt(fmt, WTERMSIG(wstat))] = 0 ; + strerr_warnw3x(stage3, " was killed by signal ", fmt) ; + } + else if (WEXITSTATUS(wstat)) + { + char fmt[UINT_FMT] ; + fmt[uint_fmt(fmt, WEXITSTATUS(wstat))] = 0 ; + strerr_warnw3x(stage3, " exited ", fmt) ; + } + } + } + + + /* The end is coming! */ + + prepare_stage4(bindir, basedir, what) ; + if (fd_move(2, 1) == -1) strerr_warnwu1sys("fd_move") ; + sync() ; + if (sig_ignore(SIGTERM) == -1) strerr_warnwu1sys("sig_ignore SIGTERM") ; + strerr_warni1x("sending all processes the TERM signal...") ; + kill(-1, SIGTERM) ; + kill(-1, SIGCONT) ; + tain_from_millisecs(&deadline, grace_time) ; + tain_add_g(&deadline, &deadline) ; + deepsleepuntil_g(&deadline) ; + sync() ; + strerr_warni1x("sending all processes the KILL signal...") ; + kill(-1, SIGKILL) ; + return 0 ; +} diff --git a/src/state/s6-linux-init-single.c b/src/state/s6-linux-init-single.c new file mode 100644 index 0000000..4f23340 --- /dev/null +++ b/src/state/s6-linux-init-single.c @@ -0,0 +1,54 @@ +/* ISC license. */ + +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +#include <skalibs/allreadwrite.h> +#include <skalibs/strerr2.h> +#include <skalibs/djbunix.h> + +#include "initctl.h" + +#define USAGE "s6-linux-init-single" +#define dieusage() strerr_dieusage(100, USAGE) + +#define BANNER "Executing interactive root shell.\n" + +int main (void) +{ + int fd ; + PROG = "s6-linux-init-single" ; + fd = open(EARLYGETTY, O_RDONLY) ; + if (fd == -1) + { + if (errno != ENOENT) + strerr_diefu2sys(111, "opendir ", EARLYGETTY) ; + goto shell ; + } + if (faccessat(fd, "down", F_OK, 0) == 0) + { + fd_close(fd) ; + goto shell ; + } + if (errno != ENOENT) + strerr_diefu3sys(111, "access ", EARLYGETTY, "/down") ; + return 0 ; + + shell: + + fd = open("/dev/console", O_RDONLY) ; + if (fd == -1) strerr_diefu2sys(111, "open /dev/console for ", "reading") ; + if (fd_move(0, fd) == -1) strerr_diefu1sys(111, "move /dev/console fd to 0") ; + fd = open("/dev/console", O_WRONLY) ; + if (fd == -1) strerr_diefu2sys(111, "open /dev/console for ", "writing") ; + if (fd_move(1, fd) == -1) strerr_diefu1sys(111, "move /dev/console fd to 1") ; + if (fd_copy(2, 1) == -1) strerr_diefu1sys(111, "dup2 1 to 2") ; + if (allwrite(1, BANNER, sizeof(BANNER) - 1) < sizeof(BANNER) - 1) + strerr_diefu1sys(111, "write banner") ; + { + char *argv[3] = { "/bin/sh", "-i", 0 } ; + execv(argv[0], argv) ; + strerr_dieexec(111, argv[0]) ; + } +} diff --git a/src/state/s6-linux-init-telinit.c b/src/state/s6-linux-init-telinit.c new file mode 100644 index 0000000..856e78b --- /dev/null +++ b/src/state/s6-linux-init-telinit.c @@ -0,0 +1,42 @@ +/* ISC license. */ + +#include <skalibs/sgetopt.h> +#include <skalibs/types.h> +#include <skalibs/strerr2.h> + +#include "defaults.h" +#include "initctl.h" +#include "hpr.h" + +#define USAGE "s6-linux-init-telinit runlevel" +#define dieusage() strerr_dieusage(100, USAGE) + +int main (int argc, char const *const *argv, char const *const *envp) +{ + PROG = "s6-linux-init-telinit" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + int opt = subgetopt_r(argc, argv, "rc:p:s:m:d:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'r' : + case 'c' : + case 'p' : + case 's' : + case 'm' : + case 'd' : break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + + if (!argc) dieusage() ; + if (!argv[0][0] || argv[0][1]) dieusage() ; + if (argv[0][0] != 'S' && (argv[0][0] < '0' || argv[0][0] > '6')) dieusage() ; + if (!hpr_send(argv[0], 1)) strerr_diefu2sys(111, "write to ", INITCTL) ; + return 0 ; +} |