From cea4fdf5786a1d9e83bb82cf997cdd939906b7f4 Mon Sep 17 00:00:00 2001 From: Mike Hibler Date: Fri, 20 Oct 2017 15:14:46 -0600 Subject: [PATCH] Old: hacks for FreeBSD 10.4 support. --- clientside/configure | 1 + clientside/configure.ac | 1 + .../tmcc/freebsd/init/10.4/GNUmakefile.in | 30 + .../tmcc/freebsd/init/10.4/getmntopts.c | 197 ++ clientside/tmcc/freebsd/init/10.4/init.c | 2078 +++++++++++++++++ clientside/tmcc/freebsd/init/10.4/mntopts.h | 102 + clientside/tmcc/freebsd/init/10.4/pathnames.h | 43 + clientside/tmcc/freebsd/init/GNUmakefile.in | 6 +- configure | 1 + configure.ac | 1 + 10 files changed, 2459 insertions(+), 1 deletion(-) create mode 100644 clientside/tmcc/freebsd/init/10.4/GNUmakefile.in create mode 100644 clientside/tmcc/freebsd/init/10.4/getmntopts.c create mode 100644 clientside/tmcc/freebsd/init/10.4/init.c create mode 100644 clientside/tmcc/freebsd/init/10.4/mntopts.h create mode 100644 clientside/tmcc/freebsd/init/10.4/pathnames.h diff --git a/clientside/configure b/clientside/configure index 56ca6811c..529ea8f79 100755 --- a/clientside/configure +++ b/clientside/configure @@ -4558,6 +4558,7 @@ outfiles="Makeconf GNUmakefile setversion \ tmcc/freebsd/init/9/GNUmakefile \ tmcc/freebsd/init/10/GNUmakefile \ tmcc/freebsd/init/10.3/GNUmakefile \ + tmcc/freebsd/init/10.4/GNUmakefile \ tmcc/freebsd/init/11/GNUmakefile \ tmcc/freebsd/init/12/GNUmakefile \ tmcc/freebsd/supfile tmcc/freebsd/sethostname \ diff --git a/clientside/configure.ac b/clientside/configure.ac index bb841b7ef..6b26a19c4 100644 --- a/clientside/configure.ac +++ b/clientside/configure.ac @@ -285,6 +285,7 @@ outfiles="Makeconf GNUmakefile setversion \ tmcc/freebsd/init/9/GNUmakefile \ tmcc/freebsd/init/10/GNUmakefile \ tmcc/freebsd/init/10.3/GNUmakefile \ + tmcc/freebsd/init/10.4/GNUmakefile \ tmcc/freebsd/init/11/GNUmakefile \ tmcc/freebsd/init/12/GNUmakefile \ tmcc/freebsd/supfile tmcc/freebsd/sethostname \ diff --git a/clientside/tmcc/freebsd/init/10.4/GNUmakefile.in b/clientside/tmcc/freebsd/init/10.4/GNUmakefile.in new file mode 100644 index 000000000..3f89d7655 --- /dev/null +++ b/clientside/tmcc/freebsd/init/10.4/GNUmakefile.in @@ -0,0 +1,30 @@ +# +# Insert Copyright Here. +# +SRCDIR = @srcdir@ +TESTBED_SRCDIR = @top_srcdir@ +OBJDIR = @top_builddir@ +SUBDIR = $(subst $(TESTBED_SRCDIR)/,,$(SRCDIR)) + +include $(OBJDIR)/Makeconf + +all: +client: init + +include $(TESTBED_SRCDIR)/GNUmakerules + +CFLAGS += -DDEBUGSHELL -DSECURE -DLOGIN_CAP -DCOMPAT_SYSV_INIT -DTESTBED + +init.c: pathnames.h mntopts.h +getmntopts.c: mntopts.h + +init: init.c getmntopts.c + $(CC) $(CFLAGS) -static -o init $^ -lutil -lcrypt + +install: + +client-install: client + install -s -o root -g wheel -m 555 -b -B.bak -fschg -S init $(DESTDIR)/sbin/init + +clean: + rm -f *.o core init diff --git a/clientside/tmcc/freebsd/init/10.4/getmntopts.c b/clientside/tmcc/freebsd/init/10.4/getmntopts.c new file mode 100644 index 000000000..05eda5953 --- /dev/null +++ b/clientside/tmcc/freebsd/init/10.4/getmntopts.c @@ -0,0 +1,197 @@ +/*- + * Copyright (c) 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if 0 +#ifndef lint +static char sccsid[] = "@(#)getmntopts.c 8.3 (Berkeley) 3/29/95"; +#endif /* not lint */ +#endif +#include +__FBSDID("$FreeBSD: releng/10.4/sbin/mount/getmntopts.c 310378 2016-12-21 23:16:58Z brooks $"); + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "mntopts.h" + +int getmnt_silent = 0; + +void +getmntopts(const char *options, const struct mntopt *m0, int *flagp, + int *altflagp) +{ + const struct mntopt *m; + int negative, len; + char *opt, *optbuf, *p; + int *thisflagp; + + /* Copy option string, since it is about to be torn asunder... */ + if ((optbuf = strdup(options)) == NULL) + err(1, NULL); + + for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) { + /* Check for "no" prefix. */ + if (opt[0] == 'n' && opt[1] == 'o') { + negative = 1; + opt += 2; + } else + negative = 0; + + /* + * for options with assignments in them (ie. quotas) + * ignore the assignment as it's handled elsewhere + */ + p = strchr(opt, '='); + if (p != NULL) + *++p = '\0'; + + /* Scan option table. */ + for (m = m0; m->m_option != NULL; ++m) { + len = strlen(m->m_option); + if (strncasecmp(opt, m->m_option, len) == 0) + if (opt[len] == '\0' || opt[len] == '=') + break; + } + + /* Save flag, or fail if option is not recognized. */ + if (m->m_option) { + thisflagp = m->m_altloc ? altflagp : flagp; + if (negative == m->m_inverse) + *thisflagp |= m->m_flag; + else + *thisflagp &= ~m->m_flag; + } else if (!getmnt_silent) { + errx(1, "-o %s: option not supported", opt); + } + } + + free(optbuf); +} + +void +rmslashes(char *rrpin, char *rrpout) +{ + char *rrpoutstart; + + *rrpout = *rrpin; + for (rrpoutstart = rrpout; *rrpin != '\0'; *rrpout++ = *rrpin++) { + + /* skip all double slashes */ + while (*rrpin == '/' && *(rrpin + 1) == '/') + rrpin++; + } + + /* remove trailing slash if necessary */ + if (rrpout - rrpoutstart > 1 && *(rrpout - 1) == '/') + *(rrpout - 1) = '\0'; + else + *rrpout = '\0'; +} + +int +checkpath(const char *path, char *resolved) +{ + struct stat sb; + + if (realpath(path, resolved) == NULL || stat(resolved, &sb) != 0) + return (1); + if (!S_ISDIR(sb.st_mode)) { + errno = ENOTDIR; + return (1); + } + return (0); +} + +void +build_iovec(struct iovec **iov, int *iovlen, const char *name, void *val, + size_t len) +{ + int i; + + if (*iovlen < 0) + return; + i = *iovlen; + *iov = realloc(*iov, sizeof **iov * (i + 2)); + if (*iov == NULL) { + *iovlen = -1; + return; + } + (*iov)[i].iov_base = strdup(name); + (*iov)[i].iov_len = strlen(name) + 1; + i++; + (*iov)[i].iov_base = val; + if (len == (size_t)-1) { + if (val != NULL) + len = strlen(val) + 1; + else + len = 0; + } + (*iov)[i].iov_len = (int)len; + *iovlen = ++i; +} + +/* + * This function is needed for compatibility with parameters + * which used to use the mount_argf() command for the old mount() syscall. + */ +void +build_iovec_argf(struct iovec **iov, int *iovlen, const char *name, + const char *fmt, ...) +{ + va_list ap; + char val[255] = { 0 }; + + va_start(ap, fmt); + vsnprintf(val, sizeof(val), fmt, ap); + va_end(ap); + build_iovec(iov, iovlen, name, strdup(val), (size_t)-1); +} + +/* + * Free the iovec and reset to NULL with zero length. Useful for calling + * nmount in a loop. + */ +void +free_iovec(struct iovec **iov, int *iovlen) +{ + int i; + + for (i = 0; i < *iovlen; i++) + free((*iov)[i].iov_base); + free(*iov); +} diff --git a/clientside/tmcc/freebsd/init/10.4/init.c b/clientside/tmcc/freebsd/init/10.4/init.c new file mode 100644 index 000000000..50db360bd --- /dev/null +++ b/clientside/tmcc/freebsd/init/10.4/init.c @@ -0,0 +1,2078 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Donn Seeley at Berkeley Software Design, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static const char copyright[] = +"@(#) Copyright (c) 1991, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)init.c 8.1 (Berkeley) 7/15/93"; +#endif +static const char rcsid[] = + "$FreeBSD: releng/10.4/sbin/init/init.c 321907 2017-08-02 05:47:26Z delphij $"; +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef SECURE +#include +#endif + +#ifdef LOGIN_CAP +#include +#endif + +#include "mntopts.h" +#include "pathnames.h" + +/* + * Sleep times; used to prevent thrashing. + */ +#define GETTY_SPACING 5 /* N secs minimum getty spacing */ +#define GETTY_SLEEP 30 /* sleep N secs after spacing problem */ +#define GETTY_NSPACE 3 /* max. spacing count to bring reaction */ +#define WINDOW_WAIT 3 /* wait N secs after starting window */ +#define STALL_TIMEOUT 30 /* wait N secs after warning */ +#define DEATH_WATCH 10 /* wait N secs for procs to die */ +#define DEATH_SCRIPT 120 /* wait for 2min for /etc/rc.shutdown */ +#define RESOURCE_RC "daemon" +#define RESOURCE_WINDOW "default" +#define RESOURCE_GETTY "default" + +static void handle(sig_t, ...); +static void delset(sigset_t *, ...); + +static void stall(const char *, ...) __printflike(1, 2); +static void warning(const char *, ...) __printflike(1, 2); +static void emergency(const char *, ...) __printflike(1, 2); +static void disaster(int); +static void badsys(int); +static void revoke_ttys(void); +static int runshutdown(void); +static char *strk(char *); + +/* + * We really need a recursive typedef... + * The following at least guarantees that the return type of (*state_t)() + * is sufficiently wide to hold a function pointer. + */ +typedef long (*state_func_t)(void); +typedef state_func_t (*state_t)(void); + +static state_func_t single_user(void); +static state_func_t runcom(void); +static state_func_t read_ttys(void); +static state_func_t multi_user(void); +static state_func_t clean_ttys(void); +static state_func_t catatonia(void); +static state_func_t death(void); +static state_func_t death_single(void); +static state_func_t reroot(void); +static state_func_t reroot_phase_two(void); + +static state_func_t run_script(const char *); + +static enum { AUTOBOOT, FASTBOOT } runcom_mode = AUTOBOOT; +#define FALSE 0 +#define TRUE 1 + +static int Reboot = FALSE; +static int howto = RB_AUTOBOOT; + +static int devfs; + +static void transition(state_t); +static state_t requested_transition; +static state_t current_state = death_single; + +static void open_console(void); +static const char *get_shell(void); +static void write_stderr(const char *message); + +typedef struct init_session { + int se_index; /* index of entry in ttys file */ + pid_t se_process; /* controlling process */ + time_t se_started; /* used to avoid thrashing */ + int se_flags; /* status of session */ +#define SE_SHUTDOWN 0x1 /* session won't be restarted */ +#define SE_PRESENT 0x2 /* session is in /etc/ttys */ + int se_nspace; /* spacing count */ + char *se_device; /* filename of port */ + char *se_getty; /* what to run on that port */ + char *se_getty_argv_space; /* pre-parsed argument array space */ + char **se_getty_argv; /* pre-parsed argument array */ + char *se_window; /* window system (started only once) */ + char *se_window_argv_space; /* pre-parsed argument array space */ + char **se_window_argv; /* pre-parsed argument array */ + char *se_type; /* default terminal type */ + struct init_session *se_prev; + struct init_session *se_next; +} session_t; + +static void free_session(session_t *); +static session_t *new_session(session_t *, int, struct ttyent *); +static session_t *sessions; + +static char **construct_argv(char *); +static void start_window_system(session_t *); +static void collect_child(pid_t); +static pid_t start_getty(session_t *); +static void transition_handler(int); +static void alrm_handler(int); +static void setsecuritylevel(int); +static int getsecuritylevel(void); +static int setupargv(session_t *, struct ttyent *); +#ifdef LOGIN_CAP +static void setprocresources(const char *); +#endif +static int clang; + +static int start_session_db(void); +static void add_session(session_t *); +static void del_session(session_t *); +static session_t *find_session(pid_t); +static DB *session_db; + +/* + * The mother of all processes. + */ +int +main(int argc, char *argv[]) +{ + state_t initial_transition = runcom; + char kenv_value[PATH_MAX]; + int c, error; + struct sigaction sa; + sigset_t mask; + + /* Dispose of random users. */ + if (getuid() != 0) + errx(1, "%s", strerror(EPERM)); + + /* System V users like to reexec init. */ + if (getpid() != 1) { +#ifdef COMPAT_SYSV_INIT + /* So give them what they want */ + if (argc > 1) { + if (strlen(argv[1]) == 1) { + char runlevel = *argv[1]; + int sig; + + switch (runlevel) { + case '0': /* halt + poweroff */ + sig = SIGUSR2; + break; + case '1': /* single-user */ + sig = SIGTERM; + break; + case '6': /* reboot */ + sig = SIGINT; + break; + case 'c': /* block further logins */ + sig = SIGTSTP; + break; + case 'q': /* rescan /etc/ttys */ + sig = SIGHUP; + break; + case 'r': /* remount root */ + sig = SIGEMT; + break; + default: + goto invalid; + } + kill(1, sig); + _exit(0); + } else +invalid: + errx(1, "invalid run-level ``%s''", argv[1]); + } else +#endif + errx(1, "already running"); + } + /* + * Note that this does NOT open a file... + * Does 'init' deserve its own facility number? + */ + openlog("init", LOG_CONS|LOG_ODELAY, LOG_AUTH); + + /* + * Create an initial session. + */ + if (setsid() < 0 && (errno != EPERM || getsid(0) != 1)) + warning("initial setsid() failed: %m"); + + /* + * Establish an initial user so that programs running + * single user do not freak out and die (like passwd). + */ + if (setlogin("root") < 0) + warning("setlogin() failed: %m"); + + /* + * This code assumes that we always get arguments through flags, + * never through bits set in some random machine register. + */ + while ((c = getopt(argc, argv, "dsfr")) != -1) + switch (c) { + case 'd': + devfs = 1; + break; + case 's': + initial_transition = single_user; + break; + case 'f': + runcom_mode = FASTBOOT; + break; + case 'r': + initial_transition = reroot_phase_two; + break; + default: + warning("unrecognized flag '-%c'", c); + break; + } + + if (optind != argc) + warning("ignoring excess arguments"); + + /* + * We catch or block signals rather than ignore them, + * so that they get reset on exec. + */ + handle(badsys, SIGSYS, 0); + handle(disaster, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGXCPU, + SIGXFSZ, 0); + handle(transition_handler, SIGHUP, SIGINT, SIGEMT, SIGTERM, SIGTSTP, + SIGUSR1, SIGUSR2, 0); + handle(alrm_handler, SIGALRM, 0); + sigfillset(&mask); + delset(&mask, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGSYS, + SIGXCPU, SIGXFSZ, SIGHUP, SIGINT, SIGEMT, SIGTERM, SIGTSTP, + SIGALRM, SIGUSR1, SIGUSR2, 0); + sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0); + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = SIG_IGN; + sigaction(SIGTTIN, &sa, (struct sigaction *)0); + sigaction(SIGTTOU, &sa, (struct sigaction *)0); + + /* + * Paranoia. + */ + close(0); + close(1); + close(2); + + if (kenv(KENV_GET, "init_script", kenv_value, sizeof(kenv_value)) > 0) { + state_func_t next_transition; + + if ((next_transition = run_script(kenv_value)) != 0) + initial_transition = (state_t) next_transition; + } + + if (kenv(KENV_GET, "init_chroot", kenv_value, sizeof(kenv_value)) > 0) { + if (chdir(kenv_value) != 0 || chroot(".") != 0) + warning("Can't chroot to %s: %m", kenv_value); + } + + /* + * Additional check if devfs needs to be mounted: + * If "/" and "/dev" have the same device number, + * then it hasn't been mounted yet. + */ + if (!devfs) { + struct stat stst; + dev_t root_devno; + + stat("/", &stst); + root_devno = stst.st_dev; + if (stat("/dev", &stst) != 0) + warning("Can't stat /dev: %m"); + else if (stst.st_dev == root_devno) + devfs++; + } + + if (devfs) { + struct iovec iov[4]; + char *s; + int i; + + char _fstype[] = "fstype"; + char _devfs[] = "devfs"; + char _fspath[] = "fspath"; + char _path_dev[]= _PATH_DEV; + + iov[0].iov_base = _fstype; + iov[0].iov_len = sizeof(_fstype); + iov[1].iov_base = _devfs; + iov[1].iov_len = sizeof(_devfs); + iov[2].iov_base = _fspath; + iov[2].iov_len = sizeof(_fspath); + /* + * Try to avoid the trailing slash in _PATH_DEV. + * Be *very* defensive. + */ + s = strdup(_PATH_DEV); + if (s != NULL) { + i = strlen(s); + if (i > 0 && s[i - 1] == '/') + s[i - 1] = '\0'; + iov[3].iov_base = s; + iov[3].iov_len = strlen(s) + 1; + } else { + iov[3].iov_base = _path_dev; + iov[3].iov_len = sizeof(_path_dev); + } + nmount(iov, 4, 0); + if (s != NULL) + free(s); + } + + if (initial_transition != reroot_phase_two) { + /* + * Unmount reroot leftovers. This runs after init(8) + * gets reexecuted after reroot_phase_two() is done. + */ + error = unmount(_PATH_REROOT, MNT_FORCE); + if (error != 0 && errno != EINVAL) + warning("Cannot unmount %s: %m", _PATH_REROOT); + } + + /* + * Start the state machine. + */ + transition(initial_transition); + + /* + * Should never reach here. + */ + return 1; +} + +/* + * Associate a function with a signal handler. + */ +static void +handle(sig_t handler, ...) +{ + int sig; + struct sigaction sa; + sigset_t mask_everything; + va_list ap; + va_start(ap, handler); + + sa.sa_handler = handler; + sigfillset(&mask_everything); + + while ((sig = va_arg(ap, int)) != 0) { + sa.sa_mask = mask_everything; + /* XXX SA_RESTART? */ + sa.sa_flags = sig == SIGCHLD ? SA_NOCLDSTOP : 0; + sigaction(sig, &sa, (struct sigaction *) 0); + } + va_end(ap); +} + +/* + * Delete a set of signals from a mask. + */ +static void +delset(sigset_t *maskp, ...) +{ + int sig; + va_list ap; + va_start(ap, maskp); + + while ((sig = va_arg(ap, int)) != 0) + sigdelset(maskp, sig); + va_end(ap); +} + +/* + * Log a message and sleep for a while (to give someone an opportunity + * to read it and to save log or hardcopy output if the problem is chronic). + * NB: should send a message to the session logger to avoid blocking. + */ +static void +stall(const char *message, ...) +{ + va_list ap; + va_start(ap, message); + + vsyslog(LOG_ALERT, message, ap); + va_end(ap); + sleep(STALL_TIMEOUT); +} + +/* + * Like stall(), but doesn't sleep. + * If cpp had variadic macros, the two functions could be #defines for another. + * NB: should send a message to the session logger to avoid blocking. + */ +static void +warning(const char *message, ...) +{ + va_list ap; + va_start(ap, message); + + vsyslog(LOG_ALERT, message, ap); + va_end(ap); +} + +/* + * Log an emergency message. + * NB: should send a message to the session logger to avoid blocking. + */ +static void +emergency(const char *message, ...) +{ + va_list ap; + va_start(ap, message); + + vsyslog(LOG_EMERG, message, ap); + va_end(ap); +} + +/* + * Catch a SIGSYS signal. + * + * These may arise if a system does not support sysctl. + * We tolerate up to 25 of these, then throw in the towel. + */ +static void +badsys(int sig) +{ + static int badcount = 0; + + if (badcount++ < 25) + return; + disaster(sig); +} + +/* + * Catch an unexpected signal. + */ +static void +disaster(int sig) +{ + + emergency("fatal signal: %s", + (unsigned)sig < NSIG ? sys_siglist[sig] : "unknown signal"); + + sleep(STALL_TIMEOUT); + _exit(sig); /* reboot */ +} + +/* + * Get the security level of the kernel. + */ +static int +getsecuritylevel(void) +{ +#ifdef KERN_SECURELVL + int name[2], curlevel; + size_t len; + + name[0] = CTL_KERN; + name[1] = KERN_SECURELVL; + len = sizeof curlevel; + if (sysctl(name, 2, &curlevel, &len, NULL, 0) == -1) { + emergency("cannot get kernel security level: %s", + strerror(errno)); + return (-1); + } + return (curlevel); +#else + return (-1); +#endif +} + +/* + * Set the security level of the kernel. + */ +static void +setsecuritylevel(int newlevel) +{ +#ifdef KERN_SECURELVL + int name[2], curlevel; + + curlevel = getsecuritylevel(); + if (newlevel == curlevel) + return; + name[0] = CTL_KERN; + name[1] = KERN_SECURELVL; + if (sysctl(name, 2, NULL, NULL, &newlevel, sizeof newlevel) == -1) { + emergency( + "cannot change kernel security level from %d to %d: %s", + curlevel, newlevel, strerror(errno)); + return; + } +#ifdef SECURE + warning("kernel security level changed from %d to %d", + curlevel, newlevel); +#endif +#endif +} + +/* + * Change states in the finite state machine. + * The initial state is passed as an argument. + */ +static void +transition(state_t s) +{ + + current_state = s; + for (;;) + current_state = (state_t) (*current_state)(); +} + +/* + * Start a session and allocate a controlling terminal. + * Only called by children of init after forking. + */ +static void +open_console(void) +{ + int fd; + + /* + * Try to open /dev/console. Open the device with O_NONBLOCK to + * prevent potential blocking on a carrier. + */ + revoke(_PATH_CONSOLE); + if ((fd = open(_PATH_CONSOLE, O_RDWR | O_NONBLOCK)) != -1) { + (void)fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK); + if (login_tty(fd) == 0) + return; + close(fd); + } + + /* No luck. Log output to file if possible. */ + if ((fd = open(_PATH_DEVNULL, O_RDWR)) == -1) { + stall("cannot open null device."); + _exit(1); + } + if (fd != STDIN_FILENO) { + dup2(fd, STDIN_FILENO); + close(fd); + } + fd = open(_PATH_INITLOG, O_WRONLY | O_APPEND | O_CREAT, 0644); + if (fd == -1) + dup2(STDIN_FILENO, STDOUT_FILENO); + else if (fd != STDOUT_FILENO) { + dup2(fd, STDOUT_FILENO); + close(fd); + } + dup2(STDOUT_FILENO, STDERR_FILENO); +} + +static const char * +get_shell(void) +{ + static char kenv_value[PATH_MAX]; + + if (kenv(KENV_GET, "init_shell", kenv_value, sizeof(kenv_value)) > 0) + return kenv_value; + else + return _PATH_BSHELL; +} + +static void +write_stderr(const char *message) +{ + + write(STDERR_FILENO, message, strlen(message)); +} + +static int +read_file(const char *path, void **bufp, size_t *bufsizep) +{ + struct stat sb; + size_t bufsize; + void *buf; + ssize_t nbytes; + int error, fd; + + fd = open(path, O_RDONLY); + if (fd < 0) { + emergency("%s: %s", path, strerror(errno)); + return (-1); + } + + error = fstat(fd, &sb); + if (error != 0) { + emergency("fstat: %s", strerror(errno)); + close(fd); + return (error); + } + + bufsize = sb.st_size; + buf = malloc(bufsize); + if (buf == NULL) { + emergency("malloc: %s", strerror(errno)); + close(fd); + return (error); + } + + nbytes = read(fd, buf, bufsize); + if (nbytes != (ssize_t)bufsize) { + emergency("read: %s", strerror(errno)); + close(fd); + free(buf); + return (error); + } + + error = close(fd); + if (error != 0) { + emergency("close: %s", strerror(errno)); + free(buf); + return (error); + } + + *bufp = buf; + *bufsizep = bufsize; + + return (0); +} + +static int +create_file(const char *path, const void *buf, size_t bufsize) +{ + ssize_t nbytes; + int error, fd; + + fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0700); + if (fd < 0) { + emergency("%s: %s", path, strerror(errno)); + return (-1); + } + + nbytes = write(fd, buf, bufsize); + if (nbytes != (ssize_t)bufsize) { + emergency("write: %s", strerror(errno)); + close(fd); + return (-1); + } + + error = close(fd); + if (error != 0) { + emergency("close: %s", strerror(errno)); + return (-1); + } + + return (0); +} + +static int +mount_tmpfs(const char *fspath) +{ + struct iovec *iov; + char errmsg[255]; + int error, iovlen; + + iov = NULL; + iovlen = 0; + memset(errmsg, 0, sizeof(errmsg)); + build_iovec(&iov, &iovlen, "fstype", + __DECONST(void *, "tmpfs"), (size_t)-1); + build_iovec(&iov, &iovlen, "fspath", + __DECONST(void *, fspath), (size_t)-1); + build_iovec(&iov, &iovlen, "errmsg", + errmsg, sizeof(errmsg)); + + error = nmount(iov, iovlen, 0); + if (error != 0) { + if (*errmsg != '\0') { + emergency("cannot mount tmpfs on %s: %s: %s", + fspath, errmsg, strerror(errno)); + } else { + emergency("cannot mount tmpfs on %s: %s", + fspath, strerror(errno)); + } + return (error); + } + return (0); +} + +static state_func_t +reroot(void) +{ + void *buf; + char init_path[PATH_MAX]; + size_t bufsize, init_path_len; + int error, name[4]; + + buf = NULL; + bufsize = 0; + + name[0] = CTL_KERN; + name[1] = KERN_PROC; + name[2] = KERN_PROC_PATHNAME; + name[3] = -1; + init_path_len = sizeof(init_path); + error = sysctl(name, 4, init_path, &init_path_len, NULL, 0); + if (error != 0) { + emergency("failed to get kern.proc.pathname: %s", + strerror(errno)); + goto out; + } + + revoke_ttys(); + runshutdown(); + + /* + * Make sure nobody can interfere with our scheme. + */ + error = kill(-1, SIGKILL); + if (error != 0) { + emergency("kill(2) failed: %s", strerror(errno)); + goto out; + } + + /* + * Copy the init binary into tmpfs, so that we can unmount + * the old rootfs without committing suicide. + */ + error = read_file(init_path, &buf, &bufsize); + if (error != 0) + goto out; + error = mount_tmpfs(_PATH_REROOT); + if (error != 0) + goto out; + error = create_file(_PATH_REROOT_INIT, buf, bufsize); + if (error != 0) + goto out; + + /* + * Execute the temporary init. + */ + execl(_PATH_REROOT_INIT, _PATH_REROOT_INIT, "-r", NULL); + emergency("cannot exec %s: %s", _PATH_REROOT_INIT, strerror(errno)); + +out: + emergency("reroot failed; going to single user mode"); + free(buf); + return (state_func_t) single_user; +} + +static state_func_t +reroot_phase_two(void) +{ + char init_path[PATH_MAX], *path, *path_component; + size_t init_path_len; + int nbytes, error; + + /* + * Ask the kernel to mount the new rootfs. + */ + error = reboot(RB_REROOT); + if (error != 0) { + emergency("RB_REBOOT failed: %s", strerror(errno)); + goto out; + } + + /* + * Figure out where the destination init(8) binary is. Note that + * the path could be different than what we've started with. Use + * the value from kenv, if set, or the one from sysctl otherwise. + * The latter defaults to a hardcoded value, but can be overridden + * by a build time option. + */ + nbytes = kenv(KENV_GET, "init_path", init_path, sizeof(init_path)); + if (nbytes <= 0) { + init_path_len = sizeof(init_path); + error = sysctlbyname("kern.init_path", + init_path, &init_path_len, NULL, 0); + if (error != 0) { + emergency("failed to retrieve kern.init_path: %s", + strerror(errno)); + goto out; + } + } + + /* + * Repeat the init search logic from sys/kern/init_path.c + */ + path_component = init_path; + while ((path = strsep(&path_component, ":")) != NULL) { + /* + * Execute init(8) from the new rootfs. + */ + execl(path, path, NULL); + } + emergency("cannot exec init from %s: %s", init_path, strerror(errno)); + +out: + emergency("reroot failed; going to single user mode"); + return (state_func_t) single_user; +} + +/* + * Bring the system up single user. + */ +static state_func_t +single_user(void) +{ + pid_t pid, wpid; + int status; + sigset_t mask; + const char *shell; + char *argv[2]; + struct timeval tv, tn; +#ifdef SECURE + struct ttyent *typ; + struct passwd *pp; + static const char banner[] = + "Enter root password, or ^D to go multi-user\n"; + char *clear, *password; +#endif +#ifdef DEBUGSHELL + char altshell[128]; +#endif + + if (Reboot) { + /* Instead of going single user, let's reboot the machine */ + sync(); + if (reboot(howto) == -1) { + emergency("reboot(%#x) failed, %s", howto, + strerror(errno)); + _exit(1); /* panic and reboot */ + } + warning("reboot(%#x) returned", howto); + _exit(0); /* panic as well */ + } + + shell = get_shell(); + + if ((pid = fork()) == 0) { + /* + * Start the single user session. + */ + open_console(); + +#ifdef TESTBED +#define TERMCMD "/bootcmd" + + if (access(TERMCMD, F_OK) == 0) { + FILE *fp; + char cmd[256], *bp; + char *myargv[3]; + + /* + * Very simple; the file contains the path of a + * command to run. No arguments supported, sorry. + */ + if ((fp = fopen(TERMCMD, "r")) == NULL) { + /* Lets avoid loops! */ + unlink(TERMCMD); + goto skip; + } + + if (fgets(cmd, sizeof(cmd), fp) == NULL) { + fclose(fp); + /* Lets avoid loops! */ + unlink(TERMCMD); + goto skip; + } + fclose(fp); + + /* Lets avoid loops! */ + unlink(TERMCMD); + + if ((bp = rindex(cmd, '\n'))) + *bp = '\0'; + + if (access(cmd, X_OK) != 0) { + emergency("%s does not exist!", cmd); + goto skip; + } + + /* See comment below */ + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0); + + char name[] = "-sh"; + + myargv[0] = name; + myargv[1] = cmd; + myargv[2] = 0; + + execv(_PATH_BSHELL, myargv); + stall("can't exec %s for %s: %m", _PATH_BSHELL, cmd); + } + /* + * If something goes wrong, we want to sit in single user mode + * so that we might catch the error. Not sure, might have to + * do something fancier, like perhaps add a state transition + * for this + */ + skip: +#endif + +#ifdef SECURE + /* + * Check the root password. + * We don't care if the console is 'on' by default; + * it's the only tty that can be 'off' and 'secure'. + */ + typ = getttynam("console"); + pp = getpwnam("root"); + if (typ && (typ->ty_status & TTY_SECURE) == 0 && + pp && *pp->pw_passwd) { + write_stderr(banner); + for (;;) { + clear = getpass("Password:"); + if (clear == 0 || *clear == '\0') + _exit(0); + password = crypt(clear, pp->pw_passwd); + bzero(clear, _PASSWORD_LEN); + if (password == NULL || + strcmp(password, pp->pw_passwd) == 0) + break; + warning("single-user login failed\n"); + } + } + endttyent(); + endpwent(); +#endif /* SECURE */ + +#ifdef DEBUGSHELL + { + char *cp = altshell; + int num; + +#define SHREQUEST "Enter full pathname of shell or RETURN for " + write_stderr(SHREQUEST); + write_stderr(shell); + write_stderr(": "); + while ((num = read(STDIN_FILENO, cp, 1)) != -1 && + num != 0 && *cp != '\n' && cp < &altshell[127]) + cp++; + *cp = '\0'; + if (altshell[0] != '\0') + shell = altshell; + } +#endif /* DEBUGSHELL */ + + /* + * Unblock signals. + * We catch all the interesting ones, + * and those are reset to SIG_DFL on exec. + */ + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0); + + /* + * Fire off a shell. + * If the default one doesn't work, try the Bourne shell. + */ + + char name[] = "-sh"; + + argv[0] = name; + argv[1] = 0; + execv(shell, argv); + emergency("can't exec %s for single user: %m", shell); + execv(_PATH_BSHELL, argv); + emergency("can't exec %s for single user: %m", _PATH_BSHELL); + sleep(STALL_TIMEOUT); + _exit(1); + } + + if (pid == -1) { + /* + * We are seriously hosed. Do our best. + */ + emergency("can't fork single-user shell, trying again"); + while (waitpid(-1, (int *) 0, WNOHANG) > 0) + continue; + return (state_func_t) single_user; + } + + requested_transition = 0; + do { + if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1) + collect_child(wpid); + if (wpid == -1) { + if (errno == EINTR) + continue; + warning("wait for single-user shell failed: %m; restarting"); + return (state_func_t) single_user; + } + if (wpid == pid && WIFSTOPPED(status)) { + warning("init: shell stopped, restarting\n"); + kill(pid, SIGCONT); + wpid = -1; + } + } while (wpid != pid && !requested_transition); + + if (requested_transition) + return (state_func_t) requested_transition; + + if (!WIFEXITED(status)) { + if (WTERMSIG(status) == SIGKILL) { + /* + * reboot(8) killed shell? + */ + warning("single user shell terminated."); + gettimeofday(&tv, NULL); + tn = tv; + tv.tv_sec += STALL_TIMEOUT; + while (tv.tv_sec > tn.tv_sec || (tv.tv_sec == + tn.tv_sec && tv.tv_usec > tn.tv_usec)) { + sleep(1); + gettimeofday(&tn, NULL); + } + _exit(0); + } else { + warning("single user shell terminated, restarting"); + return (state_func_t) single_user; + } + } + + runcom_mode = FASTBOOT; + return (state_func_t) runcom; +} + +/* + * Run the system startup script. + */ +static state_func_t +runcom(void) +{ + state_func_t next_transition; + + if ((next_transition = run_script(_PATH_RUNCOM)) != 0) + return next_transition; + + runcom_mode = AUTOBOOT; /* the default */ + return (state_func_t) read_ttys; +} + +/* + * Run a shell script. + * Returns 0 on success, otherwise the next transition to enter: + * - single_user if fork/execv/waitpid failed, or if the script + * terminated with a signal or exit code != 0. + * - death_single if a SIGTERM was delivered to init(8). + */ +static state_func_t +run_script(const char *script) +{ + pid_t pid, wpid; + int status; + char *argv[4]; + const char *shell; + struct sigaction sa; + + shell = get_shell(); + + if ((pid = fork()) == 0) { + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = SIG_IGN; + sigaction(SIGTSTP, &sa, (struct sigaction *)0); + sigaction(SIGHUP, &sa, (struct sigaction *)0); + + open_console(); + + char _sh[] = "sh"; + char _autoboot[] = "autoboot"; + + argv[0] = _sh; + argv[1] = __DECONST(char *, script); + argv[2] = runcom_mode == AUTOBOOT ? _autoboot : 0; + argv[3] = 0; + + sigprocmask(SIG_SETMASK, &sa.sa_mask, (sigset_t *) 0); + +#ifdef LOGIN_CAP + setprocresources(RESOURCE_RC); +#endif + execv(shell, argv); + stall("can't exec %s for %s: %m", shell, script); + _exit(1); /* force single user mode */ + } + + if (pid == -1) { + emergency("can't fork for %s on %s: %m", shell, script); + while (waitpid(-1, (int *) 0, WNOHANG) > 0) + continue; + sleep(STALL_TIMEOUT); + return (state_func_t) single_user; + } + + /* + * Copied from single_user(). This is a bit paranoid. + */ + requested_transition = 0; + do { + if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1) + collect_child(wpid); + if (wpid == -1) { + if (requested_transition == death_single || + requested_transition == reroot) + return (state_func_t) requested_transition; + if (errno == EINTR) + continue; + warning("wait for %s on %s failed: %m; going to " + "single user mode", shell, script); + return (state_func_t) single_user; + } + if (wpid == pid && WIFSTOPPED(status)) { + warning("init: %s on %s stopped, restarting\n", + shell, script); + kill(pid, SIGCONT); + wpid = -1; + } + } while (wpid != pid); + + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM && + requested_transition == catatonia) { + /* /etc/rc executed /sbin/reboot; wait for the end quietly */ + sigset_t s; + + sigfillset(&s); + for (;;) + sigsuspend(&s); + } + + if (!WIFEXITED(status)) { + warning("%s on %s terminated abnormally, going to single " + "user mode", shell, script); + return (state_func_t) single_user; + } + + if (WEXITSTATUS(status)) + return (state_func_t) single_user; + + return (state_func_t) 0; +} + +/* + * Open the session database. + * + * NB: We could pass in the size here; is it necessary? + */ +static int +start_session_db(void) +{ + if (session_db && (*session_db->close)(session_db)) + emergency("session database close: %s", strerror(errno)); + if ((session_db = dbopen(NULL, O_RDWR, 0, DB_HASH, NULL)) == 0) { + emergency("session database open: %s", strerror(errno)); + return (1); + } + return (0); + +} + +/* + * Add a new login session. + */ +static void +add_session(session_t *sp) +{ + DBT key; + DBT data; + + key.data = &sp->se_process; + key.size = sizeof sp->se_process; + data.data = &sp; + data.size = sizeof sp; + + if ((*session_db->put)(session_db, &key, &data, 0)) + emergency("insert %d: %s", sp->se_process, strerror(errno)); +} + +/* + * Delete an old login session. + */ +static void +del_session(session_t *sp) +{ + DBT key; + + key.data = &sp->se_process; + key.size = sizeof sp->se_process; + + if ((*session_db->del)(session_db, &key, 0)) + emergency("delete %d: %s", sp->se_process, strerror(errno)); +} + +/* + * Look up a login session by pid. + */ +static session_t * +find_session(pid_t pid) +{ + DBT key; + DBT data; + session_t *ret; + + key.data = &pid; + key.size = sizeof pid; + if ((*session_db->get)(session_db, &key, &data, 0) != 0) + return 0; + bcopy(data.data, (char *)&ret, sizeof(ret)); + return ret; +} + +/* + * Construct an argument vector from a command line. + */ +static char ** +construct_argv(char *command) +{ + int argc = 0; + char **argv = (char **) malloc(((strlen(command) + 1) / 2 + 1) + * sizeof (char *)); + + if ((argv[argc++] = strk(command)) == 0) { + free(argv); + return (NULL); + } + while ((argv[argc++] = strk((char *) 0)) != NULL) + continue; + return argv; +} + +/* + * Deallocate a session descriptor. + */ +static void +free_session(session_t *sp) +{ + free(sp->se_device); + if (sp->se_getty) { + free(sp->se_getty); + free(sp->se_getty_argv_space); + free(sp->se_getty_argv); + } + if (sp->se_window) { + free(sp->se_window); + free(sp->se_window_argv_space); + free(sp->se_window_argv); + } + if (sp->se_type) + free(sp->se_type); + free(sp); +} + +/* + * Allocate a new session descriptor. + * Mark it SE_PRESENT. + */ +static session_t * +new_session(session_t *sprev, int session_index, struct ttyent *typ) +{ + session_t *sp; + int fd; + + if ((typ->ty_status & TTY_ON) == 0 || + typ->ty_name == 0 || + typ->ty_getty == 0) + return 0; + + sp = (session_t *) calloc(1, sizeof (session_t)); + + sp->se_index = session_index; + sp->se_flags |= SE_PRESENT; + + if (asprintf(&sp->se_device, "%s%s", _PATH_DEV, typ->ty_name) < 0) + err(1, "asprintf"); + + /* + * Attempt to open the device, if we get "device not configured" + * then don't add the device to the session list. + */ + if ((fd = open(sp->se_device, O_RDONLY | O_NONBLOCK, 0)) < 0) { + if (errno == ENXIO) { + free_session(sp); + return (0); + } + } else + close(fd); + + if (setupargv(sp, typ) == 0) { + free_session(sp); + return (0); + } + + sp->se_next = 0; + if (sprev == 0) { + sessions = sp; + sp->se_prev = 0; + } else { + sprev->se_next = sp; + sp->se_prev = sprev; + } + + return sp; +} + +/* + * Calculate getty and if useful window argv vectors. + */ +static int +setupargv(session_t *sp, struct ttyent *typ) +{ + + if (sp->se_getty) { + free(sp->se_getty); + free(sp->se_getty_argv_space); + free(sp->se_getty_argv); + } + if (asprintf(&sp->se_getty, "%s %s", typ->ty_getty, typ->ty_name) < 0) + err(1, "asprintf"); + sp->se_getty_argv_space = strdup(sp->se_getty); + sp->se_getty_argv = construct_argv(sp->se_getty_argv_space); + if (sp->se_getty_argv == 0) { + warning("can't parse getty for port %s", sp->se_device); + free(sp->se_getty); + free(sp->se_getty_argv_space); + sp->se_getty = sp->se_getty_argv_space = 0; + return (0); + } + if (sp->se_window) { + free(sp->se_window); + free(sp->se_window_argv_space); + free(sp->se_window_argv); + } + sp->se_window = sp->se_window_argv_space = 0; + sp->se_window_argv = 0; + if (typ->ty_window) { + sp->se_window = strdup(typ->ty_window); + sp->se_window_argv_space = strdup(sp->se_window); + sp->se_window_argv = construct_argv(sp->se_window_argv_space); + if (sp->se_window_argv == 0) { + warning("can't parse window for port %s", + sp->se_device); + free(sp->se_window_argv_space); + free(sp->se_window); + sp->se_window = sp->se_window_argv_space = 0; + return (0); + } + } + if (sp->se_type) + free(sp->se_type); + sp->se_type = typ->ty_type ? strdup(typ->ty_type) : 0; + return (1); +} + +/* + * Walk the list of ttys and create sessions for each active line. + */ +static state_func_t +read_ttys(void) +{ + int session_index = 0; + session_t *sp, *snext; + struct ttyent *typ; + + /* + * Destroy any previous session state. + * There shouldn't be any, but just in case... + */ + for (sp = sessions; sp; sp = snext) { + snext = sp->se_next; + free_session(sp); + } + sessions = 0; + if (start_session_db()) + return (state_func_t) single_user; + + /* + * Allocate a session entry for each active port. + * Note that sp starts at 0. + */ + while ((typ = getttyent()) != NULL) + if ((snext = new_session(sp, ++session_index, typ)) != NULL) + sp = snext; + + endttyent(); + + return (state_func_t) multi_user; +} + +/* + * Start a window system running. + */ +static void +start_window_system(session_t *sp) +{ + pid_t pid; + sigset_t mask; + char term[64], *env[2]; + int status; + + if ((pid = fork()) == -1) { + emergency("can't fork for window system on port %s: %m", + sp->se_device); + /* hope that getty fails and we can try again */ + return; + } + if (pid) { + waitpid(-1, &status, 0); + return; + } + + /* reparent window process to the init to not make a zombie on exit */ + if ((pid = fork()) == -1) { + emergency("can't fork for window system on port %s: %m", + sp->se_device); + _exit(1); + } + if (pid) + _exit(0); + + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0); + + if (setsid() < 0) + emergency("setsid failed (window) %m"); + +#ifdef LOGIN_CAP + setprocresources(RESOURCE_WINDOW); +#endif + if (sp->se_type) { + /* Don't use malloc after fork */ + strcpy(term, "TERM="); + strlcat(term, sp->se_type, sizeof(term)); + env[0] = term; + env[1] = 0; + } + else + env[0] = 0; + execve(sp->se_window_argv[0], sp->se_window_argv, env); + stall("can't exec window system '%s' for port %s: %m", + sp->se_window_argv[0], sp->se_device); + _exit(1); +} + +/* + * Start a login session running. + */ +static pid_t +start_getty(session_t *sp) +{ + pid_t pid; + sigset_t mask; + time_t current_time = time((time_t *) 0); + int too_quick = 0; + char term[64], *env[2]; + + if (current_time >= sp->se_started && + current_time - sp->se_started < GETTY_SPACING) { + if (++sp->se_nspace > GETTY_NSPACE) { + sp->se_nspace = 0; + too_quick = 1; + } + } else + sp->se_nspace = 0; + + /* + * fork(), not vfork() -- we can't afford to block. + */ + if ((pid = fork()) == -1) { + emergency("can't fork for getty on port %s: %m", sp->se_device); + return -1; + } + + if (pid) + return pid; + + if (too_quick) { + warning("getty repeating too quickly on port %s, sleeping %d secs", + sp->se_device, GETTY_SLEEP); + sleep((unsigned) GETTY_SLEEP); + } + + if (sp->se_window) { + start_window_system(sp); + sleep(WINDOW_WAIT); + } + + sigemptyset(&mask); + sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0); + +#ifdef LOGIN_CAP + setprocresources(RESOURCE_GETTY); +#endif + if (sp->se_type) { + /* Don't use malloc after fork */ + strcpy(term, "TERM="); + strlcat(term, sp->se_type, sizeof(term)); + env[0] = term; + env[1] = 0; + } else + env[0] = 0; + execve(sp->se_getty_argv[0], sp->se_getty_argv, env); + stall("can't exec getty '%s' for port %s: %m", + sp->se_getty_argv[0], sp->se_device); + _exit(1); +} + +/* + * Collect exit status for a child. + * If an exiting login, start a new login running. + */ +static void +collect_child(pid_t pid) +{ + session_t *sp, *sprev, *snext; + + if (! sessions) + return; + + if (! (sp = find_session(pid))) + return; + + del_session(sp); + sp->se_process = 0; + + if (sp->se_flags & SE_SHUTDOWN) { + if ((sprev = sp->se_prev) != NULL) + sprev->se_next = sp->se_next; + else + sessions = sp->se_next; + if ((snext = sp->se_next) != NULL) + snext->se_prev = sp->se_prev; + free_session(sp); + return; + } + + if ((pid = start_getty(sp)) == -1) { + /* serious trouble */ + requested_transition = clean_ttys; + return; + } + + sp->se_process = pid; + sp->se_started = time((time_t *) 0); + add_session(sp); +} + +/* + * Catch a signal and request a state transition. + */ +static void +transition_handler(int sig) +{ + + switch (sig) { + case SIGHUP: + if (current_state == read_ttys || current_state == multi_user || + current_state == clean_ttys || current_state == catatonia) + requested_transition = clean_ttys; + break; + case SIGUSR2: + howto = RB_POWEROFF; + case SIGUSR1: + howto |= RB_HALT; + case SIGINT: + Reboot = TRUE; + case SIGTERM: + if (current_state == read_ttys || current_state == multi_user || + current_state == clean_ttys || current_state == catatonia) + requested_transition = death; + else + requested_transition = death_single; + break; + case SIGTSTP: + if (current_state == runcom || current_state == read_ttys || + current_state == clean_ttys || + current_state == multi_user || current_state == catatonia) + requested_transition = catatonia; + break; + case SIGEMT: + requested_transition = reroot; + break; + default: + requested_transition = 0; + break; + } +} + +/* + * Take the system multiuser. + */ +static state_func_t +multi_user(void) +{ + pid_t pid; + session_t *sp; + + requested_transition = 0; + + /* + * If the administrator has not set the security level to -1 + * to indicate that the kernel should not run multiuser in secure + * mode, and the run script has not set a higher level of security + * than level 1, then put the kernel into secure mode. + */ + if (getsecuritylevel() == 0) + setsecuritylevel(1); + + for (sp = sessions; sp; sp = sp->se_next) { + if (sp->se_process) + continue; + if ((pid = start_getty(sp)) == -1) { + /* serious trouble */ + requested_transition = clean_ttys; + break; + } + sp->se_process = pid; + sp->se_started = time((time_t *) 0); + add_session(sp); + } + + while (!requested_transition) + if ((pid = waitpid(-1, (int *) 0, 0)) != -1) + collect_child(pid); + + return (state_func_t) requested_transition; +} + +/* + * This is an (n*2)+(n^2) algorithm. We hope it isn't run often... + */ +static state_func_t +clean_ttys(void) +{ + session_t *sp, *sprev; + struct ttyent *typ; + int session_index = 0; + int devlen; + char *old_getty, *old_window, *old_type; + + /* + * mark all sessions for death, (!SE_PRESENT) + * as we find or create new ones they'll be marked as keepers, + * we'll later nuke all the ones not found in /etc/ttys + */ + for (sp = sessions; sp != NULL; sp = sp->se_next) + sp->se_flags &= ~SE_PRESENT; + + devlen = sizeof(_PATH_DEV) - 1; + while ((typ = getttyent()) != NULL) { + ++session_index; + + for (sprev = 0, sp = sessions; sp; sprev = sp, sp = sp->se_next) + if (strcmp(typ->ty_name, sp->se_device + devlen) == 0) + break; + + if (sp) { + /* we want this one to live */ + sp->se_flags |= SE_PRESENT; + if (sp->se_index != session_index) { + warning("port %s changed utmp index from %d to %d", + sp->se_device, sp->se_index, + session_index); + sp->se_index = session_index; + } + if ((typ->ty_status & TTY_ON) == 0 || + typ->ty_getty == 0) { + sp->se_flags |= SE_SHUTDOWN; + kill(sp->se_process, SIGHUP); + continue; + } + sp->se_flags &= ~SE_SHUTDOWN; + old_getty = sp->se_getty ? strdup(sp->se_getty) : 0; + old_window = sp->se_window ? strdup(sp->se_window) : 0; + old_type = sp->se_type ? strdup(sp->se_type) : 0; + if (setupargv(sp, typ) == 0) { + warning("can't parse getty for port %s", + sp->se_device); + sp->se_flags |= SE_SHUTDOWN; + kill(sp->se_process, SIGHUP); + } + else if ( !old_getty + || (!old_type && sp->se_type) + || (old_type && !sp->se_type) + || (!old_window && sp->se_window) + || (old_window && !sp->se_window) + || (strcmp(old_getty, sp->se_getty) != 0) + || (old_window && strcmp(old_window, sp->se_window) != 0) + || (old_type && strcmp(old_type, sp->se_type) != 0) + ) { + /* Don't set SE_SHUTDOWN here */ + sp->se_nspace = 0; + sp->se_started = 0; + kill(sp->se_process, SIGHUP); + } + if (old_getty) + free(old_getty); + if (old_window) + free(old_window); + if (old_type) + free(old_type); + continue; + } + + new_session(sprev, session_index, typ); + } + + endttyent(); + + /* + * sweep through and kill all deleted sessions + * ones who's /etc/ttys line was deleted (SE_PRESENT unset) + */ + for (sp = sessions; sp != NULL; sp = sp->se_next) { + if ((sp->se_flags & SE_PRESENT) == 0) { + sp->se_flags |= SE_SHUTDOWN; + kill(sp->se_process, SIGHUP); + } + } + + return (state_func_t) multi_user; +} + +/* + * Block further logins. + */ +static state_func_t +catatonia(void) +{ + session_t *sp; + + for (sp = sessions; sp; sp = sp->se_next) + sp->se_flags |= SE_SHUTDOWN; + + return (state_func_t) multi_user; +} + +/* + * Note SIGALRM. + */ +static void +alrm_handler(int sig) +{ + + (void)sig; + clang = 1; +} + +/* + * Bring the system down to single user. + */ +static state_func_t +death(void) +{ + int block, blocked; + size_t len; + + /* Temporarily block suspend. */ + len = sizeof(blocked); + block = 1; + if (sysctlbyname("kern.suspend_blocked", &blocked, &len, + &block, sizeof(block)) == -1) + blocked = 0; + + /* + * Also revoke the TTY here. Because runshutdown() may reopen + * the TTY whose getty we're killing here, there is no guarantee + * runshutdown() will perform the initial open() call, causing + * the terminal attributes to be misconfigured. + */ + revoke_ttys(); + + /* Try to run the rc.shutdown script within a period of time */ + runshutdown(); + + /* Unblock suspend if we blocked it. */ + if (!blocked) + sysctlbyname("kern.suspend_blocked", NULL, NULL, + &blocked, sizeof(blocked)); + + return (state_func_t) death_single; +} + +/* + * Do what is necessary to reinitialize single user mode or reboot + * from an incomplete state. + */ +static state_func_t +death_single(void) +{ + int i; + pid_t pid; + static const int death_sigs[2] = { SIGTERM, SIGKILL }; + + revoke(_PATH_CONSOLE); + + for (i = 0; i < 2; ++i) { + if (kill(-1, death_sigs[i]) == -1 && errno == ESRCH) + return (state_func_t) single_user; + + clang = 0; + alarm(DEATH_WATCH); + do + if ((pid = waitpid(-1, (int *)0, 0)) != -1) + collect_child(pid); + while (clang == 0 && errno != ECHILD); + + if (errno == ECHILD) + return (state_func_t) single_user; + } + + warning("some processes would not die; ps axl advised"); + + return (state_func_t) single_user; +} + +static void +revoke_ttys(void) +{ + session_t *sp; + + for (sp = sessions; sp; sp = sp->se_next) { + sp->se_flags |= SE_SHUTDOWN; + kill(sp->se_process, SIGHUP); + revoke(sp->se_device); + } +} + +/* + * Run the system shutdown script. + * + * Exit codes: XXX I should document more + * -2 shutdown script terminated abnormally + * -1 fatal error - can't run script + * 0 good. + * >0 some error (exit code) + */ +static int +runshutdown(void) +{ + pid_t pid, wpid; + int status; + int shutdowntimeout; + size_t len; + char *argv[4]; + const char *shell; + struct sigaction sa; + struct stat sb; + + /* + * rc.shutdown is optional, so to prevent any unnecessary + * complaints from the shell we simply don't run it if the + * file does not exist. If the stat() here fails for other + * reasons, we'll let the shell complain. + */ + if (stat(_PATH_RUNDOWN, &sb) == -1 && errno == ENOENT) + return 0; + + shell = get_shell(); + + if ((pid = fork()) == 0) { + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = SIG_IGN; + sigaction(SIGTSTP, &sa, (struct sigaction *)0); + sigaction(SIGHUP, &sa, (struct sigaction *)0); + + open_console(); + + char _sh[] = "sh"; + char _reboot[] = "reboot"; + char _single[] = "single"; + char _path_rundown[] = _PATH_RUNDOWN; + + argv[0] = _sh; + argv[1] = _path_rundown; + argv[2] = Reboot ? _reboot : _single; + argv[3] = 0; + + sigprocmask(SIG_SETMASK, &sa.sa_mask, (sigset_t *) 0); + +#ifdef LOGIN_CAP + setprocresources(RESOURCE_RC); +#endif + execv(shell, argv); + warning("can't exec %s for %s: %m", shell, _PATH_RUNDOWN); + _exit(1); /* force single user mode */ + } + + if (pid == -1) { + emergency("can't fork for %s on %s: %m", shell, _PATH_RUNDOWN); + while (waitpid(-1, (int *) 0, WNOHANG) > 0) + continue; + sleep(STALL_TIMEOUT); + return -1; + } + + len = sizeof(shutdowntimeout); + if (sysctlbyname("kern.init_shutdown_timeout", &shutdowntimeout, &len, + NULL, 0) == -1 || shutdowntimeout < 2) + shutdowntimeout = DEATH_SCRIPT; + alarm(shutdowntimeout); + clang = 0; + /* + * Copied from single_user(). This is a bit paranoid. + * Use the same ALRM handler. + */ + do { + if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1) + collect_child(wpid); + if (clang == 1) { + /* we were waiting for the sub-shell */ + kill(wpid, SIGTERM); + warning("timeout expired for %s on %s: %m; going to " + "single user mode", shell, _PATH_RUNDOWN); + return -1; + } + if (wpid == -1) { + if (errno == EINTR) + continue; + warning("wait for %s on %s failed: %m; going to " + "single user mode", shell, _PATH_RUNDOWN); + return -1; + } + if (wpid == pid && WIFSTOPPED(status)) { + warning("init: %s on %s stopped, restarting\n", + shell, _PATH_RUNDOWN); + kill(pid, SIGCONT); + wpid = -1; + } + } while (wpid != pid && !clang); + + /* Turn off the alarm */ + alarm(0); + + if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM && + requested_transition == catatonia) { + /* + * /etc/rc.shutdown executed /sbin/reboot; + * wait for the end quietly + */ + sigset_t s; + + sigfillset(&s); + for (;;) + sigsuspend(&s); + } + + if (!WIFEXITED(status)) { + warning("%s on %s terminated abnormally, going to " + "single user mode", shell, _PATH_RUNDOWN); + return -2; + } + + if ((status = WEXITSTATUS(status)) != 0) + warning("%s returned status %d", _PATH_RUNDOWN, status); + + return status; +} + +static char * +strk(char *p) +{ + static char *t; + char *q; + int c; + + if (p) + t = p; + if (!t) + return 0; + + c = *t; + while (c == ' ' || c == '\t' ) + c = *++t; + if (!c) { + t = 0; + return 0; + } + q = t; + if (c == '\'') { + c = *++t; + q = t; + while (c && c != '\'') + c = *++t; + if (!c) /* unterminated string */ + q = t = 0; + else + *t++ = 0; + } else { + while (c && c != ' ' && c != '\t' ) + c = *++t; + *t++ = 0; + if (!c) + t = 0; + } + return q; +} + +#ifdef LOGIN_CAP +static void +setprocresources(const char *cname) +{ + login_cap_t *lc; + if ((lc = login_getclassbyname(cname, NULL)) != NULL) { + setusercontext(lc, (struct passwd*)NULL, 0, + LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | + LOGIN_SETLOGINCLASS | LOGIN_SETCPUMASK); + login_close(lc); + } +} +#endif diff --git a/clientside/tmcc/freebsd/init/10.4/mntopts.h b/clientside/tmcc/freebsd/init/10.4/mntopts.h new file mode 100644 index 000000000..0a6a2274e --- /dev/null +++ b/clientside/tmcc/freebsd/init/10.4/mntopts.h @@ -0,0 +1,102 @@ +/*- + * Copyright (c) 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)mntopts.h 8.7 (Berkeley) 3/29/95 + * $FreeBSD: releng/10.4/sbin/mount/mntopts.h 310378 2016-12-21 23:16:58Z brooks $ + */ + +struct mntopt { + const char *m_option; /* option name */ + int m_inverse; /* if a negative option, e.g. "atime" */ + long long m_flag; /* bit to set, e.g. MNT_RDONLY */ + int m_altloc; /* 1 => set bit in altflags */ +}; + +/* User-visible MNT_ flags. */ +#define MOPT_ASYNC { "async", 0, MNT_ASYNC, 0 } +#define MOPT_NOATIME { "atime", 1, MNT_NOATIME, 0 } +#define MOPT_NOEXEC { "exec", 1, MNT_NOEXEC, 0 } +#define MOPT_NOSUID { "suid", 1, MNT_NOSUID, 0 } +#define MOPT_NOSYMFOLLOW { "symfollow", 1, MNT_NOSYMFOLLOW, 0 } +#define MOPT_RDONLY { "rdonly", 0, MNT_RDONLY, 0 } +#define MOPT_SYNC { "sync", 0, MNT_SYNCHRONOUS, 0 } +#define MOPT_UNION { "union", 0, MNT_UNION, 0 } +#define MOPT_USERQUOTA { "userquota", 0, 0, 0 } +#define MOPT_GROUPQUOTA { "groupquota", 0, 0, 0 } +#define MOPT_NOCLUSTERR { "clusterr", 1, MNT_NOCLUSTERR, 0 } +#define MOPT_NOCLUSTERW { "clusterw", 1, MNT_NOCLUSTERW, 0 } +#define MOPT_SUIDDIR { "suiddir", 0, MNT_SUIDDIR, 0 } +#define MOPT_SNAPSHOT { "snapshot", 0, MNT_SNAPSHOT, 0 } +#define MOPT_MULTILABEL { "multilabel", 0, MNT_MULTILABEL, 0 } +#define MOPT_ACLS { "acls", 0, MNT_ACLS, 0 } +#define MOPT_NFS4ACLS { "nfsv4acls", 0, MNT_NFS4ACLS, 0 } +#define MOPT_AUTOMOUNTED { "automounted",0, MNT_AUTOMOUNTED, 0 } + +/* Control flags. */ +#define MOPT_FORCE { "force", 0, MNT_FORCE, 0 } +#define MOPT_UPDATE { "update", 0, MNT_UPDATE, 0 } +#define MOPT_RO { "ro", 0, MNT_RDONLY, 0 } +#define MOPT_RW { "rw", 1, MNT_RDONLY, 0 } + +/* This is parsed by mount(8), but is ignored by specific mount_*(8)s. */ +#define MOPT_AUTO { "auto", 0, 0, 0 } + +/* A handy macro as terminator of MNT_ array. */ +#define MOPT_END { NULL, 0, 0, 0 } + +#define MOPT_FSTAB_COMPAT \ + MOPT_RO, \ + MOPT_RW, \ + MOPT_AUTO + +/* Standard options which all mounts can understand. */ +#define MOPT_STDOPTS \ + MOPT_USERQUOTA, \ + MOPT_GROUPQUOTA, \ + MOPT_FSTAB_COMPAT, \ + MOPT_NOATIME, \ + MOPT_NOEXEC, \ + MOPT_SUIDDIR, /* must be before MOPT_NOSUID */ \ + MOPT_NOSUID, \ + MOPT_NOSYMFOLLOW, \ + MOPT_RDONLY, \ + MOPT_UNION, \ + MOPT_NOCLUSTERR, \ + MOPT_NOCLUSTERW, \ + MOPT_MULTILABEL, \ + MOPT_ACLS, \ + MOPT_NFS4ACLS, \ + MOPT_AUTOMOUNTED + +void getmntopts(const char *, const struct mntopt *, int *, int *); +void rmslashes(char *, char *); +int checkpath(const char *, char resolved_path[]); +extern int getmnt_silent; +void build_iovec(struct iovec **iov, int *iovlen, const char *name, void *val, size_t len); +void build_iovec_argf(struct iovec **iov, int *iovlen, const char *name, const char *fmt, ...); +void free_iovec(struct iovec **iovec, int *iovlen); diff --git a/clientside/tmcc/freebsd/init/10.4/pathnames.h b/clientside/tmcc/freebsd/init/10.4/pathnames.h new file mode 100644 index 000000000..23ba5ba28 --- /dev/null +++ b/clientside/tmcc/freebsd/init/10.4/pathnames.h @@ -0,0 +1,43 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Donn Seeley at Berkeley Software Design, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/5/93 + * $FreeBSD: releng/10.4/sbin/init/pathnames.h 293744 2016-01-12 10:14:57Z trasz $ + */ + +#include + +#define _PATH_INITLOG "/var/log/init.log" +#define _PATH_SLOGGER "/sbin/session_logger" +#define _PATH_RUNCOM "/etc/rc" +#define _PATH_RUNDOWN "/etc/rc.shutdown" +#define _PATH_REROOT "/dev/reroot" +#define _PATH_REROOT_INIT _PATH_REROOT "/init" diff --git a/clientside/tmcc/freebsd/init/GNUmakefile.in b/clientside/tmcc/freebsd/init/GNUmakefile.in index 1c30c9c8a..8fd986faa 100644 --- a/clientside/tmcc/freebsd/init/GNUmakefile.in +++ b/clientside/tmcc/freebsd/init/GNUmakefile.in @@ -1,5 +1,5 @@ # -# Copyright (c) 2006-2016 University of Utah and the Flux Group. +# Copyright (c) 2006-2017 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # @@ -50,12 +50,16 @@ ifeq ($(FBSDVERSION),FreeBSD9) FSUBDIR = 9 endif ifeq ($(FBSDVERSION),FreeBSD10) +ifeq ($(FBSDREL),10.4) +FSUBDIR = 10.4 +else ifeq ($(FBSDREL),10.3) FSUBDIR = 10.3 else FSUBDIR = 10 endif endif +endif ifeq ($(FBSDVERSION),FreeBSD11) FSUBDIR = 11 endif diff --git a/configure b/configure index 4b6743db2..316fdf054 100755 --- a/configure +++ b/configure @@ -7157,6 +7157,7 @@ outfiles="$outfiles clientside/GNUmakefile clientside/setversion \ clientside/tmcc/freebsd/init/9/GNUmakefile \ clientside/tmcc/freebsd/init/10/GNUmakefile \ clientside/tmcc/freebsd/init/10.3/GNUmakefile \ + clientside/tmcc/freebsd/init/10.4/GNUmakefile \ clientside/tmcc/freebsd/init/11/GNUmakefile \ clientside/tmcc/freebsd/init/12/GNUmakefile \ clientside/tmcc/freebsd/supfile clientside/tmcc/freebsd/sethostname \ diff --git a/configure.ac b/configure.ac index e2d19186f..c7276d50f 100644 --- a/configure.ac +++ b/configure.ac @@ -1489,6 +1489,7 @@ outfiles="$outfiles clientside/GNUmakefile clientside/setversion \ clientside/tmcc/freebsd/init/9/GNUmakefile \ clientside/tmcc/freebsd/init/10/GNUmakefile \ clientside/tmcc/freebsd/init/10.3/GNUmakefile \ + clientside/tmcc/freebsd/init/10.4/GNUmakefile \ clientside/tmcc/freebsd/init/11/GNUmakefile \ clientside/tmcc/freebsd/init/12/GNUmakefile \ clientside/tmcc/freebsd/supfile clientside/tmcc/freebsd/sethostname \ -- GitLab