/* Finit - Fast /sbin/init replacement w/ I/O, hook & service plugins * * Copyright (c) 2008-2010 Claudio Matsuoka * Copyright (c) 2008-2025 Joachim Wiberg * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "config.h" /* Generated by configure script */ #include #include #include #ifdef HAVE_MNTENT_H #include #endif #include /* tzet() */ #include #include #include /* umask(), mkdir() */ #include #ifdef HAVE_SYS_SYSMACROS_H #include #endif #include #include #ifdef _LIBITE_LITE # include #else # include #endif #include "finit.h" #include "cgroup.h" #include "cond.h" #include "conf.h" #include "devmon.h" #include "helpers.h" #include "private.h" #include "plugin.h" #include "service.h" #include "sig.h" #include "sm.h" #include "tty.h" #include "util.h" #include "utmp-api.h" uev_ctx_t *ctx; /* Main loop context */ svc_t *wdog; /* No watchdog by default */ char *arg0; /* Saved for setprocnm() */ /* * --enable-fastboot => fsck_mode: NULL => no fsck by default * --enable-fsckfix => fsck_mode: "-f" + fsck_repair: "y" */ #ifdef FSCK_FIX # ifdef FAST_BOOT char *fsck_mode = NULL; # else char *fsck_mode = "-f"; # endif char *fsck_repair = "-y"; #else # ifdef FAST_BOOT char *fsck_mode = NULL; # else char *fsck_mode = ""; # endif char *fsck_repair = "-p"; #endif /* * Show user configured banner before service bootstrap progress */ static void banner(void) { #ifndef KERNEL_LOGGING /* * Silence kernel logs, assuming users have sysklogd or * similar enabled to start emptying /dev/kmsg. * * Instead of using `configure --disable-kernel-logging`, we * recommend adjusting the kernel log level in the kernel's * menuconfig, or using sysctl kernel.printk, or setting the * desired log level, on the kernel cmdline, e.g. 'quiet'. * * By default KERNEL_LOGGING is enabled so you can see any * warnings/errors or higher on your system console. */ if (!debug && !kerndebug) klogctl(6, NULL, 0); #endif /* * First level hooks, if you want to run here, you're * pretty much on your own. Nothing's up yet ... */ plugin_run_hooks(HOOK_BANNER); #ifdef INIT_OSHEADING osheading = INIT_OSHEADING; if (osheading) { if (!osheading[0]) osheading = release_heading(); print_banner(osheading); } #endif } static int sulogin(int do_reboot) { int rc = EX_OSFILE; char *cmd[] = { "sulogin", _PATH_SULOGIN, }; size_t i; for (i = 0; i < NELEMS(cmd); i++) { char *path = which(cmd[i]); if (!path) continue; if (access(path, X_OK)) { free(path); continue; } rc = systemf("%s", path); free(path); break; } if (do_reboot) { do_shutdown(SHUT_REBOOT); exit(rc); } return rc; } char *fs_root_dev(char *real, size_t len) { struct dirent *d; struct stat st; int found = 0; dev_t dev; DIR *dir; if (stat("/", &st)) return NULL; if (S_ISBLK(st.st_mode)) dev = st.st_rdev; else dev = st.st_dev; dir = opendir("/sys/block"); if (!dir) return NULL; while ((d = readdir(dir))) { char buf[10]; char *ptr; if (fnread(buf, sizeof(buf), "/sys/block/%s/dev", d->d_name) == -1) continue; ptr = strchr(buf, ':'); if (!ptr) continue; *ptr++ = 0; if (atoi(buf) == (int)major(dev) && atoi(ptr) == (int)minor(dev)) { /* Guess name, assume no renaming */ snprintf(real, len, "/dev/%s", d->d_name); found = 1; break; } } closedir(dir); if (!found) return NULL; return real; } /* * Check all filesystems in /etc/fstab with a fs_passno > 0 */ static int fsck(int pass) { struct mntent mount; struct mntent *mnt; char real[192]; char buf[256]; int rc = 0; FILE *fp; fp = setmntent(fstab, "r"); if (!fp) { logit(LOG_CONSOLE | LOG_ALERT, "Failed opening fstab: %s", fstab); sulogin(1); } dbg("Opened %s, pass %d", fstab, pass); while ((mnt = getmntent_r(fp, &mount, buf, sizeof(buf)))) { int fsck_rc = 0; struct stat st; char cmd[256]; char *dev; dbg("got: fsname '%s' dir '%s' type '%s' opts '%s' freq '%d' passno '%d'", mnt->mnt_fsname, mnt->mnt_dir, mnt->mnt_type, mnt->mnt_opts, mnt->mnt_freq, mnt->mnt_passno); if (mnt->mnt_passno == 0 || mnt->mnt_passno != pass) continue; if (hasmntopt(mnt, "noauto")) { dbg("Skipping fsck of %s, marked noauto", mnt->mnt_fsname); continue; } /* Device to maybe fsck, */ dev = mnt->mnt_fsname; errno = 0; if (stat(dev, &st) || !S_ISBLK(st.st_mode)) { int skip = 1; if (string_match(dev, "UUID=") || string_match(dev, "LABEL=")) skip = 0; /* * Kernel short form for root= device, figure out * actual device since we cannot rely on symlinks * https://bugs.busybox.net/show_bug.cgi?id=8891 */ else if (string_compare(dev, "/dev/root")) { if (fs_root_dev(real, sizeof(real))) { dev = real; skip = 0; } } if (skip) { dbg("Cannot fsck %s, not a block device: %s", dev, strerror(errno)); continue; } } if (ismnt("/proc/mounts", mnt->mnt_dir, "rw")) { dbg("Skipping fsck of %s, already mounted rw on %s.", dev, mnt->mnt_dir); continue; } snprintf(cmd, sizeof(cmd), "fsck %s %s %s", fsck_mode, fsck_repair, dev); dbg("Running pass %d fsck command %s", pass, cmd); fsck_rc = run_interactive(cmd, "Checking filesystem %s", dev); /* * "failure" is defined as exiting with a return code of * 2 or larger. A return code of 1 indicates that filesystem * errors were corrected but that the boot may proceed. */ if (fsck_rc > 1) { if (hasmntopt(mnt,"nofail")) { logit(LOG_CONSOLE | LOG_WARNING, "Ignoring failed fsck %s because of nofail option", dev); fsck_rc = 0; } else { logit(LOG_CONSOLE | LOG_ALERT, "Failed fsck %s, attempting sulogin ...", dev); sulogin(1); } } rc |= fsck_rc; } endmntent(fp); return rc; } static int fsck_all(void) { int rc = 0; int pass; if (!fsck_mode) return 0; for (pass = 1; pass < 10; pass++) { rc = fsck(pass); if (rc) break; } return rc; } /* Wrapper for mount(2), logs any errors to stderr */ static void fs_mount(const char *src, const char *tgt, const char *fstype, unsigned long flags, const void *data) { const char *msg = !fstype ? "MS_MOVE" : "mounting"; int rc; rc = mount(src, tgt, fstype, flags, data); if (rc && errno != EBUSY) err(1, "Failed %s %s on %s", msg, src, tgt); } #ifndef SYSROOT static void fs_remount_root(int fsckerr) { struct mntent *mnt; FILE *fp; fp = setmntent(fstab, "r"); if (!fp) return; while ((mnt = getmntent(fp))) { if (!strcmp(mnt->mnt_dir, "/")) break; } /* If / is not listed in fstab, or listed as 'ro', leave it alone */ if (!mnt || hasmntopt(mnt, "ro")) goto out; if (fsckerr) print(1, "Cannot remount / as read-write, fsck failed before"); else run_interactive("mount -n -o remount,rw /", "Remounting / as read-write"); out: endmntent(fp); } #else static void fs_remount_root(int fsckerr) { /* * XXX: Untested, in the initramfs age we should * probably use switch_root instead. */ fs_mount(SYSROOT, "/", NULL, MS_MOVE, NULL); } #endif /* SYSROOT */ /* * Opinionated file system setup. Checks for critical mount points and * mounts them as most users expect. All file systems are checked with * /proc/mounts before mounting. * * Embedded systems, and other people who want full control, can set up * their system with /etc/fstab, which is handled before this function * is called. For systems like Debian/Ubuntu, who only have / and swap * in their /etc/fstab, this function does all the magic necessary. */ static void fs_finalize(void) { int flags = MS_NOEXEC | MS_NOSUID; /* * Some systems rely on us to both create /dev/shm and, to mount * a tmpfs there. Any system with dbus needs shared memory, so * mount it, unless its already mounted, but not if listed in * the /etc/fstab file already. */ if (!fismnt("/dev/shm")) { makedir("/dev/shm", 1777); fs_mount("shm", "/dev/shm", "tmpfs", flags | MS_NODEV, "mode=1777"); } /* Remount devpts with proper gid/mode now that /etc/group is available */ { char opts[32]; int gid; gid = getgroup("tty"); if (gid == -1) gid = 0; /* 0600 is default on Debian, use 0620 to get mesg y by default */ snprintf(opts, sizeof(opts), "gid=%d,mode=0620,ptmxmode=0666", gid); makedir("/dev/pts", 0755); fs_mount("devpts", "/dev/pts", "devpts", flags | MS_REMOUNT, opts); } /* Needed on systems like Alpine Linux */ if (!fismnt("/dev/mqueue")) { makedir("/dev/mqueue", 1777); fs_mount("mqueue", "/dev/mqueue", "tmpfs", flags | MS_NODEV, NULL); } /* * Modern systems use tmpfs for /run. Fallback to /var/run if * /run doesn't exist is handled by the bootmisc plugin. It * also sets up compat symlinks. * * The unconditional mount of /run/lock is for DoS prevention. * To override any of this behavior, add entries to /etc/fstab * for /run (and optionally /run/lock). */ if (fisdir("/run") && !fismnt("/run") && !fistmpfs("/run")) { fs_mount("tmpfs", "/run", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME, "mode=0755,size=10%"); /* This prevents user DoS of /run by filling /run/lock at the expense of another tmpfs, max 5MiB */ makedir("/run/lock", 1777); fs_mount("tmpfs", "/run/lock", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RELATIME, "mode=0777,size=5252880"); } /* Modern systems use tmpfs for /tmp */ if (!fismnt("/tmp") && !fistmpfs("/tmp")) fs_mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_NODEV, "mode=1777"); } static void fs_swapon(char *cmd, size_t len) { struct mntent *mnt; FILE *fp; if (!whichp("swapon")) return; fp = setmntent(fstab, "r"); if (!fp) return; while ((mnt = getmntent(fp))) { if (strcmp(mnt->mnt_type, MNTTYPE_SWAP)) continue; if (hasmntopt(mnt, "noauto")) { dbg("Skipping swap %s, marked noauto", mnt->mnt_fsname); continue; } snprintf(cmd, len, "swapon %s", mnt->mnt_fsname); run_interactive(cmd, "Enabling swap %s", mnt->mnt_fsname); } endmntent(fp); } static void fs_mount_all(void) { char cmd[256] = "mount -na"; if (!fstab || !fexist(fstab)) { logit(LOG_CONSOLE | LOG_NOTICE, "%s system fstab %s, trying fallback ...", !fstab ? "Missing" : "Cannot find", fstab ?: "\b"); fstab = FINIT_FSTAB; } if (!fstab || !fexist(fstab)) { logit(LOG_CONSOLE | LOG_EMERG, "%s system fstab %s, attempting sulogin ...", !fstab ? "Missing" : "Cannot find", fstab ?: "\b"); sulogin(1); } /* * Needed by fsck, both BusyBox and util-linux support this. * We leave it set in the env. for the benefit of any mount * helpers and other system tools that the user expects to * behave even if we've booted with a different fstab. */ setenv("FSTAB_FILE", fstab, 1); if (!rescue) fs_remount_root(fsck_all()); dbg("Root FS up, calling hooks ..."); plugin_run_hooks(HOOK_ROOTFS_UP); if (fstab && strcmp(fstab, "/etc/fstab")) snprintf(cmd, sizeof(cmd), "mount -na -T %s", fstab); if (run_interactive(cmd, "Mounting filesystems from %s", fstab)) plugin_run_hooks(HOOK_MOUNT_ERROR); dbg("Calling extra mount hook, after mount -a ..."); plugin_run_hooks(HOOK_MOUNT_POST); dbg("Enable any swap ..."); fs_swapon(cmd, sizeof(cmd)); dbg("Finalize, ensure common file systems are available ..."); fs_finalize(); dbg("Base FS up, calling hooks ..."); plugin_run_hooks(HOOK_BASEFS_UP); } /* * We need /proc for rs_remount_root() and conf_parse_cmdline(), /dev * for early multi-console, and /sys for the cgroups support. Any * occurrence of these file systems in /etc/fstab will replace these * mounts later in fs_mount_all() * * Ignore any mount errors with EBUSY, kernel likely already mounted * the filesystem for us automatically, e.g., CONFIG_DEVTMPFS_MOUNT. */ static void fs_init(void) { struct { char *spec, *file, *type; } fs[] = { { "proc", "/proc", "proc" }, { "devtmpfs", "/dev", "devtmpfs" }, { "sysfs", "/sys", "sysfs" }, }; size_t i; /* mask writable bit for g and o */ umask(022); for (i = 0; i < NELEMS(fs); i++) { /* * Check if already mounted, we may be running in a * container, or an initramfs ran before us. The * function fismnt() reliles on /proc/mounts being * unique for each chroot/container. */ if (fismnt(fs[i].file)) continue; /* Create mount point if it doesn't exist */ if (mkdir(fs[i].file, 0755) && errno != EEXIST) warn("Failed creating mountpoint %s: %s", fs[i].file, strerror(errno)); fs_mount(fs[i].spec, fs[i].file, fs[i].type, 0, NULL); } /* Early devpts mount for plugins that need PTY access (e.g. plymouth) */ if (!fismnt("/dev/pts")) { makedir("/dev/pts", 0755); fs_mount("devpts", "/dev/pts", "devpts", MS_NOSUID | MS_NOEXEC, "ptmxmode=0666"); } } static int version(int rc) { puts(PACKAGE_STRING); printf("Bug report address: %-40s\n", PACKAGE_BUGREPORT); #ifdef PACKAGE_URL printf("Project homepage: %s\n", PACKAGE_URL); #endif return rc; } static int usage(int rc) { printf("Usage: %s [OPTIONS] [q | Q | 0-9]\n\n" "Options:\n" // " -a Ignored, compat SysV init\n" // " -b Ignored, compat SysV init\n" // " -e arg Ignored, compat SysV init\n" " -h This help text\n" // " -s Ignored, compat SysV init\n" // " -t sec Ignored, compat SysV init\n" " -v Show Finit version\n" // " -z xxx Ignored, compat SysV init\n" "\n" "Commands:\n" " 0 Power-off the system, same as initctl poweroff\n" " 6 Reboot the system, same as initctl reboot\n" " 2-9 Change runlevel\n" " q, Q Reload /etc/finit.conf and/or any *.conf in /etc/finit.d/\n" " if modified, same as initctl reload or SIGHUP to PID 1\n" " 1, s, S Enter single user mode, no networking, runlevel 1\n" "\n", prognm); return rc; } /* * wrapper for old-style init/telinit commands, for compat with * /usr/bin/shutdown from sysvinit, and old fingers */ static int telinit(int argc, char *argv[]) { int c; progname(argv[0]); while ((c = getopt(argc, argv, "abe:h?st:vVz:")) != EOF) { switch(c) { case 'a': case 'b': case 'e': case 's': case 'z': break; /* ign, compat */ case 't': /* optarg == killdelay */ break; case 'v': case 'V': return version(0); case 'h': case '?': return usage(0); } } if (optind < argc) { int req = (int)argv[optind][0]; if (isdigit(req)) return systemf("initctl -b runlevel %c", req); if (req == 'q' || req == 'Q') return systemf("initctl -b reload"); if (req == 's' || req == 'S') return systemf("initctl -b runlevel %c", req); } return usage(1); } int main(int argc, char *argv[]) { uev_ctx_t loop; /* Save argv for setprocnm() in child processes */ arg0 = argv[0]; /* user calling telinit or init */ if (getpid() != 1) { if (geteuid() == 0) return telinit(argc, argv); /* * Issue #301: add non-pid1 process monitor here. * Default to read ~/.config/finit.conf with the * API socket in /run/user/$UID/. Lot of things * to refactor before then though. */ return EX_NOPERM; } /* * Sanity check critical environment variables. We need PATH * and SHELL for early commands like mount in fs_init(). */ if (!checkenv("PATH")) setenv("PATH", _PATH_STDPATH, 1); if (!checkenv("SHELL")) setenv("SHELL", _PATH_BSHELL, 1); /* * Need /dev, /proc, and /sys for console=, remount and cgroups */ fs_init(); /* * Parse /proc/cmdline (debug, rescue, console=, etc.) */ conf_parse_cmdline(argc, argv); /* * Clear command line arguments for ps display, issue #442 */ prctl(PR_SET_NAME, "init", 0, 0, 0); for (int i = 1; i < argc; i++) memset(argv[i], 0, strlen(argv[i])); /* * Figure out system console(s) and call log_init() to set * correct log level, possibly finit.debug enabled. */ console_init(); /* * Initialize event context. */ uev_init1(&loop, 1); ctx = &loop; /* * Set PATH, SHELL, and PWD early to something sane */ conf_reset_env(); if (chdir("/")) err(1, "Failed cd /"); /* * In case of emergency. */ #ifdef RESCUE_MODE if (rescue) rescue = sulogin(0); #endif /* * Load plugins early, the first hook is in banner(), so we * need plugins loaded before calling it. */ plugin_init(&loop); /* * Hello world. */ enable_progress(1); /* Allow progress, if enabled */ banner(); if (osheading) logit(LOG_CONSOLE | LOG_NOTICE, "%s, entering runlevel S", osheading); else logit(LOG_CONSOLE | LOG_NOTICE, "Entering runlevel S"); /* * Initial setup of signals, ignore all until we're up. */ sig_init(); /* * Initialize default control groups, if available */ cgroup_init(&loop); /* * Check custom fstab from cmdline, including fallback, then run * fsck before mounting all filesystems, on error call sulogin. */ fs_mount_all(); /* * Base FS up, enable standard SysV init signals and * Bootstrap conditions, needed for hooks */ sig_setup(&loop); cond_init(); /* * Emit conditions for early hooks that ran before the condition * system was initialized in case anyone. */ cond_set_oneshot(plugin_hook_str(HOOK_BANNER)); cond_set_oneshot(plugin_hook_str(HOOK_ROOTFS_UP)); /* Some bootstrap tasks may need to know if we're in a container. */ if (in_container()) cond_set_oneshot("int/container"); /* * Initialize .conf system and load static /etc/finit.conf then * tell the world what we used. */ devmon_init(&loop); conf_init(&loop); conf_saverc(); /* * Set up inotify watcher for /etc/finit.conf, /etc/finit.d, and * their deps, to figure out how to bootstrap the system. */ conf_monitor(); dbg("Starting initctl API responder ..."); api_init(&loop); #ifdef HAVE_DBUS dbg("Starting D-Bus listener ..."); dbus_init(&loop); #endif dbg("Starting service interval monitor ..."); service_init(&loop); /* * Initialize state machine and start all bootstrap tasks * NOTE: no network available! */ sm_init(); sm_step(); /* * Enter main loop to monitor /dev/initctl and services */ dbg("Entering main loop ..."); return uev_run(&loop, 0); } /** * Local Variables: * indent-tabs-mode: t * c-file-style: "linux" * End: */