├── services ├── early-env ├── early-pseudofs ├── early-net-lo ├── early-bless-boot ├── early-fs-btrfs ├── early-hostname ├── early-modules.target ├── early-swap ├── recovery ├── pre-network.target ├── time-sync.target ├── single ├── device ├── early-kernel-env ├── early-tmpfs ├── early-cgroups ├── early-dmraid ├── early-fs-zfs ├── early-mdadm ├── early-root-rw.target ├── early-modules ├── early-devices.target ├── early-sysctl ├── login.target ├── network.target ├── early-keyboard.target ├── early-binfmt ├── early-dev-trigger ├── early-fs-fsck ├── system ├── early-kdump ├── early-modules-early ├── early-console.target ├── early-tmpfiles ├── local.target ├── early-tmpfiles-dev ├── early-dev-settle ├── zram-device ├── early-fs-pre.target ├── early-rng ├── early-lvm ├── early-prepare.target ├── early-machine-id ├── early-fs-fstab.target ├── early-devmon ├── boot ├── early-fs-local.target ├── early-root-fsck ├── early-hwclock ├── early-cryptdisks-early ├── early-swclock ├── early-cryptdisks ├── early-devd ├── pre-local.target └── meson.build ├── early ├── scripts │ ├── net-lo.sh │ ├── try-kdump.sh │ ├── sysctl.sh │ ├── devmon.sh │ ├── rng.sh │ ├── swap.sh │ ├── local.sh │ ├── modules.sh │ ├── root-rw.sh │ ├── modules-early.sh │ ├── dmraid.sh │ ├── console.sh │ ├── pseudofs.sh │ ├── cryptdisks.sh │ ├── tmpfiles.sh │ ├── dev.sh │ ├── fs-fstab.sh │ ├── kernel-env.sh │ ├── zram.sh │ ├── fs-btrfs.sh │ ├── mdadm.sh │ ├── clock.sh │ ├── binfmt.sh │ ├── bless-boot.sh │ ├── lvm.sh │ ├── fs-zfs.sh │ ├── common.sh │ ├── done.sh │ ├── hostname.sh │ ├── cgroups.sh │ ├── fs-fsck.sh │ ├── env.sh │ ├── machine-id.sh │ ├── tmpfs.sh │ ├── meson.build │ ├── root-fsck.sh │ └── kdump.sh └── helpers │ ├── clock_common.hh │ ├── meson.build │ ├── devclient.cc │ ├── lo.cc │ ├── devmon.cc │ ├── hwclock.cc │ ├── swap.cc │ ├── binfmt.cc │ ├── swclock.cc │ ├── sysctl.cc │ ├── kmod.cc │ ├── seedrng.cc │ ├── zram.cc │ └── mnt.cc ├── man ├── meson.build └── early-modules.target.8 ├── tmpfiles ├── home.conf ├── tmp.conf ├── x11.conf ├── meson.build ├── var.conf ├── chimera.conf └── static-nodes-permissions.conf ├── .mailmap ├── meson_options.txt ├── shutdown-hook ├── COPYING.md ├── meson.build ├── init └── README.md /services/early-env: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/env.sh 3 | options: pass-cs-fd 4 | -------------------------------------------------------------------------------- /services/early-pseudofs: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/pseudofs.sh 3 | depends-on: early-env 4 | -------------------------------------------------------------------------------- /services/early-net-lo: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/net-lo.sh 3 | depends-on: early-devices.target 4 | -------------------------------------------------------------------------------- /services/early-bless-boot: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/bless-boot.sh 3 | depends-on: pre-local.target 4 | -------------------------------------------------------------------------------- /services/early-fs-btrfs: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/fs-btrfs.sh 3 | depends-on: early-fs-pre.target 4 | -------------------------------------------------------------------------------- /services/early-hostname: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/hostname.sh 3 | depends-on: early-devices.target 4 | -------------------------------------------------------------------------------- /services/early-modules.target: -------------------------------------------------------------------------------- 1 | # kernel modules are done loading 2 | 3 | type = internal 4 | depends-ms: early-modules 5 | -------------------------------------------------------------------------------- /services/early-swap: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/swap.sh start 3 | depends-on: early-fs-local.target 4 | -------------------------------------------------------------------------------- /services/recovery: -------------------------------------------------------------------------------- 1 | type = process 2 | command = @DINIT_SULOGIN_PATH@ 3 | restart = false 4 | options: runs-on-console 5 | -------------------------------------------------------------------------------- /services/pre-network.target: -------------------------------------------------------------------------------- 1 | # reached before net daemons are started 2 | 3 | type = internal 4 | depends-on: local.target 5 | -------------------------------------------------------------------------------- /early/scripts/net-lo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=net-lo 4 | 5 | . @SCRIPT_PATH@/common.sh 6 | 7 | exec @HELPER_PATH@/lo 8 | -------------------------------------------------------------------------------- /early/scripts/try-kdump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | [ -x "@SCRIPT_PATH@/kdump.sh" ] || exit 0 4 | 5 | exec @SCRIPT_PATH@/kdump.sh "$@" 6 | -------------------------------------------------------------------------------- /services/time-sync.target: -------------------------------------------------------------------------------- 1 | # time should be synced before this is reached 2 | 3 | type = internal 4 | depends-on: local.target 5 | -------------------------------------------------------------------------------- /early/scripts/sysctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=sysctl 4 | 5 | . @SCRIPT_PATH@/common.sh 6 | 7 | exec @HELPER_PATH@/sysctl 8 | -------------------------------------------------------------------------------- /services/single: -------------------------------------------------------------------------------- 1 | type = process 2 | command = @DINIT_SULOGIN_PATH@ 3 | restart = false 4 | chain-to: boot 5 | options: shares-console 6 | -------------------------------------------------------------------------------- /early/scripts/devmon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=devmon 4 | 5 | . @SCRIPT_PATH@/common.sh 6 | 7 | exec @HELPER_PATH@/devmon "$1" 8 | -------------------------------------------------------------------------------- /services/device: -------------------------------------------------------------------------------- 1 | type = process 2 | command = @HELPER_PATH@/devclient $1 4 3 | ready-notification = pipefd:4 4 | depends-on: early-devmon 5 | -------------------------------------------------------------------------------- /services/early-kernel-env: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/kernel-env.sh 3 | depends-on: early-pseudofs 4 | options: pass-cs-fd 5 | -------------------------------------------------------------------------------- /services/early-tmpfs: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/tmpfs.sh 3 | depends-on: early-kernel-env 4 | depends-on: early-pseudofs 5 | -------------------------------------------------------------------------------- /man/meson.build: -------------------------------------------------------------------------------- 1 | manpages = [ 2 | 'early-modules.target.8' 3 | ] 4 | 5 | foreach manp: manpages 6 | install_man(manp) 7 | endforeach 8 | -------------------------------------------------------------------------------- /services/early-cgroups: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/cgroups.sh 3 | depends-on: early-kernel-env 4 | depends-on: early-pseudofs 5 | -------------------------------------------------------------------------------- /services/early-dmraid: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/dmraid.sh 3 | depends-on: early-devices.target 4 | depends-ms: early-root-fsck 5 | -------------------------------------------------------------------------------- /services/early-fs-zfs: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/fs-zfs.sh 3 | depends-on: early-fs-pre.target 4 | options: starts-on-console 5 | -------------------------------------------------------------------------------- /services/early-mdadm: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/mdadm.sh 3 | depends-on: early-devices.target 4 | depends-ms: early-root-fsck 5 | -------------------------------------------------------------------------------- /services/early-root-rw.target: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/root-rw.sh 3 | depends-ms: early-root-fsck 4 | options: starts-rwfs 5 | -------------------------------------------------------------------------------- /services/early-modules: -------------------------------------------------------------------------------- 1 | # handle modules-load.d 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/modules.sh 5 | depends-ms: early-modules-early 6 | -------------------------------------------------------------------------------- /early/scripts/rng.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=rng 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | exec @HELPER_PATH@/seedrng 9 | -------------------------------------------------------------------------------- /services/early-devices.target: -------------------------------------------------------------------------------- 1 | # all device events have been processed 2 | 3 | type = internal 4 | depends-on: early-devd 5 | depends-ms: early-devmon 6 | -------------------------------------------------------------------------------- /services/early-sysctl: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/sysctl.sh 3 | depends-on: early-devices.target 4 | depends-on: early-fs-local.target 5 | -------------------------------------------------------------------------------- /services/login.target: -------------------------------------------------------------------------------- 1 | # virtual service run before login is enabled 2 | 3 | type = internal 4 | depends-on: local.target 5 | options: runs-on-console 6 | -------------------------------------------------------------------------------- /services/network.target: -------------------------------------------------------------------------------- 1 | # virtual service for others to depend on; bring up networking daemons 2 | 3 | type = internal 4 | depends-on: pre-network.target 5 | -------------------------------------------------------------------------------- /tmpfiles/home.conf: -------------------------------------------------------------------------------- 1 | # This file is a part of dinit-chimera. 2 | # 3 | # See tmpfiles.d(5) for details. 4 | 5 | Q /home 0755 - - - 6 | q /srv 0755 - - - 7 | -------------------------------------------------------------------------------- /early/scripts/swap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=swap 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | exec @HELPER_PATH@/swap "$1" 9 | -------------------------------------------------------------------------------- /services/early-keyboard.target: -------------------------------------------------------------------------------- 1 | # set console keyboard 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/console.sh keyboard 5 | depends-on: early-devices.target 6 | -------------------------------------------------------------------------------- /early/scripts/local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=local 4 | 5 | . @SCRIPT_PATH@/common.sh 6 | 7 | [ -x /etc/rc.local ] && /etc/rc.local 8 | 9 | exit 0 10 | -------------------------------------------------------------------------------- /early/scripts/modules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=modules 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | exec @HELPER_PATH@/kmod modules 9 | -------------------------------------------------------------------------------- /early/scripts/root-rw.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=root-rw 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | exec @HELPER_PATH@/mnt root-rw 9 | -------------------------------------------------------------------------------- /services/early-binfmt: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/binfmt.sh start 3 | stop-command = @SCRIPT_PATH@/binfmt.sh stop 4 | depends-on: early-fs-local.target 5 | -------------------------------------------------------------------------------- /tmpfiles/tmp.conf: -------------------------------------------------------------------------------- 1 | # This file is a part of dinit-chimera. 2 | # 3 | # See tmpfiles.d(5) for details. 4 | 5 | q /tmp 1777 root root 10d 6 | q /var/tmp 1777 root root 30d 7 | -------------------------------------------------------------------------------- /services/early-dev-trigger: -------------------------------------------------------------------------------- 1 | # trigger device events for already-present devices 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/dev.sh trigger 5 | depends-on: early-devd 6 | -------------------------------------------------------------------------------- /services/early-fs-fsck: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/fs-fsck.sh 3 | depends-on: early-fs-pre.target 4 | waits-for: early-fs-btrfs 5 | options: starts-on-console 6 | -------------------------------------------------------------------------------- /services/system: -------------------------------------------------------------------------------- 1 | # the actual primary chimera service 2 | 3 | type = internal 4 | depends-on: login.target 5 | depends-on: network.target 6 | waits-for.d: /usr/lib/dinit.d/boot.d 7 | -------------------------------------------------------------------------------- /early/scripts/modules-early.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=modules-early 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | exec @HELPER_PATH@/kmod static-modules 9 | -------------------------------------------------------------------------------- /services/early-kdump: -------------------------------------------------------------------------------- 1 | # handle kernel crash dumps 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/try-kdump.sh 5 | depends-on: early-devices.target 6 | depends-on: early-fs-local.target 7 | -------------------------------------------------------------------------------- /services/early-modules-early: -------------------------------------------------------------------------------- 1 | # static kernel modules loaded before device manager 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/modules-early.sh 5 | depends-on: early-prepare.target 6 | -------------------------------------------------------------------------------- /services/early-console.target: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/console.sh 3 | depends-on: early-devices.target 4 | depends-on: early-fs-local.target 5 | depends-on: early-keyboard.target 6 | -------------------------------------------------------------------------------- /services/early-tmpfiles: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/tmpfiles.sh --create --remove --boot --exclude-prefix=/dev 3 | depends-on: early-fs-local.target 4 | depends-on: pre-local.target 5 | -------------------------------------------------------------------------------- /services/local.target: -------------------------------------------------------------------------------- 1 | # rc.local has been run 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/local.sh 5 | depends-on: pre-local.target 6 | depends-on: early-tmpfiles 7 | waits-for: early-bless-boot 8 | -------------------------------------------------------------------------------- /services/early-tmpfiles-dev: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/tmpfiles.sh --prefix=/dev --create --boot 3 | depends-on: early-modules-early 4 | depends-on: early-pseudofs 5 | depends-on: early-tmpfs 6 | -------------------------------------------------------------------------------- /services/early-dev-settle: -------------------------------------------------------------------------------- 1 | # wait until all queued device events have been processed 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/dev.sh settle 5 | depends-on: early-devd 6 | depends-on: early-dev-trigger 7 | -------------------------------------------------------------------------------- /early/scripts/dmraid.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=dmraid 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | command -v dmraid > /dev/null 2>&1 || exit 0 9 | 10 | exec dmraid -i -ay 11 | -------------------------------------------------------------------------------- /services/zram-device: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/zram.sh start $1 3 | stop-command = @SCRIPT_PATH@/zram.sh stop $1 4 | depends-on: early-prepare.target 5 | depends-on: early-devd 6 | before: early-fs-pre 7 | -------------------------------------------------------------------------------- /early/scripts/console.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=${1:-console} 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | [ -x @DINIT_CONSOLE_PATH@ ] || exit 0 9 | 10 | exec @DINIT_CONSOLE_PATH@ "$1" 11 | -------------------------------------------------------------------------------- /services/early-fs-pre.target: -------------------------------------------------------------------------------- 1 | # just before filesystems are checked and mounted 2 | 3 | type = internal 4 | depends-on: early-devices.target 5 | depends-on: early-cryptdisks 6 | waits-for: early-dmraid 7 | waits-for: early-mdadm 8 | -------------------------------------------------------------------------------- /services/early-rng: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/rng.sh start 3 | stop-command = @SCRIPT_PATH@/rng.sh stop 4 | depends-on: early-devices.target 5 | waits-for: early-modules.target 6 | waits-for: early-fs-local.target 7 | -------------------------------------------------------------------------------- /services/early-lvm: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/lvm.sh start 3 | depends-on: early-devices.target 4 | depends-on: early-cryptdisks-early 5 | depends-ms: early-root-fsck 6 | waits-for: early-dmraid 7 | waits-for: early-mdadm 8 | -------------------------------------------------------------------------------- /services/early-prepare.target: -------------------------------------------------------------------------------- 1 | # earliest system bringup target 2 | 3 | type = internal 4 | depends-on: early-env 5 | depends-on: early-pseudofs 6 | depends-on: early-kernel-env 7 | depends-on: early-tmpfs 8 | depends-on: early-cgroups 9 | -------------------------------------------------------------------------------- /services/early-machine-id: -------------------------------------------------------------------------------- 1 | # try our best to make sure /etc/machine-id is available 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/machine-id.sh 5 | depends-on: early-rng 6 | depends-on: early-swclock 7 | waits-for: early-root-rw.target 8 | -------------------------------------------------------------------------------- /early/scripts/pseudofs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=pseudofs 4 | # can't mount in containers 5 | DINIT_NO_CONTAINER=1 6 | 7 | . @SCRIPT_PATH@/common.sh 8 | 9 | exec @HELPER_PATH@/mnt prepare ${dinit_early_root_remount:-ro,rshared} 10 | -------------------------------------------------------------------------------- /early/scripts/cryptdisks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE="cryptdisks-${1:-unknown}" 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | [ -x @DINIT_CRYPTDISKS_PATH@ ] || exit 0 9 | 10 | exec @DINIT_CRYPTDISKS_PATH@ "$@" 11 | -------------------------------------------------------------------------------- /services/early-fs-fstab.target: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/fs-fstab.sh start 3 | depends-on: early-fs-pre.target 4 | depends-ms: early-fs-fsck 5 | waits-for: early-fs-zfs 6 | waits-for: early-fs-btrfs 7 | waits-for: early-root-rw.target 8 | -------------------------------------------------------------------------------- /services/early-devmon: -------------------------------------------------------------------------------- 1 | # device monitor; it facilitates device dependencies 2 | 3 | type = process 4 | command = @SCRIPT_PATH@/devmon.sh 4 5 | smooth-recovery = yes 6 | ready-notification = pipefd:4 7 | depends-on: early-devd 8 | depends-ms: early-dev-settle 9 | -------------------------------------------------------------------------------- /services/boot: -------------------------------------------------------------------------------- 1 | # This is the primary entry point. It triggers startup 2 | # of every other service. In addition to that it also 3 | # provides the user-enabled service directory. 4 | 5 | type = internal 6 | depends-on: system 7 | waits-for.d: /etc/dinit.d/boot.d 8 | -------------------------------------------------------------------------------- /services/early-fs-local.target: -------------------------------------------------------------------------------- 1 | # all non-network filesystems are mounted 2 | 3 | type = internal 4 | depends-on: early-fs-pre.target 5 | waits-for: early-fs-btrfs 6 | waits-for: early-fs-zfs 7 | waits-for: early-root-rw.target 8 | waits-for: early-fs-fstab.target 9 | -------------------------------------------------------------------------------- /early/scripts/tmpfiles.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=tmpfiles 4 | 5 | . @SCRIPT_PATH@/common.sh 6 | 7 | sd-tmpfiles "$@" 8 | 9 | RET=$? 10 | case "$RET" in 11 | 65) exit 0 ;; # DATERR 12 | 73) exit 0 ;; # CANTCREAT 13 | *) exit $RET ;; 14 | esac 15 | -------------------------------------------------------------------------------- /services/early-root-fsck: -------------------------------------------------------------------------------- 1 | type = scripted 2 | command = @SCRIPT_PATH@/root-fsck.sh 3 | start-timeout = 0 # unlimited 4 | depends-on: early-prepare.target 5 | depends-ms: early-devd 6 | waits-for: early-dev-trigger 7 | options: starts-on-console pass-cs-fd start-interruptible skippable 8 | -------------------------------------------------------------------------------- /services/early-hwclock: -------------------------------------------------------------------------------- 1 | # set system time from harwdare clock 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/clock.sh hwclock start 5 | stop-command = @SCRIPT_PATH@/clock.sh hwclock stop 6 | depends-on: early-devd 7 | depends-on: early-prepare.target 8 | waits-for: early-root-rw.target 9 | -------------------------------------------------------------------------------- /early/scripts/dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | case "$1" in 4 | start|stop) DINIT_SERVICE=dev ;; 5 | trigger|settle) DINIT_SERVICE="dev-$1" ;; 6 | *) DINIT_SERVICE=dev-unknown ;; 7 | esac 8 | 9 | DINIT_NO_CONTAINER=1 10 | 11 | . @SCRIPT_PATH@/common.sh 12 | 13 | exec @DINIT_DEVD_PATH@ "$1" 14 | -------------------------------------------------------------------------------- /tmpfiles/x11.conf: -------------------------------------------------------------------------------- 1 | # This file is a part of dinit-chimera. 2 | # 3 | # See tmpfiles.d(5) for details. 4 | 5 | D! /tmp/.font-unix 1777 root root 10d 6 | D! /tmp/.ICE-unix 1777 root root 10d 7 | D! /tmp/.X11-unix 1777 root root 10d 8 | D! /tmp/.XIM-unix 1777 root root 10d 9 | r! /tmp/.X[0-9]*-lock 10 | -------------------------------------------------------------------------------- /services/early-cryptdisks-early: -------------------------------------------------------------------------------- 1 | # crypt devices available directly 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/cryptdisks.sh early start 5 | depends-on: early-devices.target 6 | depends-on: early-keyboard.target 7 | depends-ms: early-root-fsck 8 | waits-for: early-dmraid 9 | waits-for: early-mdadm 10 | options: starts-on-console 11 | -------------------------------------------------------------------------------- /services/early-swclock: -------------------------------------------------------------------------------- 1 | # adjust system date/time as necessary by timestamp/rtc 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/clock.sh swclock start 5 | stop-command = @SCRIPT_PATH@/clock.sh swclock stop 6 | depends-on: early-devd 7 | depends-on: early-prepare.target 8 | depends-on: early-fs-local.target 9 | waits-for: early-hwclock 10 | -------------------------------------------------------------------------------- /services/early-cryptdisks: -------------------------------------------------------------------------------- 1 | # remaining crypto devices 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/cryptdisks.sh remaining start 5 | depends-on: early-devices.target 6 | depends-on: early-cryptdisks-early 7 | depends-on: early-dmraid 8 | depends-on: early-lvm 9 | depends-ms: early-root-fsck 10 | waits-for: early-mdadm 11 | options: starts-on-console 12 | -------------------------------------------------------------------------------- /services/early-devd: -------------------------------------------------------------------------------- 1 | # run the early device manager; not supervised, meant to 2 | # be replaced with a supervised service later in the boot 3 | 4 | type = scripted 5 | command = @SCRIPT_PATH@/dev.sh start 6 | stop-command = @SCRIPT_PATH@/dev.sh stop 7 | depends-on: early-prepare.target 8 | depends-on: early-modules-early 9 | depends-on: early-tmpfiles-dev 10 | -------------------------------------------------------------------------------- /tmpfiles/meson.build: -------------------------------------------------------------------------------- 1 | tmpfiles = [ 2 | 'chimera.conf', 3 | 'home.conf', 4 | 'static-nodes-permissions.conf', 5 | 'tmp.conf', 6 | 'var.conf', 7 | 'x11.conf', 8 | ] 9 | 10 | foreach tmpf: tmpfiles 11 | install_data( 12 | tmpf, 13 | install_dir: tmpfdir, 14 | install_mode: 'rw-r--r--', 15 | ) 16 | endforeach 17 | -------------------------------------------------------------------------------- /tmpfiles/var.conf: -------------------------------------------------------------------------------- 1 | # This file is a part of dinit-chimera. 2 | # 3 | # See tmpfiles.d(5) for details. 4 | 5 | q /var 0755 - - - 6 | d /var/cache 0755 - - - 7 | d /var/lib 0755 - - - 8 | d /var/log 0755 - - - 9 | f /var/log/btmp 0660 root utmp - 10 | f /var/log/lastlog 0664 root utmp - 11 | f /var/log/wtmp 0664 root utmp - 12 | L /var/run - - - - ../run 13 | d /var/spool 0755 - - - 14 | -------------------------------------------------------------------------------- /early/scripts/fs-fstab.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=fs-fstab 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | case "$1" in 9 | start) 10 | exec mount -a -t "nosysfs,nonfs,nonfs4,nosmbfs,nocifs" -O no_netdev 11 | ;; 12 | stop) 13 | exec umount -r -a -t nosysfs,noproc,nodevtmpfs,notmpfs 14 | ;; 15 | *) exit 1 ;; 16 | esac 17 | -------------------------------------------------------------------------------- /early/scripts/kernel-env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Expose kernel environment in dinit 4 | # 5 | # Nothing to do here for now, as there is no way to tell what would 6 | # become environment variables. 7 | 8 | DINIT_SERVICE=kernel-env 9 | # containers do not clear environment so no need, also not portable 10 | DINIT_NO_CONTAINER=1 11 | 12 | . @SCRIPT_PATH@/common.sh 13 | 14 | set -e 15 | 16 | exit 0 17 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # add yourself here if name/email changes 2 | # 3 | # format: 4 | # 5 | # propername commitname 6 | 7 | q66 Daniel Kolesa 8 | q66 Daniel Kolesa 9 | q66 Daniel Kolesa 10 | q66 q66 11 | -------------------------------------------------------------------------------- /early/scripts/zram.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=zram 4 | 5 | . @SCRIPT_PATH@/common.sh 6 | 7 | if [ -n "$DINIT_CONTAINER" ]; then 8 | echo "zram must not be used in containers" 9 | exit 1 10 | fi 11 | 12 | if [ "$1" = "stop" ]; then 13 | exec @HELPER_PATH@/zram "$2" stop 14 | fi 15 | 16 | # we need this loaded 17 | @HELPER_PATH@/kmod load zram 18 | 19 | exec @HELPER_PATH@/zram "$2" 20 | -------------------------------------------------------------------------------- /early/scripts/fs-btrfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=fs-btrfs 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | command -v btrfs > /dev/null 2>&1 || exit 0 9 | 10 | if [ -r /proc/cmdline ]; then 11 | for x in $(cat /proc/cmdline); do 12 | case "$x" in 13 | dinit_skip_volumes) exit 0 ;; 14 | esac 15 | done 16 | fi 17 | 18 | exec btrfs device scan 19 | -------------------------------------------------------------------------------- /services/pre-local.target: -------------------------------------------------------------------------------- 1 | # core system init is done 2 | 3 | type = scripted 4 | command = @SCRIPT_PATH@/done.sh 5 | depends-on: early-fs-local.target 6 | depends-on: early-console.target 7 | depends-on: early-net-lo 8 | depends-on: early-hostname 9 | waits-for: early-swap 10 | waits-for: early-rng 11 | waits-for: early-machine-id 12 | waits-for: early-sysctl 13 | waits-for: early-binfmt 14 | waits-for: early-kdump 15 | -------------------------------------------------------------------------------- /tmpfiles/chimera.conf: -------------------------------------------------------------------------------- 1 | # This file is a part of dinit-chimera. 2 | # 3 | # See tmpfiles.d(5) for details. 4 | 5 | d /etc/dinit.d/boot.d 0755 root root - 6 | d /usr/lib/dinit.d/boot.d 0755 root root - 7 | 8 | d /run/lvm 0700 root root - 9 | d /run/user 0755 root root - 10 | d /run/lock 0777 root root - 11 | d /run/log 0755 root root - 12 | 13 | L+ /etc/mtab - - - - ../proc/self/mounts 14 | L /var/lock - - - - ../run/lock 15 | -------------------------------------------------------------------------------- /tmpfiles/static-nodes-permissions.conf: -------------------------------------------------------------------------------- 1 | # This file is a part of dinit-chimera. 2 | # 3 | # See tmpfiles.d(5) for details. 4 | 5 | z /dev/snd/seq 0660 - audio - 6 | z /dev/snd/timer 0660 - audio - 7 | z /dev/loop-control 0660 - disk - 8 | z /dev/net/tun 0666 - - - 9 | z /dev/fuse 0666 - - - 10 | z /dev/kvm 0660 - kvm - 11 | z /dev/vhost-net 0660 - kvm - 12 | z /dev/vhost-vsock 0660 - kvm - 13 | -------------------------------------------------------------------------------- /early/scripts/mdadm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=mdadm 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | command -v mdadm > /dev/null 2>&1 || exit 0 9 | 10 | CONFIG=/etc/mdadm.conf 11 | ALTCONFIG=/etc/mdadm/mdadm.conf 12 | 13 | [ ! -f "$CONFIG" ] && [ -f "$ALTCONFIG" ] && CONFIG="$ALTCONFIG" || : 14 | 15 | # no config 16 | if [ ! -f "$CONFIG" ]; then 17 | exit 0 18 | fi 19 | 20 | # no array in config 21 | if ! grep -q "^ARRAY" "$CONFIG"; then 22 | exit 0 23 | fi 24 | 25 | mdadm -As || : 26 | -------------------------------------------------------------------------------- /early/scripts/clock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE="${1:-clock}" 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | [ -r /etc/hwclock ] && read -r HWCLOCK < /etc/hwclock 9 | 10 | case "$1" in 11 | hwclock|swclock) ;; 12 | *) exit 1 ;; 13 | esac 14 | 15 | HELPER=$1 16 | shift 17 | 18 | case "$1" in 19 | start|stop) ;; 20 | *) exit 1 ;; 21 | esac 22 | 23 | case "$HWCLOCK" in 24 | utc|localtime) set -- "$1" "$HWCLOCK" ;; 25 | *) set -- "$1" ;; 26 | esac 27 | 28 | exec "@HELPER_PATH@/${HELPER}" "$@" 29 | -------------------------------------------------------------------------------- /early/scripts/binfmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=binfmt 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | if [ "$1" = "stop" ]; then 9 | exec @HELPER_PATH@/binfmt -u 10 | fi 11 | 12 | # require the module if it's around, but don't fail - it may be builtin 13 | @HELPER_PATH@/kmod load binfmt_misc 14 | 15 | # try to make sure it's mounted too, otherwise binfmt-helper will fail 16 | @HELPER_PATH@/mnt try /proc/sys/fs/binfmt_misc binfmt_misc binfmt_misc \ 17 | nosuid,noexec,nodev 2>/dev/null 18 | 19 | exec @HELPER_PATH@/binfmt 20 | -------------------------------------------------------------------------------- /early/scripts/bless-boot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=bless-boot 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | bless=@BLESS_BOOT_PATH@ 9 | 10 | [ -x $bless ] || exit 0 11 | 12 | case "$($bless status)" in 13 | indeterminate) 14 | # bless quietly 15 | $bless good 16 | ;; 17 | bad) 18 | # notify and bless 19 | echo "Successful boot from bad image, clearing..." 20 | $bless good 21 | ;; 22 | *) 23 | # probably not used 24 | ;; 25 | esac 26 | 27 | exit 0 28 | -------------------------------------------------------------------------------- /early/scripts/lvm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=lvm 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | command -v vgchange > /dev/null 2>&1 || exit 0 9 | 10 | if [ -r /proc/cmdline ]; then 11 | for x in $(cat /proc/cmdline); do 12 | case "$x" in 13 | dinit_skip_volumes) exit 0 ;; 14 | esac 15 | done 16 | fi 17 | 18 | case "$1" in 19 | start) exec vgchange --sysinit -a ay ;; 20 | stop) 21 | if [ $(vgs | wc -l) -gt 0 ]; then 22 | exec vgchange -an 23 | fi 24 | ;; 25 | esac 26 | -------------------------------------------------------------------------------- /early/scripts/fs-zfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # TODO: actually handle errors properly 4 | 5 | DINIT_SERVICE=fs-zfs 6 | DINIT_NO_CONTAINER=1 7 | 8 | . @SCRIPT_PATH@/common.sh 9 | 10 | command -v zfs > /dev/null 2>&1 || exit 0 11 | command -v zpool > /dev/null 2>&1 || exit 0 12 | 13 | if [ -r /proc/cmdline ]; then 14 | for x in $(cat /proc/cmdline); do 15 | case "$x" in 16 | dinit_skip_volumes) exit 0 ;; 17 | esac 18 | done 19 | fi 20 | 21 | if [ -e /etc/zfs/zpool.cache ]; then 22 | zpool import -N -a -c /etc/zfs/zpool.cache || exit 0 23 | else 24 | zpool import -N -a -o cachefile=none || exit 0 25 | fi 26 | 27 | zfs mount -a -l || exit 0 28 | zfs share -a || : 29 | -------------------------------------------------------------------------------- /early/scripts/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Common code sourced into each early boot script. 4 | 5 | # sanitize common PATH 6 | export PATH=/sbin:/bin:/usr/sbin:/usr/bin 7 | 8 | # convenience debug logging function 9 | log_debug() { 10 | [ -n "$DINIT_EARLY_DEBUG" ] || return 0 11 | echo "INIT:" "$@" 12 | if [ -n "$DINIT_EARLY_DEBUG_SLOW" ]; then 13 | sleep "$DINIT_EARLY_DEBUG_SLOW" 14 | fi 15 | } 16 | 17 | # if requested, append all to logfile 18 | if [ -n "$DINIT_EARLY_DEBUG" -a -n "$DINIT_EARLY_DEBUG_LOG" ]; then 19 | exec 1>>"$DINIT_EARLY_DEBUG_LOG" 20 | exec 2>&1 21 | fi 22 | 23 | [ -z "$DINIT_CONTAINER" -o -z "$DINIT_NO_CONTAINER" ] || exit 0 24 | 25 | log_debug "$DINIT_SERVICE" 26 | -------------------------------------------------------------------------------- /early/scripts/done.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # tries to commit machine-id to disk to mark boot done 4 | # 5 | 6 | DINIT_SERVICE=done 7 | # the mount test would fail, might as well just skip it altogether 8 | DINIT_NO_CONTAINER=1 9 | 10 | . @SCRIPT_PATH@/common.sh 11 | 12 | # was never bind-mounted, so just exit 13 | @HELPER_PATH@/mnt is /etc/machine-id || exit 0 14 | # no generated machine-id 15 | test -e /run/dinit/machine-id || exit 0 16 | 17 | @HELPER_PATH@/mnt umnt /etc/machine-id 18 | 19 | if touch /etc/machine-id > /dev/null 2>&1; then 20 | cat /run/dinit/machine-id > /etc/machine-id 21 | else 22 | # failed to write, bind it again 23 | @HELPER_PATH@/mnt mnt /etc/machine-id /run/dinit/machine-id none bind 24 | fi 25 | 26 | exit 0 27 | -------------------------------------------------------------------------------- /early/scripts/hostname.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=hostname 4 | 5 | . @SCRIPT_PATH@/common.sh 6 | 7 | [ -r /etc/hostname ] && read -r HOSTNAME < /etc/hostname 8 | [ -z "$HOSTNAME" ] && HOSTNAME=chimera 9 | 10 | set_hostname() { 11 | # some container envs allow setting hostname via syscall, 12 | # but not via procfs; so default to using a command, falling 13 | # back to procfs when available and when the command is not 14 | if command -v hostname > /dev/null 2>&1; then 15 | hostname "$1" 16 | elif [ -e /proc/sys/kernel/hostname ]; then 17 | printf "%s" "$1" > /proc/sys/kernel/hostname 18 | fi 19 | } 20 | 21 | # in some environments this may fail 22 | set_hostname "$HOSTNAME" > /dev/null 2>&1 || : 23 | -------------------------------------------------------------------------------- /early/scripts/cgroups.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=cgroups 4 | DINIT_NO_CONTAINER=1 5 | 6 | set -e 7 | 8 | . @SCRIPT_PATH@/common.sh 9 | 10 | CG_PATH="/sys/fs/cgroup" 11 | 12 | mkdir -p "$CG_PATH" 13 | @HELPER_PATH@/mnt try "$CG_PATH" cgroup2 cgroup2 nsdelegate 14 | 15 | # just in case 16 | [ -e "${CG_PATH}/cgroup.subtree_control" ] || exit 0 17 | [ -e "${CG_PATH}/cgroup.controllers" ] || exit 0 18 | 19 | # get the available controllers 20 | read -r CG_ACTIVE < "${CG_PATH}/cgroup.controllers" 21 | 22 | # enable them individually; if some fail, that's ok 23 | # we want to enable things here as it may not be possible later 24 | # (e.g. cpu will not enable when there are any rt processes running) 25 | for cont in ${CG_ACTIVE}; do 26 | echo "+${cont}" > "${CG_PATH}/cgroup.subtree_control" 2>/dev/null || : 27 | done 28 | -------------------------------------------------------------------------------- /early/helpers/clock_common.hh: -------------------------------------------------------------------------------- 1 | #ifndef CLOCK_COMMON_H 2 | #define CLOCK_COMMON_H 3 | 4 | #include 5 | #include 6 | 7 | typedef enum { 8 | RTC_MOD_UTC, 9 | RTC_MOD_LOCALTIME, 10 | } rtc_mod_t; 11 | 12 | static rtc_mod_t rtc_mod_guess(void) { 13 | rtc_mod_t ret = RTC_MOD_UTC; 14 | 15 | FILE *f = fopen("/etc/adjtime", "r"); 16 | if (!f) { 17 | return RTC_MOD_UTC; 18 | } 19 | 20 | char buf[256]; 21 | while (fgets(buf, sizeof(buf), f)) { 22 | /* last line will decide it, compliant file should be 3 lines */ 23 | if (!strncmp(buf, "LOCAL", 5)) { 24 | ret = RTC_MOD_LOCALTIME; 25 | break; 26 | } else if (!strncmp(buf, "UTC", 3)) { 27 | ret = RTC_MOD_UTC; 28 | break; 29 | } 30 | } 31 | 32 | fclose(f); 33 | return ret; 34 | } 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /early/helpers/meson.build: -------------------------------------------------------------------------------- 1 | helpers = [ 2 | ['binfmt', ['binfmt.cc'], [], []], 3 | ['devclient', ['devclient.cc'], [], []], 4 | ['devmon', ['devmon.cc'], [], []], 5 | ['hwclock', ['hwclock.cc'], [], []], 6 | ['swclock', ['swclock.cc'], [], []], 7 | ['kmod', ['kmod.cc'], [kmod_dep], []], 8 | ['lo', ['lo.cc'], [], []], 9 | ['mnt', ['mnt.cc'], [], []], 10 | ['seedrng', ['seedrng.cc'], [], []], 11 | ['sysctl', ['sysctl.cc'], [], []], 12 | ['swap', ['swap.cc'], [], []], 13 | ] 14 | 15 | if build_machine.kernel() == 'linux' 16 | helpers += [['zram', ['zram.cc'], [], []]] 17 | endif 18 | 19 | foreach helper: helpers 20 | executable( 21 | helper[0], helper[1], 22 | dependencies: helper[2], 23 | cpp_args: helper[3], 24 | install: true, 25 | install_dir: earlydir / 'helpers' 26 | ) 27 | endforeach 28 | 29 | install_symlink('mnt-service', install_dir: earlydir / 'helpers', pointing_to: 'mnt') 30 | -------------------------------------------------------------------------------- /man/early-modules.target.8: -------------------------------------------------------------------------------- 1 | .Dd June 1, 2016 2 | .Dt EARLY-MODULES.TARGET 8 3 | .Os Linux 4 | .Sh NAME 5 | .Nm early-modules.target 6 | .Nd Configure kernel modules to load at boot 7 | .Sh SYNOPSIS 8 | .Nm early-modules.target 9 | .Sh DESCRIPTION 10 | .Nm 11 | early-boot service reads files which contain kernel modules to load 12 | during boot from the list of locations below. 13 | .El 14 | .Sh FILES 15 | Configuration files are read from the following locations: 16 | .Bl -tag -width indent 17 | .It /etc/modules-load.d/*.conf 18 | .It /run/modules-load.d/*.conf 19 | .It /usr/lib/modules-load.d/*.conf 20 | .El 21 | .Pp 22 | The configuration files should simply contain a list of kernel module names 23 | to load, separated by newlines. 24 | Empty lines and lines whose first non-whitespace character is # or ; are 25 | ignored. 26 | .Sh EXAMPLES 27 | .Pa /etc/modules-load.d/virtio-net.conf : 28 | .Bd -literal -offset indent 29 | # Load virtio-net.ko at boot 30 | virtio-net 31 | .Ed 32 | .Sh SEE ALSO 33 | .Xr modprobe 8 34 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('bless-boot-path', 2 | type: 'string', 3 | value: '', 4 | description: 'path to systemd-bless-boot (default: libexecdir/systemd-bless-boot)' 5 | ) 6 | 7 | option('dinit-console-path', 8 | type: 'string', 9 | value: '', 10 | description: 'path to dinit-console (default: libexecdir/dinit-console)' 11 | ) 12 | 13 | option('dinit-cryptdisks-path', 14 | type: 'string', 15 | value: '', 16 | description: 'path to dinit-cryptdisks (default: libexecdir/dinit-cryptdisks)' 17 | ) 18 | 19 | option('dinit-devd-path', 20 | type: 'string', 21 | value: '', 22 | description: 'path to dinit-devd (default: libexecdir/dinit-console)' 23 | ) 24 | 25 | option('dinit-sulogin-path', 26 | type: 'string', 27 | value: '', 28 | description: 'path to sulogin (default: sbindir/sulogin)' 29 | ) 30 | 31 | option('default-path-env', 32 | type: 'string', 33 | value: '/sbin:/usr/sbin:/bin:/usr/bin', 34 | description: 'default PATH to use for init' 35 | ) 36 | -------------------------------------------------------------------------------- /shutdown-hook: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # run after all services have shut down and 4 | # remaining processes have been terminated 5 | # 6 | 7 | export PATH=/sbin:/bin:/usr/sbin:/usr/bin 8 | 9 | # assume proper directory 10 | cd @EARLY_PATH@/.. 11 | 12 | if [ ! -e /run/dinit/container ]; then 13 | echo "Disabling swap..." 14 | ./early/scripts/swap.sh stop 15 | echo "Unmounting network filesystems..." 16 | umount -l -a -t nfs,nfs4,smbfs,cifs 17 | umount -l -a -O netdev 18 | echo "Unmounting filesystems..." 19 | ./early/scripts/fs-fstab.sh stop 20 | echo "Remounting root read-only..." 21 | ./early/helpers/mnt rmnt / ro 22 | fi 23 | 24 | sync 25 | 26 | if [ ! -e /run/dinit/container ]; then 27 | export DM_DISABLE_UDEV=1 28 | echo "Deactivating cryptdisks..." 29 | ./early/scripts/cryptdisks.sh remaining stop 30 | echo "Deactivating volume groups..." 31 | ./early/scripts/lvm.sh stop 32 | echo "Deactivating remaining cryptdisks..." 33 | ./early/scripts/cryptdisks.sh early stop 34 | fi 35 | -------------------------------------------------------------------------------- /early/scripts/fs-fsck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=fs-fsck 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | command -v fsck > /dev/null 2>&1 || exit 0 9 | 10 | FORCEARG= 11 | FIXARG="-a" 12 | 13 | if [ -r /proc/cmdline ]; then 14 | for x in $(cat /proc/cmdline); do 15 | case "$x" in 16 | fastboot|fsck.mode=skip) 17 | echo "Skipping filesystem checks (fastboot)." 18 | exit 0 19 | ;; 20 | forcefsck|fsck.mode=force) 21 | FORCEARG="-f" 22 | ;; 23 | fsckfix|fsck.repair=yes) 24 | FIXARG="-y" 25 | ;; 26 | fsck.repair=no) 27 | FIXARG="-n" 28 | ;; 29 | esac 30 | done 31 | fi 32 | 33 | fsck -A -R -C -t noopts=_netdev $FORCEARG $FIXARG 34 | FSCKRET=$? 35 | 36 | if [ $(($FSCKRET & 4)) -eq 4 ]; then 37 | echo "ERROR: at least one fstab filesystem has unrecoverable errors." 38 | exit 1 39 | fi 40 | 41 | # we don't care about the other conditions much; the 42 | # filesystems were either repaired or nothing has happened 43 | exit 0 44 | -------------------------------------------------------------------------------- /early/scripts/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Expose environment variables in dinit activation environment 4 | # 5 | # This allows early services to work more generically without assumptions 6 | 7 | set -e 8 | 9 | # passed by the kernel 10 | if [ "$dinit_early_debug" ]; then 11 | dinitctl --use-passed-cfd setenv "DINIT_EARLY_DEBUG=1" 12 | # slow execution of each 13 | if [ -n "$dinit_early_debug_slow" ]; then 14 | dinitctl --use-passed-cfd setenv "DINIT_EARLY_DEBUG_SLOW=$dinit_early_debug_slow" 15 | fi 16 | if [ -n "$dinit_early_debug_log" ]; then 17 | dinitctl --use-passed-cfd setenv "DINIT_EARLY_DEBUG_LOG=$dinit_early_debug_log" 18 | fi 19 | fi 20 | 21 | # detect if running in a container, expose it globally 22 | if [ -n "${container+x}" ]; then 23 | dinitctl --use-passed-cfd setenv DINIT_CONTAINER=1 24 | fi 25 | 26 | # detect first boot 27 | if [ ! -e /etc/machine-id ]; then 28 | dinitctl --use-passed-cfd setenv DINIT_FIRST_BOOT=1 29 | elif [ "$(cat /etc/machine-id)" = "uninitialized" ]; then 30 | dinitctl --use-passed-cfd setenv DINIT_FIRST_BOOT=1 31 | fi 32 | 33 | # mount service 34 | dinitctl --use-passed-cfd setenv "DINIT_MOUNT=@HELPER_PATH@/mnt-service" 35 | 36 | exit 0 37 | -------------------------------------------------------------------------------- /early/scripts/machine-id.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # prepares a valid machine-id until it can be written to disk (maybe never) 4 | # 5 | 6 | DINIT_SERVICE=machine-id 7 | 8 | . @SCRIPT_PATH@/common.sh 9 | 10 | set -e 11 | umask 022 12 | 13 | gen_machineid() { 14 | if command -v dbus-uuidgen > /dev/null 2>&1; then 15 | dbus-uuidgen 16 | else 17 | od -An -N16 -tx /dev/urandom | tr -d ' ' 18 | fi 19 | } 20 | 21 | # first boot or empty machine-id; generate something we can use 22 | if [ -e /run/dinit/first-boot -o ! -s /etc/machine-id ]; then 23 | gen_machineid > /run/dinit/machine-id 24 | fi 25 | 26 | # missing machine-id and writable fs; set to uninitialized 27 | if [ ! -e /etc/machine-id ] && touch /etc/machine-id > /dev/null 2>&1; then 28 | echo uninitialized > /etc/machine-id 29 | fi 30 | 31 | # if we generated one, bind-mount it over the real file 32 | if [ -e /run/dinit/machine-id -a -e /etc/machine-id ]; then 33 | # containers can't mount but might have a mutable fs 34 | if [ -n "$DINIT_CONTAINER" ]; then 35 | cat /run/dinit/machine-id > /etc/machine-id 36 | exit 0 37 | fi 38 | @HELPER_PATH@/mnt mnt /etc/machine-id /run/dinit/machine-id none bind 39 | fi 40 | 41 | exit 0 42 | -------------------------------------------------------------------------------- /COPYING.md: -------------------------------------------------------------------------------- 1 | Copyright 2021-2024 q66 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 17 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 18 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 20 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 22 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /early/scripts/tmpfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=tmpfs 4 | 5 | . @SCRIPT_PATH@/common.sh 6 | 7 | umask 022 8 | set -e 9 | 10 | # default unset 11 | RUNSIZE= 12 | 13 | # if initramfs-tools is used, source its configs for consistent runsize 14 | if [ -r /etc/initramfs-tools/initramfs.conf ]; then 15 | . /etc/initramfs-tools/initramfs.conf 16 | for conf in /etc/initramfs-tools/conf.d/*; do 17 | [ -f "$conf" ] && . "$conf" 18 | done 19 | fi 20 | 21 | # overrides via kernel cmdline 22 | if [ -r /proc/cmdline ]; then 23 | for x in $(cat /proc/cmdline); do 24 | case "$x" in 25 | # initramfs-tools compat 26 | initramfs.runsize=*) 27 | RUNSIZE="${x#initramfs.runsize=}" 28 | ;; 29 | dinit.runsize=*) 30 | RUNSIZE="${x#dinit.runsize=}" 31 | ;; 32 | esac 33 | done 34 | fi 35 | 36 | RUNSIZE="${RUNSIZE:-10%}" 37 | 38 | @HELPER_PATH@/mnt try /run tmpfs tmpfs "nodev,noexec,nosuid,size=${RUNSIZE},mode=0755" 39 | 40 | # readable system state 41 | mkdir -p /run/dinit /run/user 42 | 43 | # mount /run/user at this point, should *not* be noexec (breaks some flatpaks) 44 | # give it the same max size as /run itself, generally it should be tiny so 45 | # it does not need the 50% default at any point 46 | @HELPER_PATH@/mnt try /run/user tmpfs tmpfs "nodev,nosuid,size=${RUNSIZE},mode=0755" 47 | 48 | # now that we a /run, expose container as state file too (for shutdown etc) 49 | if [ -n "$DINIT_CONTAINER" ]; then 50 | touch /run/dinit/container 51 | fi 52 | 53 | # ditto 54 | if [ -n "$DINIT_FIRST_BOOT" ]; then 55 | touch /run/dinit/first-boot 56 | fi 57 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'dinit-chimera', 3 | ['cpp'], 4 | version: '0.99.22', 5 | default_options: [ 6 | 'prefix=/usr', 7 | 'cpp_std=c++17', 8 | 'cpp_eh=none', 'cpp_rtti=false', 9 | 'warning_level=3', 10 | 'buildtype=debugoptimized', 11 | ], 12 | license: 'BSD-2-Clause', 13 | ) 14 | 15 | pfx = get_option('prefix') 16 | lexecdir = get_option('libexecdir') 17 | sbindir = get_option('sbindir') 18 | dlibdir = get_option('libdir') / 'dinit' 19 | tmpfdir = get_option('libdir') / 'tmpfiles.d' 20 | srvdir = get_option('libdir') / 'dinit.d' 21 | earlydir = srvdir / 'early' 22 | 23 | cpp = meson.get_compiler('cpp') 24 | 25 | kmod_dep = dependency('libkmod') 26 | 27 | bless_boot_path = get_option('bless-boot-path') 28 | dinit_console_path = get_option('dinit-console-path') 29 | dinit_cryptdisks_path = get_option('dinit-cryptdisks-path') 30 | dinit_devd_path = get_option('dinit-devd-path') 31 | dinit_sulogin_path = get_option('dinit-sulogin-path') 32 | dinit_path = pfx / sbindir / 'dinit' 33 | 34 | if bless_boot_path == '' 35 | bless_boot_path = pfx / lexecdir / 'systemd-bless-boot' 36 | endif 37 | 38 | if dinit_console_path == '' 39 | dinit_console_path = pfx / lexecdir / 'dinit-console' 40 | endif 41 | 42 | if dinit_cryptdisks_path == '' 43 | dinit_cryptdisks_path = pfx / lexecdir / 'dinit-cryptdisks' 44 | endif 45 | 46 | if dinit_devd_path == '' 47 | dinit_devd_path = pfx / lexecdir / 'dinit-devd' 48 | endif 49 | 50 | if dinit_sulogin_path == '' 51 | dinit_sulogin_path = pfx / sbindir / 'sulogin' 52 | endif 53 | 54 | subdir('early/helpers') 55 | subdir('early/scripts') 56 | subdir('man') 57 | subdir('services') 58 | subdir('tmpfiles') 59 | -------------------------------------------------------------------------------- /early/helpers/devclient.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Device monitor client program 3 | * 4 | * The client program is meant to be spawned per device watch and 5 | * stays running as long as the device remains available; it will 6 | * not signal readiness until the device has become available. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | * 10 | * Copyright (c) 2024 q66 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 1. Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 | * SUCH DAMAGE. 32 | */ 33 | 34 | #include 35 | 36 | int main() { 37 | warnx("This is a dummy implementation without functionality."); 38 | 39 | return 1; 40 | } 41 | -------------------------------------------------------------------------------- /services/meson.build: -------------------------------------------------------------------------------- 1 | svconfd = configuration_data() 2 | 3 | svconfd.set('EARLY_PATH', pfx / srvdir / 'early') 4 | svconfd.set('HELPER_PATH', pfx / srvdir / 'early/helpers') 5 | svconfd.set('SCRIPT_PATH', pfx / srvdir / 'early/scripts') 6 | svconfd.set('DINIT_SULOGIN_PATH', dinit_sulogin_path) 7 | 8 | services = [ 9 | 'boot', 10 | 'device', 11 | 'early-binfmt', 12 | 'early-bless-boot', 13 | 'early-cgroups', 14 | 'early-console.target', 15 | 'early-cryptdisks', 16 | 'early-cryptdisks-early', 17 | 'early-devices.target', 18 | 'early-devmon', 19 | 'early-dmraid', 20 | 'early-env', 21 | 'early-fs-btrfs', 22 | 'early-fs-fsck', 23 | 'early-fs-fstab.target', 24 | 'early-fs-local.target', 25 | 'early-fs-pre.target', 26 | 'early-fs-zfs', 27 | 'early-hostname', 28 | 'early-hwclock', 29 | 'early-kdump', 30 | 'early-kernel-env', 31 | 'early-keyboard.target', 32 | 'early-lvm', 33 | 'early-machine-id', 34 | 'early-mdadm', 35 | 'early-modules-early', 36 | 'early-modules', 37 | 'early-modules.target', 38 | 'early-net-lo', 39 | 'early-prepare.target', 40 | 'early-pseudofs', 41 | 'early-rng', 42 | 'early-root-fsck', 43 | 'early-root-rw.target', 44 | 'early-swap', 45 | 'early-swclock', 46 | 'early-sysctl', 47 | 'early-tmpfs', 48 | 'early-tmpfiles', 49 | 'early-tmpfiles-dev', 50 | 'early-dev-settle', 51 | 'early-dev-trigger', 52 | 'early-devd', 53 | 'local.target', 54 | 'login.target', 55 | 'network.target', 56 | 'pre-local.target', 57 | 'pre-network.target', 58 | 'recovery', 59 | 'single', 60 | 'system', 61 | 'time-sync.target', 62 | ] 63 | 64 | if build_machine.kernel() == 'linux' 65 | services += ['zram-device'] 66 | endif 67 | 68 | foreach srv: services 69 | configure_file( 70 | input: srv, 71 | output: srv, 72 | configuration: svconfd, 73 | format: 'cmake@', 74 | install: true, 75 | install_dir: srvdir, 76 | install_mode: 'rw-r--r--', 77 | ) 78 | endforeach 79 | -------------------------------------------------------------------------------- /init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Make sure dinit runs with a clean environment, 4 | # while also ensuring that PATH is set in container 5 | # environments 6 | # 7 | 8 | # source this file if it exists, for any overrides 9 | if [ -r /etc/dinit/init ]; then 10 | . /etc/dinit/init 11 | fi 12 | 13 | # global default, may be "unlimited" or any integer value 14 | if [ -n "$dinit_rlimit_core" ]; then 15 | ulimit -c "$dinit_rlimit_core" 16 | fi 17 | 18 | if [ "$dinit_auto_recovery" = "1" ]; then 19 | set -- --auto-recovery "$@" 20 | fi 21 | 22 | if [ "$dinit_quiet" = "1" ]; then 23 | set -- --quiet "$@" 24 | fi 25 | 26 | if [ -n "$dinit_log_level" ]; then 27 | set -- --log-level "$dinit_log_level" "$@" 28 | fi 29 | 30 | if [ -n "$dinit_console_level" ]; then 31 | set -- --console-level "$dinit_console_level" "$@" 32 | fi 33 | 34 | if [ -n "$dinit_log_file" ]; then 35 | set -- --log-file "$dinit_log_file" "$@" 36 | fi 37 | 38 | unset dinit_auto_recovery dinit_quiet dinit_log_level 39 | unset dinit_console_level dinit_log_file 40 | 41 | export PATH=@DEFAULT_PATH_ENV@ 42 | 43 | # in a container, exec directly as we don't have a way to deal with 44 | # the init env after the fact, and there is no initramfs anyway 45 | if [ -n "${container+x}" ]; then 46 | exec @DINIT_PATH@ "$@" 47 | fi 48 | 49 | # afaik getent is not a posix command 50 | getent_cmd=$(command -v getent) 51 | 52 | if [ -n "$getent_cmd" ]; then 53 | # retrieve using getent if we can 54 | HOME=$("$getent_cmd" passwd root | cut -f6 -d:) 55 | else 56 | # otherwise just grep from the passwd database... 57 | HOME=$(grep "^root:" /etc/passwd | cut -f6 -d:) 58 | fi 59 | 60 | # fallback just in case we don't have anything 61 | [ -n "$HOME" ] || HOME=/ 62 | 63 | # minimal defaults 64 | set -- PATH=@DEFAULT_PATH_ENV@ "HOME=$HOME" @DINIT_PATH@ "$@" 65 | 66 | # these need to be readable before we have procfs 67 | if [ "$dinit_early_debug" ]; then 68 | set -- \ 69 | dinit_early_debug=$dinit_early_debug \ 70 | dinit_early_debug_slow=$dinit_early_debug_slow \ 71 | dinit_early_debug_log=$dinit_early_debug_log \ 72 | "$@" 73 | fi 74 | 75 | # also respect this 76 | if [ "$dinit_early_root_remount" ]; then 77 | set -- dinit_early_root_remount=$dinit_early_root_remount "$@" 78 | fi 79 | 80 | # if not a container, exec in a mostly clean env... 81 | exec env -i "$@" 82 | -------------------------------------------------------------------------------- /early/scripts/meson.build: -------------------------------------------------------------------------------- 1 | confd = configuration_data() 2 | 3 | confd.set_quoted('DINIT_PATH', dinit_path) 4 | confd.set_quoted('BLESS_BOOT_PATH', bless_boot_path) 5 | confd.set_quoted('DINIT_CONSOLE_PATH', dinit_console_path) 6 | confd.set_quoted('DINIT_CRYPTDISKS_PATH', dinit_cryptdisks_path) 7 | confd.set_quoted('DINIT_DEVD_PATH', dinit_devd_path) 8 | confd.set_quoted('DINIT_SULOGIN_PATH', dinit_sulogin_path) 9 | 10 | confd.set('EARLY_PATH', pfx / srvdir / 'early') 11 | confd.set('HELPER_PATH', pfx / srvdir / 'early/helpers') 12 | confd.set('SCRIPT_PATH', pfx / srvdir / 'early/scripts') 13 | 14 | confd.set('DEFAULT_PATH_ENV', get_option('default-path-env')) 15 | 16 | scripts = [ 17 | 'binfmt.sh', 18 | 'bless-boot.sh', 19 | 'cgroups.sh', 20 | 'clock.sh', 21 | 'common.sh', 22 | 'console.sh', 23 | 'cryptdisks.sh', 24 | 'dev.sh', 25 | 'devmon.sh', 26 | 'dmraid.sh', 27 | 'done.sh', 28 | 'env.sh', 29 | 'fs-btrfs.sh', 30 | 'fs-fsck.sh', 31 | 'fs-fstab.sh', 32 | 'fs-zfs.sh', 33 | 'hostname.sh', 34 | 'kdump.sh', 35 | 'kernel-env.sh', 36 | 'local.sh', 37 | 'lvm.sh', 38 | 'machine-id.sh', 39 | 'mdadm.sh', 40 | 'modules-early.sh', 41 | 'modules.sh', 42 | 'net-lo.sh', 43 | 'pseudofs.sh', 44 | 'rng.sh', 45 | 'root-fsck.sh', 46 | 'root-rw.sh', 47 | 'swap.sh', 48 | 'sysctl.sh', 49 | 'tmpfs.sh', 50 | 'tmpfiles.sh', 51 | 'try-kdump.sh', 52 | ] 53 | 54 | if build_machine.kernel() == 'linux' 55 | scripts += ['zram.sh'] 56 | endif 57 | 58 | foreach scr: scripts 59 | configure_file( 60 | input: scr, 61 | output: scr, 62 | configuration: confd, 63 | format: 'cmake@', 64 | install: true, 65 | install_dir: earlydir / 'scripts', 66 | install_mode: 'rwxr-xr-x', 67 | ) 68 | endforeach 69 | 70 | # shutdown hook for oneshot actions 71 | configure_file( 72 | input: '../../shutdown-hook', 73 | output: 'shutdown-hook', 74 | configuration: confd, 75 | format: 'cmake@', 76 | install: true, 77 | install_dir: dlibdir, 78 | install_mode: 'rwxr-xr-x', 79 | ) 80 | 81 | # init file 82 | configure_file( 83 | input: '../../init', 84 | output: 'init', 85 | configuration: confd, 86 | format: 'cmake@', 87 | install: true, 88 | install_dir: sbindir, 89 | install_mode: 'rwxr-xr-x', 90 | ) 91 | -------------------------------------------------------------------------------- /early/scripts/root-fsck.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | DINIT_SERVICE=root-fsck 4 | DINIT_NO_CONTAINER=1 5 | 6 | . @SCRIPT_PATH@/common.sh 7 | 8 | command -v fsck > /dev/null 2>&1 || exit 0 9 | 10 | FORCEARG= 11 | FIXARG="-a" 12 | 13 | if [ -r /proc/cmdline ]; then 14 | for x in $(cat /proc/cmdline); do 15 | case "$x" in 16 | fastboot|fsck.mode=skip) 17 | echo "Skipping root filesystem check (fastboot)." 18 | exit 0 19 | ;; 20 | forcefsck|fsck.mode=force) 21 | FORCEARG="-f" 22 | ;; 23 | fsckfix|fsck.repair=yes) 24 | FIXARG="-y" 25 | ;; 26 | fsck.repair=no) 27 | FIXARG="-n" 28 | ;; 29 | esac 30 | done 31 | fi 32 | 33 | mntent() { 34 | @HELPER_PATH@/mnt getent "$1" / "$2" 2>/dev/null 35 | } 36 | 37 | ROOTFSPASS=$(mntent /etc/fstab passno) 38 | # skipped; every other number is treated as that we do check 39 | # technically the pass number could be specified as bigger than 40 | # for other filesystems, but we don't support this configuration 41 | if [ "$ROOTFSPASS" = "0" ]; then 42 | echo "Skipping root filesystem check (fs_passno == 0)." 43 | exit 0 44 | fi 45 | 46 | ROOTDEV=$(mntent /proc/self/mounts fsname) 47 | # e.g. zfs will not report a valid block device 48 | [ -n "$ROOTDEV" -a -b "$ROOTDEV" ] || exit 0 49 | 50 | ROOTFSTYPE=$(mntent /proc/self/mounts type) 51 | # ensure it's a known filesystem 52 | [ -n "$ROOTFSTYPE" ] || exit 0 53 | 54 | # ensure we have a fsck for it 55 | command -v "fsck.$ROOTFSTYPE" > /dev/null 2>&1 || exit 0 56 | 57 | echo "Checking root file system (^C to skip)..." 58 | 59 | fsck -C $FORCEARG $FIXARG -t "$ROOTFSTYPE" "$ROOTDEV" 60 | 61 | # it's a bitwise-or, but we are only checking one filesystem 62 | case $? in 63 | 0) ;; # nothing 64 | 1) # fixed errors 65 | echo "WARNING: The root filesystem was repaired, continuing boot..." 66 | sleep 2 67 | ;; 68 | 2) # system should be rebooted 69 | echo "WARNING: The root filesystem was repaired, rebooting..." 70 | sleep 5 71 | reboot --use-passed-cfd -r 72 | ;; 73 | 4) # uncorrected errors 74 | echo "WARNING: The root filesystem has unrecoverable errors." 75 | echo " A recovery shell will now be started for you." 76 | echo " The system will be rebooted when you are done." 77 | @DINIT_SULOGIN_PATH@ 78 | reboot --use-passed-cfd -r 79 | ;; 80 | *) ;; 81 | esac 82 | -------------------------------------------------------------------------------- /early/helpers/lo.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Loopback device bringup helper 3 | * 4 | * Does the same thing as `ip link set up dev lo`. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | * 8 | * Copyright (c) 2023 q66 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions 12 | * are met: 13 | * 1. Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 2. Redistributions in binary form must reproduce the above copyright 16 | * notice, this list of conditions and the following disclaimer in the 17 | * documentation and/or other materials provided with the distribution. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #ifndef _GNU_SOURCE 33 | #define _GNU_SOURCE 34 | #endif 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | int main(void) { 45 | int fams[] = {PF_INET, PF_PACKET, PF_INET6, PF_UNSPEC}; 46 | int fd = -1, serr = 0; 47 | 48 | for (int *fam = fams; *fam != PF_UNSPEC; ++fam) { 49 | fd = socket(*fam, SOCK_DGRAM, 0); 50 | if (fd >= 0) { 51 | break; 52 | } else if (!serr) { 53 | serr = errno; /* save first error */ 54 | } 55 | } 56 | 57 | if (fd < 0) { 58 | errno = serr; 59 | err(1, "socket"); 60 | } 61 | 62 | struct ifreq ifr; 63 | memcpy(ifr.ifr_name, "lo", 3); 64 | 65 | if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) { 66 | err(1, "SIOCGIFFLAGS"); 67 | } 68 | 69 | if (ifr.ifr_flags & IFF_UP) { 70 | return 0; 71 | } 72 | 73 | ifr.ifr_flags |= IFF_UP; 74 | if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) { 75 | err(1, "SIOCSIFFLAGS"); 76 | } 77 | 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /early/scripts/kdump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # inspired by void runit-kdump 3 | 4 | DINIT_SERVICE=kdump 5 | DINIT_NO_CONTAINER=1 6 | 7 | set -e 8 | 9 | . @SCRIPT_PATH@/common.sh 10 | 11 | # this is optional functionality 12 | command -v makedumpfile > /dev/null 2>&1 || exit 0 13 | command -v vmcore-dmesg > /dev/null 2>&1 || exit 0 14 | command -v kexec > /dev/null 2>&1 || exit 0 15 | 16 | if [ -e /proc/vmcore ] && ! grep -q nokdump /proc/cmdline; then 17 | DUMP_DIR="/var/crash/kdump-$(date +%Y%m%d-%H%M%S)" 18 | # save vmcore 19 | echo "Saving vmcore to '$DUMP_DIR'..." 20 | mkdir -p "$DUMP_DIR" 21 | makedumpfile -l --message-level 1 -d 31 /proc/vmcore "${DUMP_DIR}/vmcore.tmp" \ 22 | && mv "${DUMP_DIR}/vmcore.tmp" "${DUMP_DIR}/vmcore" 23 | # save dmesg 24 | echo "Saving dmesg to '$DUMP_DIR'..." 25 | vmcore-dmesg /proc/vmcore > "${DIR}/dmesg.txt.tmp" \ 26 | && mv "${DUMP_DIR}/dmesg.txt.tmp" "${DUMP_DIR}/dmesg.txt" 27 | sync 28 | # force reboot after saving 29 | echo "Crash dump done, rebooting..." 30 | sleep 5 31 | reboot --use-passed-cfd -r 32 | exit 0 33 | fi 34 | 35 | # crashkernel=NNN not specified (default), silently succeed 36 | if [ "$(cat /sys/kernel/kexec_crash_size)" = "0" ]; then 37 | exit 0 38 | fi 39 | 40 | KERNVER=$(uname -r) 41 | 42 | # try determining the kernel image path in a semi-generic way... 43 | if command -v linux-version > /dev/null 2>&1; then 44 | # we have linux-version? great, then it's nice and easy 45 | KERNIMG=$(linux-version list --paths | grep "^$KERNVER" | cut -d ' ' -f2) 46 | else 47 | # scuffed but probably generic enough detection... 48 | for kern in /boot/vmlinu*${KERNVER} /boot/*Image*${KERNVER}; do 49 | [ -e "$kern" ] || continue 50 | KERNIMG="$kern" 51 | break 52 | done 53 | fi 54 | 55 | if [ -z "$KERNIMG" ]; then 56 | echo "WARNING: could not determine kernel image path for '${KERNVER}', skipping loading crash kernel..." 57 | exit 0 58 | fi 59 | 60 | # now do that for initramfs, we have no tooling we could use for that 61 | # we may have a dedicated kdump initramfs so try matching these first 62 | for rd in /boot/initr*${KERNVER}*kdump* /boot/initr*${KERNVER}*; do 63 | [ -e "$rd" ] || continue 64 | INITRAMFS="$rd" 65 | break 66 | done 67 | 68 | if [ -z "$INITRAMFS" ]; then 69 | echo "WARNING: could not find initramfs for '${KERNVER}', skipping initramfs loading..." 70 | fi 71 | 72 | # may need adjusting 73 | KAPPEND="irqpoll nr_cpus=1 maxcpus=1 reset_devices udev.children-max=2 panic=10 cgroup_disable=memory mce=off numa=off" 74 | 75 | echo "Loading crash kernel '${KERNIMG}'..." 76 | exec kexec --load-panic "$KERNIMG" ${INITRAMFS:+--initrd="${INITRAMFS}"} \ 77 | --reuse-cmdline --append="${KAPPEND}" 78 | -------------------------------------------------------------------------------- /early/helpers/devmon.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Device monitor daemon 3 | * 4 | * The device monitor daemon opens a control socket and lets clients 5 | * watch for device availability. It keeps the connection for as long 6 | * as the device remains available. 7 | * 8 | * The protocol is a simple stream protocol; a client makes a connection 9 | * and sends a handshake byte (0xDD) followed by a 6 byte type string and 10 | * a null terminator, two bytes of value length, and N bytes of value (no null) 11 | * 12 | * At this point, the server will respond at least once, provided the handshake 13 | * is not malformed (in which case the connection will terminate); the response 14 | * bytes are either 0 (device not available) or 1 (device available); it will 15 | * send more bytes (assuming neither side terminates the connection) as the 16 | * state changes 17 | * 18 | * Once a connection is established the server will never terminate it unless 19 | * an error happens in the server; only the client can do so 20 | * 21 | * SPDX-License-Identifier: BSD-2-Clause 22 | * 23 | * Copyright (c) 2024 q66 24 | * 25 | * Redistribution and use in source and binary forms, with or without 26 | * modification, are permitted provided that the following conditions 27 | * are met: 28 | * 1. Redistributions of source code must retain the above copyright 29 | * notice, this list of conditions and the following disclaimer. 30 | * 2. Redistributions in binary form must reproduce the above copyright 31 | * notice, this list of conditions and the following disclaimer in the 32 | * documentation and/or other materials provided with the distribution. 33 | * 34 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 35 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 36 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 37 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 38 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 39 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 40 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 41 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 42 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 43 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 44 | * SUCH DAMAGE. 45 | */ 46 | 47 | #ifndef _GNU_SOURCE 48 | #define _GNU_SOURCE /* accept4 */ 49 | #endif 50 | 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | 64 | /* selfpipe for signals */ 65 | static int sigpipe[2] = {-1, -1}; 66 | pollfd sigfd{}; 67 | 68 | static void sig_handler(int sign) { 69 | write(sigpipe[1], &sign, sizeof(sign)); 70 | } 71 | 72 | int main(int argc, char **argv) { 73 | if (argc > 2) { 74 | errx(1, "usage: %s [fd]", argv[0]); 75 | } 76 | 77 | int fdnum = -1; 78 | if (argc > 1) { 79 | fdnum = atoi(argv[1]); 80 | errno = 0; 81 | if (!fdnum || (fcntl(fdnum, F_GETFD) < 0)) { 82 | errx(1, "invalid file descriptor for readiness (%d)", fdnum); 83 | } 84 | } 85 | 86 | /* simple signal handler for SIGTERM/SIGINT */ 87 | { 88 | struct sigaction sa{}; 89 | sa.sa_handler = sig_handler; 90 | sa.sa_flags = SA_RESTART; 91 | sigemptyset(&sa.sa_mask); 92 | sigaction(SIGTERM, &sa, nullptr); 93 | sigaction(SIGINT, &sa, nullptr); 94 | } 95 | 96 | std::printf("devmon: start\n"); 97 | 98 | /* signal pipe */ 99 | if (pipe(sigpipe) < 0) { 100 | warn("pipe failed"); 101 | return 1; 102 | } 103 | sigfd.fd = sigpipe[0]; 104 | sigfd.events = POLLIN; 105 | sigfd.revents = 0; 106 | 107 | /* readiness as soon as we're bound to a socket */ 108 | if (fdnum > 0) { 109 | std::printf("devmon: readiness notification\n"); 110 | write(fdnum, "READY=1\n", sizeof("READY=1")); 111 | close(fdnum); 112 | } 113 | 114 | std::printf("devmon: main loop\n"); 115 | 116 | int ret = 0; 117 | for (;;) { 118 | std::printf("devmon: poll\n"); 119 | auto pret = poll(&sigfd, 1, -1); 120 | if (pret < 0) { 121 | if (errno == EINTR) { 122 | continue; 123 | } 124 | warn("poll failed"); 125 | ret = 1; 126 | break; 127 | } else if (pret == 0) { 128 | continue; 129 | } 130 | /* signal fd */ 131 | if (sigfd.revents == POLLIN) { 132 | int sign; 133 | if (read(sigfd.fd, &sign, sizeof(sign)) != sizeof(sign)) { 134 | warn("signal read failed"); 135 | continue; 136 | } 137 | /* sigterm or sigint */ 138 | break; 139 | } 140 | if (ret) { 141 | break; 142 | } 143 | } 144 | close(sigfd.fd); 145 | 146 | std::printf("devmon: exit with %d\n", ret); 147 | return ret; 148 | } 149 | -------------------------------------------------------------------------------- /early/helpers/hwclock.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Clock setup helper program 3 | * 4 | * Meant to be used during system init and shutdown; on start, it will 5 | * set the kernel timezone (without messing with system clock, as during 6 | * bootup it is already set from hardware clock), while on stop, it will 7 | * set hardware clock from system clock. 8 | * 9 | * Created as a thin replacement for the complicated hwclock program from 10 | * util-linux, intended to do only the bootup/shutdown tasks and nothing 11 | * else. 12 | * 13 | * SPDX-License-Identifier: BSD-2-Clause 14 | * 15 | * Copyright (c) 2023 q66 16 | * 17 | * Redistribution and use in source and binary forms, with or without 18 | * modification, are permitted provided that the following conditions 19 | * are met: 20 | * 1. Redistributions of source code must retain the above copyright 21 | * notice, this list of conditions and the following disclaimer. 22 | * 2. Redistributions in binary form must reproduce the above copyright 23 | * notice, this list of conditions and the following disclaimer in the 24 | * documentation and/or other materials provided with the distribution. 25 | * 26 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 27 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 28 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 29 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 30 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 31 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 32 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 33 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 34 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 35 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 36 | * SUCH DAMAGE. 37 | */ 38 | 39 | #ifndef _GNU_SOURCE 40 | #define _GNU_SOURCE 41 | #endif 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | /* RTC_SET_TIME */ 57 | #include 58 | 59 | #include "clock_common.hh" 60 | 61 | typedef enum { 62 | OPT_START, 63 | OPT_STOP, 64 | } opt_t; 65 | 66 | static int usage(char **argv) { 67 | printf("usage: %s start|stop [utc|localtime]\n", argv[0]); 68 | return 1; 69 | } 70 | 71 | static int do_settimeofday(struct timezone const *tz) { 72 | #if !defined(SYS_settimeofday) && defined(SYS_settimeofday_time32) 73 | int ret = syscall(SYS_settimeofday_time32, 0, tz); 74 | #else 75 | int ret = syscall(SYS_settimeofday, 0, tz); 76 | #endif 77 | if (ret) { 78 | warn("settimeofday"); 79 | } 80 | return (ret != 0); 81 | } 82 | 83 | static int do_start(rtc_mod_t mod) { 84 | struct timezone tz = {}; 85 | int ret = 0; 86 | struct tm *lt; 87 | time_t ct; 88 | 89 | /* for UTC, lock warp_clock and PCIL */ 90 | if (mod == RTC_MOD_UTC) { 91 | ret = do_settimeofday(&tz); 92 | if (ret) { 93 | goto done; 94 | } 95 | } 96 | 97 | ct = time(nullptr); 98 | lt = localtime(&ct); 99 | tz.tz_minuteswest = (-lt->tm_gmtoff / 60); 100 | 101 | /* set kernel timezone; lock warp_clock and set PCIL if non-UTC */ 102 | if ((mod != RTC_MOD_UTC) || (tz.tz_minuteswest != 0)) { 103 | ret = do_settimeofday(&tz); 104 | } 105 | 106 | done: 107 | return ret; 108 | } 109 | 110 | static int do_stop(rtc_mod_t mod) { 111 | struct timeval tv; 112 | struct tm tmt = {}; 113 | /* open rtc; it may be busy, so loop */ 114 | int fd = -1; 115 | 116 | char const *rtcs[] = {"/dev/rtc", "/dev/rtc0", nullptr}; 117 | char const **crtc = rtcs; 118 | 119 | while (*crtc++) { 120 | fd = open(*crtc, O_WRONLY); 121 | int attempts = 8; /* do not stall longer than 15 * 8 sec == 2 minutes */ 122 | while ((fd < 0) && (errno == EBUSY) && attempts--) { 123 | usleep(15000); 124 | fd = open(*crtc, O_WRONLY); 125 | } 126 | if (fd < 0) { 127 | /* exists but still busy, fail */ 128 | if (errno == EBUSY) { 129 | return 1; 130 | } 131 | /* another error, see if we can move on */ 132 | continue; 133 | } 134 | /* got fd */ 135 | break; 136 | } 137 | 138 | /* didn't manage to open any fd */ 139 | if (fd < 0) { 140 | return 1; 141 | } 142 | 143 | /* should not fail though */ 144 | if (gettimeofday(&tv, nullptr) < 0) { 145 | close(fd); 146 | return 1; 147 | } 148 | 149 | /* set up tmt */ 150 | if (mod == RTC_MOD_UTC) { 151 | gmtime_r(&tv.tv_sec, &tmt); 152 | } else { 153 | localtime_r(&tv.tv_sec, &tmt); 154 | } 155 | tmt.tm_isdst = 0; 156 | 157 | int ret = syscall(SYS_ioctl, fd, RTC_SET_TIME, &tmt); 158 | close(fd); 159 | 160 | return (ret != 0); 161 | } 162 | 163 | int main(int argc, char **argv) { 164 | /* insufficient arguments */ 165 | if ((argc <= 1) || (argc > 3)) { 166 | return usage(argv); 167 | } 168 | 169 | opt_t opt; 170 | rtc_mod_t mod; 171 | 172 | if (!strcmp(argv[1], "start")) { 173 | opt = OPT_START; 174 | } else if (!strcmp(argv[1], "stop")) { 175 | opt = OPT_STOP; 176 | } else { 177 | return usage(argv); 178 | } 179 | 180 | if (argc > 2) { 181 | if (!strcmp(argv[2], "utc")) { 182 | mod = RTC_MOD_UTC; 183 | } else if (!strcmp(argv[2], "localtime")) { 184 | mod = RTC_MOD_LOCALTIME; 185 | } else { 186 | return usage(argv); 187 | } 188 | } else { 189 | mod = rtc_mod_guess(); 190 | } 191 | 192 | if (opt == OPT_START) { 193 | return do_start(mod); 194 | } 195 | 196 | return do_stop(mod); 197 | } 198 | -------------------------------------------------------------------------------- /early/helpers/swap.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Swap helper 3 | * 4 | * Activates or deactivates all swap devices in fstab and /proc/swaps. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | * 8 | * Copyright (c) 2023 q66 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions 12 | * are met: 13 | * 1. Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 2. Redistributions in binary form must reproduce the above copyright 16 | * notice, this list of conditions and the following disclaimer in the 17 | * documentation and/or other materials provided with the distribution. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #ifndef _GNU_SOURCE 33 | #define _GNU_SOURCE 34 | #endif 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #ifndef SWAP_FLAG_DISCARD_ONCE 47 | #define SWAP_FLAG_DISCARD_ONCE 0x20000 48 | #endif 49 | #ifndef SWAP_FLAG_DISCARD_PAGES 50 | #define SWAP_FLAG_DISCARD_PAGES 0x40000 51 | #endif 52 | 53 | static int usage(char **argv) { 54 | fprintf(stderr, "usage: %s start|stop\n", argv[0]); 55 | return 1; 56 | } 57 | 58 | static int do_swapoff(char const *path) { 59 | /* no need to swapoff zram devices as it only takes time and there is never 60 | * any backing storage where destroying that would depend on swap being off 61 | */ 62 | if (!strncmp(path, "/dev/zram", sizeof("/dev/zram") - 1)) { 63 | return 0; 64 | } 65 | return swapoff(path); 66 | } 67 | 68 | /* we must be able to resolve e.g. LABEL=swapname */ 69 | static char const *resolve_dev(char const *raw, char *buf, size_t bufsz) { 70 | #define CHECK_PFX(name, lname) \ 71 | if (!strncmp(raw, name "=", sizeof(name))) { \ 72 | snprintf(buf, bufsz, "/dev/disk/by-" lname "/%s", raw + sizeof(name)); \ 73 | return buf; \ 74 | } 75 | 76 | CHECK_PFX("LABEL", "label") 77 | CHECK_PFX("UUID", "uuid") 78 | CHECK_PFX("PARTLABEL", "partlabel") 79 | CHECK_PFX("PARTUUID", "partuuid") 80 | CHECK_PFX("ID", "id") 81 | 82 | /* otherwise stat the input */ 83 | return raw; 84 | } 85 | 86 | static int do_start(void) { 87 | struct mntent *m; 88 | int ret = 0; 89 | char devbuf[4096]; 90 | char const *devname; 91 | FILE *f = setmntent("/etc/fstab", "r"); 92 | if (!f) { 93 | if (errno == ENOENT) { 94 | return 0; 95 | } 96 | err(1, "fopen"); 97 | } 98 | while ((m = getmntent(f))) { 99 | char *opt; 100 | struct stat st; 101 | int flags = 0; 102 | if (strcmp(m->mnt_type, "swap")) { 103 | continue; 104 | } 105 | if (hasmntopt(m, "noauto")) { 106 | continue; 107 | } 108 | opt = hasmntopt(m, "discard"); 109 | if (opt) { 110 | opt += 7; 111 | flags |= SWAP_FLAG_DISCARD; 112 | if (*opt++ == '=') { 113 | if (!strncmp(opt, "once", 4) && (!opt[4] || (opt[4] == ','))) { 114 | flags |= SWAP_FLAG_DISCARD_ONCE; 115 | } else if ( 116 | !strncmp(opt, "pages", 5) && (!opt[5] || (opt[5] == ',')) 117 | ) { 118 | flags |= SWAP_FLAG_DISCARD_PAGES; 119 | } 120 | } 121 | } 122 | opt = hasmntopt(m, "pri"); 123 | if (opt) { 124 | opt += 3; 125 | if (*opt++ == '=') { 126 | char *err = nullptr; 127 | unsigned long pval = strtoul(opt, &err, 10); 128 | if (pval > SWAP_FLAG_PRIO_MASK) { 129 | pval = SWAP_FLAG_PRIO_MASK; 130 | } 131 | if (err && (!*err || (*err == ','))) { 132 | flags |= SWAP_FLAG_PREFER | pval; 133 | } 134 | } 135 | } 136 | devname = resolve_dev(m->mnt_fsname, devbuf, sizeof(devbuf)); 137 | if (stat(devname, &st)) { 138 | warn("stat failed for '%s'", m->mnt_fsname); 139 | ret = 1; 140 | continue; 141 | } 142 | if (S_ISREG(st.st_mode) && ((st.st_blocks * (off_t)512) < st.st_size)) { 143 | warnx("swap '%s' has holes", m->mnt_fsname); 144 | ret = 1; 145 | continue; 146 | } 147 | if (swapon(devname, flags)) { 148 | warn("swapon failed for '%s'", m->mnt_fsname); 149 | ret = 1; 150 | continue; 151 | } 152 | } 153 | endmntent(f); 154 | return ret; 155 | } 156 | 157 | static int do_stop(void) { 158 | int ret = 0; 159 | char devbuf[4096]; 160 | char const *devname; 161 | /* first do /proc/swaps */ 162 | FILE *f = fopen("/proc/swaps", "r"); 163 | if (f) { 164 | char *line = nullptr; 165 | size_t len = 0; 166 | ssize_t nread; 167 | while ((nread = getline(&line, &len, f)) != -1) { 168 | if (*line != '/') { 169 | continue; 170 | } 171 | char *p = strchr(line, ' '); 172 | if (p) { 173 | *p = '\0'; 174 | } 175 | if (do_swapoff(line)) { 176 | warn("swapoff failed for swap '%s'", line); 177 | ret = 1; 178 | } 179 | } 180 | free(line); 181 | fclose(f); 182 | } 183 | /* then do fstab */ 184 | f = setmntent("/etc/fstab", "r"); 185 | if (f) { 186 | struct mntent *m; 187 | while ((m = getmntent(f))) { 188 | if (strcmp(m->mnt_type, "swap")) { 189 | continue; 190 | } 191 | devname = resolve_dev(m->mnt_fsname, devbuf, sizeof(devbuf)); 192 | if (do_swapoff(devname) && (errno != EINVAL)) { 193 | warn("swapoff failed for '%s'", m->mnt_fsname); 194 | ret = 1; 195 | } 196 | } 197 | endmntent(f); 198 | } 199 | return ret; 200 | } 201 | 202 | int main(int argc, char **argv) { 203 | /* insufficient arguments */ 204 | if ((argc != 2) || getuid()) { 205 | return usage(argv); 206 | } 207 | 208 | if (!strcmp(argv[1], "start")) { 209 | return do_start(); 210 | } else if (!strcmp(argv[1], "stop")) { 211 | return do_stop(); 212 | } 213 | 214 | return usage(argv); 215 | } 216 | -------------------------------------------------------------------------------- /early/helpers/binfmt.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Binfmt setup helper program 3 | * 4 | * This is a utility that registers binfmt handlers using configuration files 5 | * compatible with the systemd-binfmt layout. It supports roughly the same 6 | * options as systemd-binfmt, but exists primarily for the service. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | * 10 | * Copyright (c) 2023 q66 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 1. Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 | * SUCH DAMAGE. 32 | */ 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #ifndef BINFMTFS_MAGIC 52 | /* from linux/magic.h */ 53 | #define BINFMTFS_MAGIC 0x42494e4d 54 | #endif 55 | 56 | /* /proc/sys/fs/binfmt_misc */ 57 | static int binfmt_fd = -1; 58 | 59 | /* search paths for conf files */ 60 | static char const *paths[] = { 61 | "/etc/binfmt.d", 62 | "/run/binfmt.d", 63 | "/usr/local/lib/binfmt.d", 64 | "/usr/lib/binfmt.d", 65 | nullptr 66 | }; 67 | 68 | static void usage(FILE *f) { 69 | extern char const *__progname; 70 | std::fprintf(f, "Usage: %s [OPTION]...\n" 71 | "\n" 72 | "Register or unregister formats with binfmt_misc.\n" 73 | "\n" 74 | " -u Unregister instead of registering.\n" 75 | " -p Print the contents of config files to standard output.\n" 76 | " -h Print this message and exit.\n", 77 | __progname 78 | ); 79 | } 80 | 81 | static void binfmt_check_mounted(bool print_only) { 82 | if (print_only) { 83 | return; 84 | } 85 | int fd = open("/proc/sys/fs/binfmt_misc", O_DIRECTORY | O_PATH); 86 | if (fd < 0) { 87 | err(1, "failed to open binfmt_misc"); 88 | } 89 | /* check the magic */ 90 | struct statfs buf; 91 | int ret = fstatfs(fd, &buf); 92 | if ((ret < 0) || (buf.f_type != BINFMTFS_MAGIC)) { 93 | err(1, "binfmt_misc has a wrong type"); 94 | } 95 | /* check if it's writable */ 96 | char proc[256]; 97 | std::snprintf(proc, sizeof(proc), "/proc/self/fd/%d", fd); 98 | if (access(proc, W_OK) < 0) { 99 | err(1, "binfmt_misc is not writable"); 100 | } 101 | /* now we good; O_PATH descriptor can be used with *at */ 102 | binfmt_fd = fd; 103 | } 104 | 105 | static bool poke_bfmt(char const *path, char const *value, std::size_t vlen) { 106 | int fd = openat(binfmt_fd, path, O_WRONLY | O_TRUNC); 107 | if (fd < 0) { 108 | return false; 109 | } 110 | bool ret = (write(fd, value, vlen) == ssize_t(vlen)); 111 | close(fd); 112 | return ret; 113 | } 114 | 115 | static bool load_rule(char *rule, std::size_t rlen) { 116 | /* get the name */ 117 | char *rulename = rule + 1; 118 | char delim[2] = {rule[0], '\0'}; 119 | /* length of name in rule */ 120 | auto rulelen = std::strcspn(rulename, delim); 121 | /* validate */ 122 | if (!rulelen) { 123 | warnx("invalid binfmt '%s'", rule); 124 | return false; 125 | } 126 | if ( 127 | !std::strncmp(rulename, "register", rulelen) || 128 | !std::strncmp(rulename, "status", rulelen) || 129 | !std::strncmp(rulename, "..", rulelen) || 130 | !std::strncmp(rulename, ".", rulelen) || 131 | std::memchr(rulename, '/', rulelen) 132 | ) { 133 | warnx("invalid rule name in '%s'", rule); 134 | return false; 135 | } 136 | /* deregister old rule */ 137 | rulename[rulelen] = '\0'; 138 | if (!poke_bfmt(rulename, "-1", 2) && (errno != ENOENT)) { 139 | warn("failed to unregister rule '%s'", rulename); 140 | return false; 141 | } 142 | rulename[rulelen] = rule[0]; 143 | /* register new rule */ 144 | if (!poke_bfmt("register", rule, rlen)) { 145 | warn("failed to register rule '%s'", rule); 146 | return false; 147 | } 148 | /* success! */ 149 | return true; 150 | } 151 | 152 | static bool load_conf(char const *s, char *&line, std::size_t &len) { 153 | FILE *f = std::fopen(s, "rb"); 154 | if (!f) { 155 | warnx("could not load '%s'", s); 156 | return false; 157 | } 158 | bool fret = true; 159 | for (ssize_t nread; (nread = getline(&line, &len, f)) != -1;) { 160 | /* strip leading whitespace and ignore comments, empty lines etc */ 161 | char *cline = line; 162 | auto rlen = std::size_t(nread); 163 | while (std::isspace(*cline)) { 164 | ++cline; 165 | --rlen; 166 | } 167 | if ((*cline == '#') || (*cline == ';') || !*cline) { 168 | continue; 169 | } 170 | /* strip trailing whitespace too once we are sure it's not empty */ 171 | auto rl = std::strlen(line); 172 | while (std::isspace(line[rl - 1])) { 173 | line[--rl] = '\0'; 174 | --rlen; 175 | } 176 | /* this should be a registerable binfmt */ 177 | if (!load_rule(cline, rlen)) { 178 | fret = false; 179 | } 180 | } 181 | std::fclose(f); 182 | return fret; 183 | } 184 | 185 | static bool print_conf(char const *s, char *&line, std::size_t &len) { 186 | FILE *f = std::fopen(s, "rb"); 187 | if (!f) { 188 | std::printf("# '%s' could not be loaded\n", s); 189 | return false; 190 | } 191 | std::printf("# %s\n", s); 192 | ssize_t nread; 193 | while ((nread = getline(&line, &len, f)) != -1) { 194 | std::printf("%s", line); 195 | if (line[nread - 1] != '\n') { 196 | /* just in case file is not terminated with newline */ 197 | std::putchar('\n'); 198 | } 199 | } 200 | std::fclose(f); 201 | return true; 202 | } 203 | 204 | static bool process_conf( 205 | char const *s, char *&line, std::size_t &len, bool only_print 206 | ) { 207 | if (only_print) { 208 | return print_conf(s, line, len); 209 | } 210 | return load_conf(s, line, len); 211 | } 212 | 213 | int main(int argc, char **argv) { 214 | bool arg_p = false; 215 | bool arg_u = false; 216 | 217 | for (int c; (c = getopt(argc, argv, "hpu")) >= 0;) { 218 | switch (c) { 219 | case 'h': 220 | usage(stdout); 221 | return 0; 222 | case 'p': 223 | arg_p = true; 224 | break; 225 | case 'u': 226 | arg_u = true; 227 | break; 228 | default: 229 | warnx("invalid option -- '%c'", c); 230 | usage(stderr); 231 | return 1; 232 | } 233 | } 234 | 235 | if (argc > optind) { 236 | warnx("extra arguments are not allowed"); 237 | usage(stderr); 238 | return 1; 239 | } 240 | 241 | binfmt_check_mounted(arg_p); 242 | 243 | if (arg_u) { 244 | if (!poke_bfmt("status", "-1", 2)) { 245 | err(1, "failed to unregister binfmt entries"); 246 | } 247 | /* success */ 248 | return 0; 249 | } 250 | 251 | std::unordered_map got_map; 252 | 253 | for (char const **p = paths; *p; ++p) { 254 | DIR *dfd = opendir(*p); 255 | if (!dfd) { 256 | continue; 257 | } 258 | struct dirent *dp; 259 | while ((dp = readdir(dfd))) { 260 | /* must be a regular file */ 261 | if (dp->d_type != DT_REG) { 262 | continue; 263 | } 264 | /* check if it matches .conf */ 265 | char const *dn = dp->d_name; 266 | auto sl = std::strlen(dn); 267 | if ((sl <= 5) || strcmp(dn + sl - 5, ".conf")) { 268 | continue; 269 | } 270 | /* check if already in map */ 271 | if (got_map.find(dn) != got_map.end()) { 272 | continue; 273 | } 274 | /* otherwise use its full name */ 275 | std::string fp = *p; 276 | fp.push_back('/'); 277 | fp += dp->d_name; 278 | got_map.emplace(dn, std::move(fp)); 279 | } 280 | closedir(dfd); 281 | } 282 | 283 | std::vector ord_list; 284 | 285 | /* construct a sorted vector of names, backed by map memory */ 286 | for (auto &p: got_map) { 287 | ord_list.push_back(&p.first); 288 | } 289 | std::sort(ord_list.begin(), ord_list.end(), [](auto a, auto b) { 290 | return (*a < *b); 291 | }); 292 | 293 | int ret = 0; 294 | 295 | /* now register or print each conf */ 296 | char *line = nullptr; 297 | std::size_t len = 0; 298 | for (auto &c: ord_list) { 299 | if (!process_conf(got_map[*c].data(), line, len, arg_p)) { 300 | ret = 1; 301 | } 302 | } 303 | std::free(line); 304 | close(binfmt_fd); 305 | return ret; 306 | } 307 | -------------------------------------------------------------------------------- /early/helpers/swclock.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Date/time adjustment helper 3 | * 4 | * A helper program that will adjust system date/time closer to reality 5 | * in absence of a reasonably functional RTC. It works by taking a known 6 | * file in the system, checking its timestamp, and adjusting system date 7 | * if it's less. 8 | * 9 | * On shutdown, it will update the modification time of said file to a 10 | * new value. 11 | * 12 | * Additionally, on systems with an RTC that is not writable, it will 13 | * account for the time offset in order to keep the system date/time 14 | * current. 15 | * 16 | * SPDX-License-Identifier: BSD-2-Clause 17 | * 18 | * Copyright (c) 2023 q66 19 | * 20 | * Redistribution and use in source and binary forms, with or without 21 | * modification, are permitted provided that the following conditions 22 | * are met: 23 | * 1. Redistributions of source code must retain the above copyright 24 | * notice, this list of conditions and the following disclaimer. 25 | * 2. Redistributions in binary form must reproduce the above copyright 26 | * notice, this list of conditions and the following disclaimer in the 27 | * documentation and/or other materials provided with the distribution. 28 | * 29 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 30 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 32 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 33 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 34 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 35 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 36 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 37 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 38 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 39 | * SUCH DAMAGE. 40 | */ 41 | 42 | #ifndef _GNU_SOURCE 43 | #define _GNU_SOURCE 44 | #endif 45 | 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | 61 | #include "clock_common.hh" 62 | 63 | #ifndef LOCALSTATEDIR 64 | #define LOCALSTATEDIR "/var/lib" 65 | #endif 66 | 67 | #define TS_DIR LOCALSTATEDIR "/swclock" 68 | #define TS_FILE "timestamp" 69 | #define TS_OFFSET "offset" 70 | #define RTC_NODE "/sys/class/rtc/rtc0/since_epoch" 71 | 72 | static int usage(char **argv) { 73 | fprintf(stderr, "usage: %s start|stop\n", argv[0]); 74 | return 1; 75 | } 76 | 77 | static int stat_reg(int dfd, char const *fpath, struct stat *st) { 78 | if (fstatat(dfd, fpath, st, AT_SYMLINK_NOFOLLOW) < 0) { 79 | return -1; 80 | } 81 | if (!S_ISREG(st->st_mode)) { 82 | return -1; 83 | } 84 | return 0; 85 | } 86 | 87 | bool convert_localtime(rtc_mod_t mod, unsigned long long &rtc_epoch) { 88 | time_t rtc_lt; 89 | struct tm *rtc_lm; 90 | /* if not localtime, don't do anything */ 91 | if (mod != RTC_MOD_LOCALTIME) { 92 | return true; 93 | } 94 | /* give up if we have 32-bit time_t and the rtc value does not fit */ 95 | if ((sizeof(time_t) == 4) && (rtc_epoch > INT32_MAX)) { 96 | return false; 97 | } 98 | rtc_lt = (time_t)rtc_epoch; 99 | /* gmtime assumes UTC, lie; the result is a localtime struct tm */ 100 | rtc_lm = gmtime(&rtc_lt); 101 | if (!rtc_lm) { 102 | return false; 103 | } 104 | /* convert our localtime to UTC */ 105 | rtc_lt = mktime(rtc_lm); 106 | if (rtc_lt < 0) { 107 | return false; 108 | } 109 | rtc_epoch = (unsigned long long)rtc_lt; 110 | return true; 111 | } 112 | 113 | static int do_start(int dfd, time_t curt, rtc_mod_t mod) { 114 | struct timeval tv = {}; 115 | struct stat st; 116 | FILE *rtcf, *offf; 117 | char rtc_epochs[32]; 118 | char offsets[32]; 119 | char *errp = nullptr; 120 | unsigned long long rtc_epoch, offset; 121 | int offfd; 122 | 123 | /* check if an offset file exists */ 124 | offfd = openat(dfd, TS_OFFSET, O_RDONLY); 125 | if (offfd < 0) { 126 | goto regular_set; 127 | } 128 | 129 | /* check if the rtc node exists */ 130 | rtcf = fopen(RTC_NODE, "r"); 131 | if (!rtcf) { 132 | goto regular_set; 133 | } 134 | 135 | offf = fdopen(offfd, "r"); 136 | if (!offf) { 137 | close(offfd); 138 | err(1, "fdopen"); 139 | } 140 | 141 | /* read the rtc */ 142 | if (!fgets(rtc_epochs, sizeof(rtc_epochs), rtcf)) { 143 | fclose(rtcf); 144 | fclose(offf); 145 | goto regular_set; 146 | } 147 | fclose(rtcf); 148 | 149 | /* read the offset */ 150 | if (!fgets(offsets, sizeof(offsets), offf)) { 151 | fclose(offf); 152 | goto regular_set; 153 | } 154 | fclose(offf); 155 | 156 | /* convert */ 157 | rtc_epoch = strtoull(rtc_epochs, &errp, 10); 158 | if (!rtc_epoch || !errp || (*errp && (*errp != '\n'))) { 159 | /* junk value */ 160 | goto regular_set; 161 | } 162 | 163 | /* rtc may be stored in utc or localtime 164 | * if it's localtime, adjust by timezone 165 | */ 166 | if (!convert_localtime(mod, rtc_epoch)) { 167 | goto regular_set; 168 | } 169 | 170 | errp = nullptr; 171 | offset = strtoull(offsets, &errp, 10); 172 | if (!offset || !errp || (*errp && (*errp != '\n'))) { 173 | /* junk value */ 174 | goto regular_set; 175 | } 176 | 177 | rtc_epoch += offset; 178 | /* give up if we have 32-bit time_t and the rtc value does not fit */ 179 | if ((sizeof(time_t) == 4) && (rtc_epoch > INT32_MAX)) { 180 | goto regular_set; 181 | } 182 | /* see if the new time is newer */ 183 | if ((time_t)rtc_epoch < curt) { 184 | /* nope */ 185 | goto regular_set; 186 | } 187 | 188 | /* set it in place of the timestamp */ 189 | tv.tv_sec = (time_t)rtc_epoch; 190 | goto do_set; 191 | 192 | regular_set: 193 | /* no or bogus timestamp */ 194 | if (stat_reg(dfd, TS_FILE, &st) < 0) { 195 | return 0; 196 | } 197 | 198 | tv.tv_sec = st.st_atime; 199 | /* timestamp is older than we have right now */ 200 | if (tv.tv_sec < curt) { 201 | return 0; 202 | } 203 | 204 | do_set: 205 | /* set it */ 206 | if (settimeofday(&tv, nullptr) < 0) { 207 | err(1, "settimeofday"); 208 | } 209 | 210 | return 0; 211 | } 212 | 213 | static int do_stop(int dfd, time_t curt, rtc_mod_t mod) { 214 | struct timespec times[2] = {}; 215 | char epochs[32]; 216 | char *errp = nullptr; 217 | unsigned long long epoch; 218 | FILE *rtcf; 219 | int ofd, fd; 220 | 221 | /* unlink the old offset file just in case */ 222 | unlinkat(dfd, TS_OFFSET, 0); 223 | 224 | /* check if rtc node exists */ 225 | rtcf = fopen(RTC_NODE, "r"); 226 | if (!rtcf) { 227 | goto regular_save; 228 | } 229 | 230 | /* read it */ 231 | if (!fgets(epochs, sizeof(epochs), rtcf)) { 232 | fclose(rtcf); 233 | goto regular_save; 234 | } 235 | fclose(rtcf); 236 | 237 | /* convert */ 238 | epoch = strtoull(epochs, &errp, 10); 239 | if (!epoch || !errp || (*errp && (*errp != '\n'))) { 240 | /* junk value */ 241 | goto regular_save; 242 | } 243 | 244 | /* if the rtc is in localtime, adjust to current time */ 245 | if (!convert_localtime(mod, epoch)) { 246 | /* could not adjust, don't save offset */ 247 | goto regular_save; 248 | } 249 | 250 | /* diff it against current time */ 251 | if ((unsigned long long)curt <= epoch) { 252 | /* do not save zero or negative offset; it means the rtc is updating */ 253 | goto regular_save; 254 | } 255 | 256 | /* save offset before saving the regular timestamp */ 257 | ofd = openat( 258 | dfd, TS_OFFSET, O_WRONLY | O_CREAT | O_TRUNC | O_SYNC | O_NOFOLLOW, 0600 259 | ); 260 | if (ofd < 0) { 261 | err(1, "offset open failed"); 262 | } 263 | 264 | rtcf = fdopen(ofd, "w"); 265 | if (!rtcf) { 266 | close(ofd); 267 | err(1, "fdopen"); 268 | } 269 | 270 | /* write the offset */ 271 | fprintf(rtcf, "%llu", (unsigned long long)curt - epoch); 272 | fclose(rtcf); 273 | /* but touch the regular timestamp too */ 274 | 275 | regular_save: 276 | /* create the timestamp if needed */ 277 | fd = openat( 278 | dfd, TS_FILE, 279 | O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_NOATIME, 0600 280 | ); 281 | if (fd < 0) { 282 | err(1, "timestamp open failed"); 283 | } 284 | 285 | times[0].tv_sec = times[1].tv_sec = curt; 286 | if (futimens(fd, times) < 0) { 287 | err(1, "futimens"); 288 | } 289 | close(fd); 290 | 291 | return 0; 292 | } 293 | 294 | int main(int argc, char **argv) { 295 | struct timeval ctv; 296 | rtc_mod_t mod; 297 | 298 | /* insufficient arguments */ 299 | if ((argc <= 1) || (argc > 3) || getuid()) { 300 | return usage(argv); 301 | } 302 | 303 | if (argc > 2) { 304 | if (!strcmp(argv[2], "utc")) { 305 | mod = RTC_MOD_UTC; 306 | } else if (!strcmp(argv[2], "localtime")) { 307 | mod = RTC_MOD_LOCALTIME; 308 | } else { 309 | return usage(argv); 310 | } 311 | } else { 312 | mod = rtc_mod_guess(); 313 | } 314 | 315 | if (gettimeofday(&ctv, nullptr) < 0) { 316 | err(1, "gettimeofday"); 317 | } 318 | 319 | umask(0077); 320 | 321 | if ((mkdir(TS_DIR, 0700) < 0) && (errno != EEXIST)) { 322 | err(1, "unable to create swclock stamp directory"); 323 | } 324 | 325 | int dfd = open(TS_DIR, O_DIRECTORY | O_RDONLY); 326 | if ((dfd < 0) || (flock(dfd, LOCK_EX) < 0)) { 327 | err(1, "unable to lock swclock stamp directory"); 328 | } 329 | 330 | if (!strcmp(argv[1], "start")) { 331 | return do_start(dfd, ctv.tv_sec, mod); 332 | } else if (!strcmp(argv[1], "stop")) { 333 | return do_stop(dfd, ctv.tv_sec, mod); 334 | } 335 | 336 | return usage(argv); 337 | } 338 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dinit-chimera 2 | 3 | This is the core services suite for [dinit](https://github.com/davmac314/dinit) 4 | as used by Chimera. 5 | 6 | It provides an expansive collection of service files, scripts and helpers to 7 | aid early boot, more suitable for a practical deployment than the example 8 | collection that comes with upstream. Patches for third party distro adaptations 9 | are welcome, provided they are not disruptive. 10 | 11 | Currently the documentation for the suite is lacking, which is also to be done. 12 | 13 | ## Dependencies 14 | 15 | * [dinit](https://github.com/davmac314/dinit) (0.18.0 or newer) 16 | * Linux kernel 5.10 or newer 17 | * POSIX shell 18 | * POSIX core utilities 19 | * We test [chimerautils](https://github.com/chimera-linux/chimerautils) 20 | * Others are supported (GNU, `busybox`, etc.); issues should be reported 21 | * `mount`, `umount` 22 | * Implementation must support `-a` 23 | * `sulogin` (any implementation, e.g. `shadow`, `util-linux`, `busybox`) 24 | * [sd-tools](https://github.com/chimera-linux/sd-tools) (particularly `sd-tmpfiles`) 25 | * [libkmod](https://github.com/kmod-project/kmod) 26 | 27 | ### Distribution-provided files 28 | 29 | The distribution should provide the following helpers (the paths are the 30 | defaults, they may be altered with meson options): 31 | 32 | * `/usr/libexec/dinit-console` 33 | * Perform console and keyboard setup; optional 34 | * `/usr/libexec/dinit-cryptdisks` 35 | * Perform encrypted drive setup; optional 36 | * `/usr/libexec/dinit-devd` 37 | * Perform device initialization; mandatory 38 | 39 | The `dinit-console` may look like this when using `console-setup`: 40 | 41 | ``` 42 | #!/bin/sh 43 | 44 | if [ "$1" = "keyboard" ]; then 45 | set -- "-k" 46 | else 47 | set -- 48 | fi 49 | 50 | exec setupcon "$@" 51 | ``` 52 | 53 | The `dinit-cryptdisks` may look like this when using Debian `cryptsetup` scripts: 54 | 55 | ``` 56 | #!/bin/sh 57 | 58 | [ -r /usr/lib/cryptsetup/cryptdisks-functions ] || exit 0 59 | [ -r /etc/crypttab ] || exit 0 60 | 61 | . /usr/lib/cryptsetup/cryptdisks-functions 62 | 63 | INITSTATE="$1" 64 | 65 | case "$2" in 66 | start) do_start ;; 67 | stop) do_stop ;; 68 | *) exit 1 ;; 69 | esac 70 | ``` 71 | 72 | It is passed two arguments, the first one is either `early` or `remaining` 73 | while the second one is either `start` or `stop`. 74 | 75 | The `dinit-devd` may look like this when using `udev`: 76 | 77 | ``` 78 | #!/bin/sh 79 | 80 | case "$1" in 81 | start) exec /usr/libexec/udevd --daemon ;; 82 | stop) udevadm control -e || : ;; 83 | settle) exec udevadm settle ;; 84 | trigger) exec udevadm trigger --action=add ;; 85 | esac 86 | 87 | exit 1 88 | ``` 89 | 90 | Note that currently the behaviors are subject to change. Adopters should 91 | watch out for such changes and adjust their scripts accordingly. 92 | 93 | ### Optional dependencies 94 | 95 | Not having these dependencies will allow the boot to proceed, but specific 96 | functionality will not work. Generally the affected oneshots will simply 97 | exit with success if the tools aren't located. 98 | 99 | * `fsck` 100 | * Without it, early file system checks won't be available 101 | * Tested with `util-linux`, others may work 102 | * [mdadm](https://git.kernel.org/pub/scm/utils/mdadm/mdadm.git) 103 | * [dmraid](https://people.redhat.com/~heinzm/sw/dmraid) 104 | * [LVM2](https://sourceware.org/lvm2) 105 | * [Btrfs](https://btrfs.readthedocs.io/en/latest) 106 | * [ZFS](https://openzfs.github.io/openzfs-docs) 107 | * [makedumpfile](https://github.com/makedumpfile/makedumpfile) 108 | * For kernel crashdump support 109 | * [kexec-tools](https://kernel.org/pub/linux/utils/kernel/kexec) 110 | * For kernel crashdump support 111 | 112 | ## Kernel command line 113 | 114 | This suite implements a variety of kernel command line parameters that 115 | you can use for debugging and other purposes. 116 | 117 | ### Dinit arguments 118 | 119 | * `dinit_auto_recovery=1` - passes `--auto-recovery` 120 | * `dinit_quiet=1` - passes `--quiet` 121 | * `dinit_log_file=LOGFILE` - passes `--log-file LOGFILE` 122 | * `dinit_log_level=LOGLEVEL` - passes `--log-level LOGLEVEL` 123 | * `dinit_console_level=LOGLEVEL` - passes `--console-level LOGLEVEL` 124 | 125 | These are notably useful for early boot debugging. There are a lot of 126 | early services, and if a very early service fails, the real error very 127 | quickly scrolls past the standard verbose output as services get stopped. 128 | Previously this required unreliable workarounds like slow-motion screen 129 | recording; now you can edit your kernel command line and add something 130 | like `dinit_quiet=1 dinit_console_level=warn` to supress the "started" 131 | and "stopped" messages. 132 | 133 | These are all unset so they will not make it into the activation environment. 134 | 135 | Additionally, there are more parameters that are purely for the purpose 136 | of boot debugging and are implemented by `dinit-chimera` itself: 137 | 138 | * `dinit_early_debug=1` - enables early debugging, causing each early 139 | service to echo a message before it performs its action; the following 140 | parameters only take effect if this is set 141 | * `dinit_early_debug_slow=N` - sleeps `N` seconds after the echo and before 142 | performing the action, intentionally slowing down the boot process for 143 | better clarity 144 | * `dinit_early_debug_log=LOGFILE` - instead of the console, all output will 145 | be redirected to the `LOGFILE`; note that you have to ensure the location 146 | of the file is writable 147 | 148 | The debug parameters are subject to change if necessary. They become a part 149 | of the global activation environment. 150 | 151 | ### Fsck arguments 152 | 153 | * `fastboot` or `fsck.mode=skip` - skips filesystem checks 154 | * `forcefsck` or `fsck.mode=force` - passes `-f` to `fsck` 155 | * `fsckfix` or `fsck.repair=yes` - passes `-y` to `fsck` (do not ask questions) 156 | * `fsck.repair=no` - passes `-n` to `fsck` 157 | 158 | ### Kdump arguments 159 | 160 | These only apply if the optional kdump service is installed. 161 | 162 | * `nokdump` - do not save kernel dump even if `/proc/vmcore` exists 163 | 164 | ### Tmpfs arguments 165 | 166 | * `dinit.runsize=N` or `initramfs.runsize=N` - the `size=` parameter to 167 | use when mounting `/run` and `/run/user`; they are equivalent and the 168 | former is specific to `dinit`, while the latter exists for compatibility 169 | with `initramfs-tools` (as the initramfs will mount `/run` already and 170 | then `dinit-chimera` will not). Defaults to `10%`. 171 | 172 | ### Mount arguments 173 | 174 | * `dinit_early_root_remount=VAL` the extra `remount` parameters to use for 175 | early root remount; the default is `ro,rshared` - this can be used to prevent 176 | read-only remount of the root filesystem, e.g. for debugging. Note that this 177 | variable makes it into the global activation environment. 178 | * `dinit_skip_volumes` skip ZFS pools, LVM, as well as btrfs scan on early 179 | boot; particularly useful for e.g. live images, where doing this automatically 180 | is counterproductive and may even break things (e.g. for root ZFS pools). 181 | 182 | ## Device dependencies 183 | 184 | The `dinit-chimera` suite allows services to depend on devices. 185 | To facilitate this, it needs a suitable device monitor, such as the 186 | udev-based one available [here](https://github.com/chimera-linux/dinit-chimera-udev). 187 | 188 | Dummy monitor/client are provided by default. You can replace them when 189 | installing a proper one. 190 | 191 | The capabilities depend on the device monitor implementation. 192 | 193 | Example service that will not come up unless `/dev/sda1` is around, and will 194 | shut down if `/dev/sda1` disappears: 195 | 196 | ``` 197 | type = process 198 | command = /usr/bin/foo 199 | depends-on: local.target 200 | depends-on: device@/dev/sda1 201 | ``` 202 | 203 | See the documentation for your device monitor for further capabilities. 204 | 205 | ## Zram support 206 | 207 | This suite supports management of zram devices on Linux. 208 | 209 | The following configuration files are checked: 210 | 211 | ``` 212 | /etc/dinit-zram.d/*.conf 213 | /run/dinit-zram.d/*.conf 214 | /usr/local/lib/dinit-zram.d/*.conf 215 | /usr/lib/dinit-zram.d/*.conf 216 | /etc/dinit-zram.conf 217 | ``` 218 | 219 | The directory snippet paths are checked in that order and the first directory 220 | to contain a config snippet of that name is prioritized (i.e. every file name 221 | is only loaded once). The `/etc/dinit-zram.conf` configuration file is loaded 222 | last and always (if it exists). 223 | 224 | The syntax is like this: 225 | 226 | ``` 227 | ; a comment 228 | # also a comment 229 | [zram0] 230 | size = 4G 231 | algorithm = zstd 232 | format = mkswap -U clear %0 233 | ``` 234 | 235 | Fields that are specified later override those that are specified earlier, 236 | so you can have e.g. a config file defining a zram device and then a later 237 | one defining more details for it. 238 | 239 | The above fields are currently the only supported ones (more will be added 240 | later as well as more syntax). All but `size` are optional. The `format` 241 | field specifies a command to use to format the device once set up and the 242 | default is the one above, to set up swap space. You can set custom commands 243 | for e.g. zram ramdisks with real filesystems on them. 244 | 245 | Once you have a configuration file, you can activate the device by enabling 246 | the `zram-device@zramN` service. 247 | 248 | ## Mount services 249 | 250 | This suite supports mount services, which are service-driven supervised 251 | mounts. You can define a mount service like this: 252 | 253 | ``` 254 | # /etc/dinit.d/usb-stick.mount 255 | type = process 256 | command = $DINIT_MOUNT \ 257 | --from PARTLABEL=usbstick \ 258 | --to /media/usb \ 259 | --type ext4 260 | restart = false 261 | depends-on: device@PARTLABEL=usbstick 262 | depends-on: early-fs-local.target 263 | ``` 264 | 265 | Starting this service will ensure that `/dev/sda1` will remain mounted for 266 | as long as the device exists. Stopping the service will cleanly unmount 267 | it. The `restart = false` ensures manually unmounting the device will not 268 | remount it; `restart = true` will make sure it's always mounted, unless 269 | stopped explicitly. 270 | 271 | ## Service targets 272 | 273 | The collection provides special "target" services, suffixed with `.target`, 274 | which can be used as dependencies for third party service files as well as 275 | for ordering. 276 | 277 | Until better documentation is in place, here is the list, roughly in bootup 278 | order. The actual order may vary somewhat because of parallel startup. In 279 | general your services should specify dependency links and ordering links 280 | for every target that is relevant to your functionality (i.e. you should 281 | not rely on transitive dependencies excessively). This does not apply 282 | to very early oneshots that are guaranteed to have run, i.e. in most cases 283 | services should not have to depend on `early-prepare.target` and so on. 284 | 285 | * `early-prepare.target` - early pseudo-filesystems have been mounted 286 | * `early-modules.target` - kernel modules from `/etc/modules` have been loaded 287 | * `early-devices.target` - device events have been processed 288 | * This means `/dev` is fully populated with quirks applied and so on. 289 | * `early-keyboard.target` - console keymap has been set 290 | * `early-fs-pre.target` - filesystems are ready to be checked and mounted 291 | * This means encrypted disks, RAID, LVM and so on is up. 292 | * `early-root-rw.target` - root filesystem has been re-mounted read/write. 293 | * That is, unless `fstab` explicitly specifies it should be read-only. 294 | * `early-fs-fstab.target` - non-network filesystems in `fstab` have been mounted 295 | * `early-fs-local.target` - non-network filesystems have finished mounting 296 | * This includes the above plus non-`fstab` filesystems such as ZFS. 297 | * `early-console.target` - follow-up to `early-keyboard.target` (console font, etc.) 298 | * `pre-local.target` - most important early oneshots have run. 299 | * Temporary/volatile files/dirs managed with `tmpfiles.d` are not guaranteed yet. 300 | * Most services should prefer `local.target` as their sentinel. 301 | * Typically only for services that should guarantee being up before `rc.local` is run. 302 | * All targets above this one are guaranteed to have been reached. 303 | * `local.target` - `/etc/rc.local` has run and temp/volatile files/dirs are created 304 | * Implies `pre-local.target`. 305 | * Most regular services should depend on at least this one (or `pre-local.target`). 306 | * `pre-network.target` - networking daemons may start. 307 | * This means things such as firewall have been brought up. 308 | * `network.target` - networking daemons have started. 309 | * Networking daemons should use this as `before`. 310 | * Things depending on network being up should use this as a dependency. 311 | * `login.target` - the system is ready to run gettys, launch display manager, etc. 312 | * Typically to be used as a `before` sentinel for things that must be up before login. 313 | * `time-sync.target` - system date/time should be set by now. 314 | * Things such as NTP implementations should wait and use this as `before`. 315 | * Things requiring date/time to be set should use this as a dependency. 316 | * This may take a while, so pre-login services depending on this may stall the boot. 317 | -------------------------------------------------------------------------------- /early/helpers/sysctl.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Sysctl setup helper program 3 | * 4 | * This utility reads sysctl configuration files in the right order, 5 | * ensuring the behavior of procps's `sysctl --system`. 6 | * 7 | * SPDX-License-Identifier: BSD-2-Clause 8 | * 9 | * Copyright (c) 2023 q66 10 | * 11 | * Redistribution and use in source and binary forms, with or without 12 | * modification, are permitted provided that the following conditions 13 | * are met: 14 | * 1. Redistributions of source code must retain the above copyright 15 | * notice, this list of conditions and the following disclaimer. 16 | * 2. Redistributions in binary form must reproduce the above copyright 17 | * notice, this list of conditions and the following disclaimer in the 18 | * documentation and/or other materials provided with the distribution. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 24 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | /* /proc/sys */ 50 | static int sysctl_fd = -1; 51 | static bool dry_run = false; 52 | 53 | /* search paths for conf files */ 54 | static char const *paths[] = { 55 | "/etc/sysctl.d", 56 | "/run/sysctl.d", 57 | "/usr/local/lib/sysctl.d", 58 | "/usr/lib/sysctl.d", 59 | nullptr 60 | }; 61 | static char const *sys_path = "/etc/sysctl.conf"; 62 | 63 | static void usage(FILE *f) { 64 | extern char const *__progname; 65 | std::fprintf(f, "Usage: %s\n" 66 | "\n" 67 | "Load sysctl settings.\n", 68 | __progname 69 | ); 70 | } 71 | 72 | static bool load_sysctl( 73 | char *name, char *value, bool opt, bool globbed, 74 | std::unordered_set &entries 75 | ) { 76 | size_t fsep; 77 | std::string fullpath; 78 | /* we jump here so don't bypass var init */ 79 | if (globbed) { 80 | goto doneg; 81 | } 82 | /* we implement the crappier procps algorithm as opposed to the nicer 83 | * busybox algorithm (which handles paths such as foo/bar.baz/xyz cleanly 84 | * without workarounds) for the sake of compatibility and also because it 85 | * does not require iterative file access checking which means we can make 86 | * up a single path and stick with it, which makes e.g. globbing easier... 87 | * 88 | * first find the first separator; determines if to convert the rest 89 | */ 90 | fsep = strcspn(name, "./"); 91 | /* no separator or starts with slash; leave everything intact */ 92 | if (!name[fsep] || (name[fsep] == '/')) { 93 | goto donep; 94 | } 95 | /* otherwise swap them separators */ 96 | for (char *curp = name;;) { 97 | switch (curp[fsep]) { 98 | case '.': curp[fsep] = '/'; break; 99 | case '/': curp[fsep] = '.'; break; 100 | default: break; 101 | } 102 | curp = &curp[fsep + 1]; 103 | /* end of string or no separator */ 104 | if (!*curp || !curp[fsep = strcspn(curp, "./")]) { 105 | break; 106 | } 107 | } 108 | donep: 109 | /* we have a valid pathname, so apply the sysctl; but do glob expansion 110 | * first in case there is something, do it only if we can match any of 111 | * the glob characters to avoid allocations and so on in most cases 112 | */ 113 | if (!globbed && name[strcspn(name, "*?[")]) { 114 | if (dry_run) { 115 | fprintf(stderr, "potential glob: %s\n", name); 116 | } 117 | fullpath = "/proc/sys/"; 118 | fullpath += name; 119 | glob_t pglob; 120 | int gret = glob(fullpath.data(), 0, nullptr, &pglob); 121 | switch (gret) { 122 | case 0: 123 | if (dry_run) { 124 | fprintf(stderr, "... matches: %zu\n", pglob.gl_pathc); 125 | } 126 | break; 127 | case GLOB_NOMATCH: 128 | if (dry_run) { 129 | fprintf(stderr, "... no matches\n"); 130 | } 131 | return true; 132 | default: 133 | warn("failed to glob '%s'", name); 134 | return false; 135 | } 136 | bool ret = true; 137 | struct stat st; 138 | for (char **paths = pglob.gl_pathv; *paths; ++paths) { 139 | char *subp = *paths + sizeof("/proc/sys"); 140 | if (dry_run) { 141 | fprintf(stderr, "... glob match: %s\n", subp); 142 | } 143 | if (entries.find(subp) != entries.end()) { 144 | /* skip stuff with an explicit pattern */ 145 | continue; 146 | } 147 | if (stat(*paths, &st)) { 148 | warn("failed to stat '%s'", *paths); 149 | ret = false; 150 | } 151 | if (!S_ISREG(st.st_mode)) { 152 | /* skip dirs if we match them */ 153 | continue; 154 | } 155 | if (!load_sysctl(subp, value, opt, true, entries)) { 156 | ret = false; 157 | } 158 | } 159 | return ret; 160 | } 161 | doneg: 162 | /* non-globbed entries that are fully expanded get tracked */ 163 | if (!globbed) { 164 | if (dry_run) { 165 | fprintf(stderr, "track sysctl: %s\n", name); 166 | } 167 | entries.emplace(name); 168 | } 169 | /* no value provided; this was prefixed and can be used to skip globs, 170 | * unprefixed versions would have already failed earlier due to checks 171 | */ 172 | if (!value) { 173 | if (dry_run) { 174 | fprintf(stderr, "no value sysctl: %s\n", name); 175 | } 176 | return true; 177 | } 178 | int fd = openat(sysctl_fd, name, O_WRONLY | O_CREAT | O_TRUNC, 0666); 179 | if (fd < 0) { 180 | if (dry_run) { 181 | fprintf(stderr, "lookup fail for %s (%s)\n", name, strerror(errno)); 182 | } 183 | /* write-only values, we should not fail on those */ 184 | if (errno == EACCES) { 185 | return true; 186 | } 187 | /* optional stuff never fails anyhow */ 188 | if (opt) { 189 | return true; 190 | } 191 | /* unknown entries */ 192 | if (errno == ENOENT) { 193 | for (;;) { 194 | char *sl = std::strchr(name, '/'); 195 | if (!sl) { 196 | break; 197 | } 198 | *sl = '.'; 199 | } 200 | warnx("unknown sysctl '%s'", name); 201 | return false; 202 | } 203 | /* other error */ 204 | warn("failed to set sysctl '%s'", name); 205 | return false; 206 | } 207 | if (dry_run) { 208 | fprintf(stderr, "setting sysctl: %s=%s (opt: %d)\n", name, value, opt); 209 | close(fd); 210 | return true; 211 | } 212 | bool ret = true; 213 | auto vlen = std::strlen(value); 214 | value[vlen] = '\n'; 215 | errno = 0; 216 | if ((write(fd, value, vlen + 1) <= 0) && !opt) { 217 | warn("failed to set sysctl '%s'", name); 218 | ret = false; 219 | } 220 | close(fd); 221 | return ret; 222 | } 223 | 224 | static bool load_conf( 225 | char const *s, char *&line, std::size_t &len, 226 | std::unordered_set &entries 227 | ) { 228 | FILE *f = std::fopen(s, "rb"); 229 | if (!f) { 230 | warnx("could not load '%s'", s); 231 | return false; 232 | } 233 | bool fret = true; 234 | for (ssize_t nread; (nread = getline(&line, &len, f)) != -1;) { 235 | /* strip leading whitespace and ignore comments, empty lines etc */ 236 | char *cline = line; 237 | while (std::isspace(*cline)) { 238 | ++cline; 239 | } 240 | if ((*cline == '#') || (*cline == ';') || !*cline) { 241 | continue; 242 | } 243 | /* sysctls starting with - should not fail ever */ 244 | bool opt = (*cline == '-'); 245 | if (opt) { 246 | /* disregard the dash once we know, it's not a part of the name */ 247 | ++cline; 248 | } 249 | /* strip more leading spaces if needed */ 250 | while (std::isspace(*cline)) { 251 | ++cline; 252 | } 253 | /* strip trailing whitespace too once we are sure it's not empty */ 254 | auto rl = std::strlen(line); 255 | while (std::isspace(line[rl - 1])) { 256 | line[--rl] = '\0'; 257 | } 258 | if (dry_run) { 259 | fprintf(stderr, "=> LINE MATCH: '%s'\n", cline); 260 | } 261 | /* find delimiter */ 262 | auto *delim = std::strchr(cline, '='); 263 | if ((!delim && !opt) || (*cline == '/') || (*cline == '.')) { 264 | warnx("invalid sysctl: '%s'", cline); 265 | fret = false; 266 | continue; 267 | } 268 | char *sname = cline; 269 | char *svalue = nullptr; 270 | if (delim) { 271 | *delim = '\0'; 272 | svalue = delim + 1; 273 | } 274 | auto nl = std::strlen(sname); 275 | /* trailing spaces of name */ 276 | while ((nl > 0) && std::isspace(sname[nl - 1])) { 277 | sname[--nl] = '\0'; 278 | } 279 | if (!nl) { 280 | warnx("unnamed sysctl found"); 281 | fret = false; 282 | continue; 283 | } 284 | /* leading spaces of value */ 285 | while (svalue && std::isspace(*svalue)) { 286 | ++svalue; 287 | } 288 | /* load the sysctl */ 289 | if (!load_sysctl(sname, svalue, opt, false, entries)) { 290 | fret = false; 291 | } 292 | } 293 | std::fclose(f); 294 | return fret; 295 | } 296 | 297 | int main(int argc, char **) { 298 | if (argc != 1) { 299 | usage(stderr); 300 | return 1; 301 | } 302 | 303 | sysctl_fd = open("/proc/sys", O_DIRECTORY | O_PATH); 304 | if (sysctl_fd < 0) { 305 | err(1, "failed to open sysctl path"); 306 | } 307 | 308 | /* prints stuff but does not actually set anything */ 309 | dry_run = !!getenv("DINIT_CHIMERA_SYSCTL_DRY_RUN"); 310 | 311 | std::unordered_map got_map; 312 | 313 | for (char const **p = paths; *p; ++p) { 314 | int dfd = open(*p, O_RDONLY | O_DIRECTORY); 315 | if (dfd < 0) { 316 | continue; 317 | } 318 | int dupfd = dup(dfd); 319 | if (dupfd < 0) { 320 | err(1, "dupfd"); 321 | } 322 | DIR *dirp = fdopendir(dupfd); 323 | if (!dirp) { 324 | err(1, "fdopendir"); 325 | } 326 | struct dirent *dp; 327 | while ((dp = readdir(dirp))) { 328 | /* must be a regular file or a symlink to regular file; we cannot 329 | * use d_type (nonportable anyway) because that will get DT_LNK 330 | * for symlinks (it does not follow) 331 | */ 332 | struct stat st; 333 | if ((fstatat(dfd, dp->d_name, &st, 0) < 0) || !S_ISREG(st.st_mode)) { 334 | continue; 335 | } 336 | /* check if it matches .conf */ 337 | char const *dn = dp->d_name; 338 | auto sl = std::strlen(dn); 339 | if ((sl <= 5) || strcmp(dn + sl - 5, ".conf")) { 340 | continue; 341 | } 342 | /* check if already in map */ 343 | if (got_map.find(dn) != got_map.end()) { 344 | continue; 345 | } 346 | /* otherwise use its full name */ 347 | std::string fp = *p; 348 | fp.push_back('/'); 349 | fp += dp->d_name; 350 | got_map.emplace(dn, std::move(fp)); 351 | } 352 | close(dfd); 353 | closedir(dirp); 354 | } 355 | 356 | std::vector ord_list; 357 | 358 | /* construct a sorted vector of names, backed by map memory */ 359 | for (auto &p: got_map) { 360 | ord_list.push_back(&p.first); 361 | } 362 | 363 | std::sort(ord_list.begin(), ord_list.end(), [](auto a, auto b) { 364 | return (*a < *b); 365 | }); 366 | 367 | int ret = 0; 368 | 369 | /* now register or print each conf */ 370 | char *line = nullptr; 371 | std::size_t len = 0; 372 | /* for tracking of glob exclusions */ 373 | std::unordered_set entries; 374 | 375 | for (auto &c: ord_list) { 376 | if (!load_conf(got_map[*c].data(), line, len, entries)) { 377 | ret = 1; 378 | } 379 | } 380 | /* global sysctl.conf is last if it exists */ 381 | if (!access(sys_path, R_OK)) { 382 | char const *asysp = strchr(sys_path, '/') + 1; 383 | /* only load if no file called sysctl.conf was already handled */ 384 | if (got_map.find(asysp) == got_map.end()) { 385 | if (!load_conf(sys_path, line, len, entries)) { 386 | ret = 1; 387 | } 388 | } 389 | } 390 | std::free(line); 391 | close(sysctl_fd); 392 | return ret; 393 | } 394 | -------------------------------------------------------------------------------- /early/helpers/kmod.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Kernel module helper program 3 | * 4 | * This utility facilitates kernel module handling during early boot, having 5 | * more flexibility than modprobe and similar, and notably being able to deal 6 | * with modules-load.d. 7 | * 8 | * SPDX-License-Identifier: BSD-2-Clause 9 | * 10 | * Copyright (c) 2024 q66 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 1. Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 | * SUCH DAMAGE. 32 | */ 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #include 52 | 53 | static std::unordered_set *kernel_blacklist = nullptr; 54 | 55 | /* search paths for conf files */ 56 | static char const *paths[] = { 57 | "/etc/modules-load.d", 58 | "/run/modules-load.d", 59 | "/usr/local/lib/modules-load.d", 60 | "/usr/lib/modules-load.d", 61 | nullptr 62 | }; 63 | 64 | static void usage(FILE *f) { 65 | extern char const *__progname; 66 | std::fprintf(f, "Usage: %s command [arg]\n" 67 | "\n" 68 | "Kernel module helper tool.\n" 69 | "\n" 70 | "Commands:\n" 71 | " static-modules Load early static kernel modules.\n" 72 | " modules Load modules specified in modules-load.d.\n" 73 | " load MODNAME Load the module MODNAME.\n", 74 | __progname 75 | ); 76 | } 77 | 78 | static bool mod_is_kernel_blacklist(char const *modname) { 79 | return (kernel_blacklist->find(modname) != kernel_blacklist->end()); 80 | } 81 | 82 | static int mod_load(struct kmod_ctx *ctx, char const *modname) { 83 | struct kmod_list *modlist = nullptr; 84 | struct kmod_list *it; 85 | /* first lookup the list */ 86 | int ret = kmod_module_new_from_lookup(ctx, modname, &modlist); 87 | if (ret < 0) { 88 | return ret; 89 | } 90 | /* missing modules are a success */ 91 | if (!modlist) { 92 | return 0; 93 | } 94 | /* otherwise we got a list, go over it */ 95 | kmod_list_foreach(it, modlist) { 96 | struct kmod_module *km = kmod_module_get_module(it); 97 | int state = kmod_module_get_initstate(km); 98 | /* already-loaded or builtin modules are skipped */ 99 | switch (state) { 100 | case KMOD_MODULE_BUILTIN: 101 | case KMOD_MODULE_LIVE: 102 | kmod_module_unref(km); 103 | continue; 104 | default: 105 | break; 106 | } 107 | /* actually perform a load */ 108 | int r = kmod_module_probe_insert_module( 109 | km, KMOD_PROBE_APPLY_BLACKLIST, nullptr, nullptr, nullptr, nullptr 110 | ); 111 | if (!r || (r == KMOD_PROBE_APPLY_BLACKLIST)) { 112 | continue; 113 | } 114 | /* handle kernel module_blacklist as libkmod does not handle it */ 115 | if ((r == -EPERM) && mod_is_kernel_blacklist(modname)) { 116 | continue; 117 | } 118 | /* other "success" conditions */ 119 | if ((r == -ENODEV) || (r == -ENOENT)) { 120 | continue; 121 | } 122 | /* else error but still move on, do try to probe everything first */ 123 | ret = r; 124 | } 125 | /* ok */ 126 | return ret; 127 | } 128 | 129 | static bool load_conf( 130 | struct kmod_ctx *ctx, char const *s, char *&line, std::size_t &len 131 | ) { 132 | FILE *f = std::fopen(s, "rb"); 133 | if (!f) { 134 | warnx("could not load '%s'", s); 135 | return false; 136 | } 137 | bool fret = true; 138 | for (ssize_t nread; (nread = getline(&line, &len, f)) != -1;) { 139 | /* strip leading whitespace and ignore comments, empty lines etc */ 140 | char *cline = line; 141 | while (std::isspace(*cline)) { 142 | ++cline; 143 | } 144 | if ((*cline == '#') || (*cline == ';') || !*cline) { 145 | continue; 146 | } 147 | /* strip trailing whitespace too once we are sure it's not empty */ 148 | auto rl = std::strlen(line); 149 | while (std::isspace(line[rl - 1])) { 150 | line[--rl] = '\0'; 151 | } 152 | /* try loading the module */ 153 | if (mod_load(ctx, line) < 0) { 154 | warn("failed to load module '%s'", line); 155 | fret = false; 156 | } 157 | } 158 | std::fclose(f); 159 | return fret; 160 | } 161 | 162 | static int do_static_modules(struct kmod_ctx *ctx) { 163 | char buf[256], *bufp; 164 | int modb = open("/lib/modules", O_DIRECTORY | O_PATH); 165 | if (modb < 0) { 166 | if (errno == ENOENT) { 167 | return 0; 168 | } 169 | warn("opening /lib/modules failed"); 170 | return 2; 171 | } 172 | struct utsname ub; 173 | if (uname(&ub) < 0) { 174 | warn("uname"); 175 | close(modb); 176 | return 2; 177 | } 178 | int kernb = openat(modb, ub.release, O_DIRECTORY | O_PATH); 179 | if (kernb < 0) { 180 | if (errno == ENOENT) { 181 | return 0; 182 | } 183 | warn("opening kernel directory failed"); 184 | close(modb); 185 | return 2; 186 | } 187 | close(modb); 188 | int devf = openat(kernb, "modules.devname", O_RDONLY); 189 | if (devf < 0) { 190 | if (errno == ENOENT) { 191 | return 0; 192 | } 193 | warn("opening modules.devname failed"); 194 | close(kernb); 195 | return 2; 196 | } 197 | FILE *df = fdopen(devf, "rb"); 198 | if (!df) { 199 | warn("could not reopen modules.devname as file stream"); 200 | close(devf); 201 | return 2; 202 | } 203 | while ((bufp = std::fgets(buf, sizeof(buf), df))) { 204 | auto sl = std::strlen(bufp); 205 | /* extract the module name */ 206 | char *sp = std::strchr(bufp, ' '); 207 | if (sp) { 208 | *sp = '\0'; 209 | } 210 | /* skip comments */ 211 | if (bufp[0] != '#') { 212 | if (mod_load(ctx, bufp) < 0) { 213 | /* we don't want early-modules to fail if possible, 214 | * but an error message is nice so display it anyway 215 | */ 216 | warn("failed to load module '%s'", bufp); 217 | } 218 | } 219 | /* exhaust the rest of the line just in case */ 220 | while (bufp[sl - 1] != '\n') { 221 | bufp = std::fgets(buf, sizeof(buf), df); 222 | if (!bufp) { 223 | break; 224 | } 225 | sl = std::strlen(bufp); 226 | } 227 | /* bail early if we exhausted all without another fgets */ 228 | if (!bufp) { 229 | break; 230 | } 231 | } 232 | return 0; 233 | } 234 | 235 | static int do_load(struct kmod_ctx *ctx, char const *modname) { 236 | if (mod_load(ctx, modname) < 0) { 237 | warn("failed to load module '%s'", modname); 238 | return 2; 239 | } 240 | return 0; 241 | } 242 | 243 | int main(int argc, char **argv) { 244 | bool is_static_mods = false; 245 | bool is_load = false; 246 | 247 | if (argc <= 1) { 248 | usage(stderr); 249 | return 1; 250 | } 251 | 252 | if (!std::strcmp(argv[1], "static-modules")) { 253 | is_static_mods = true; 254 | } else if (!std::strcmp(argv[1], "modules")) { 255 | /* implicit */ 256 | } else if (!std::strcmp(argv[1], "load")) { 257 | is_load = true; 258 | } else { 259 | usage(stderr); 260 | return 1; 261 | } 262 | 263 | /* needs an argument */ 264 | if (is_load && (argc <= 2)) { 265 | usage(stderr); 266 | return 1; 267 | } 268 | 269 | if ((access("/proc/modules", F_OK) < 0) && (errno == ENOENT)) { 270 | /* kernel not modular, all succeeds */ 271 | return 0; 272 | } 273 | 274 | std::unordered_map got_map; 275 | std::unordered_set kern_bl; 276 | std::vector ord_list; 277 | std::vector cmdl_mods; 278 | char *line = nullptr; 279 | std::size_t len = 0; 280 | /* we cannot seek on kernel cmdline, but it has a guaranteed max length */ 281 | char kerncmd[4097] = {}; 282 | int ret = 0; 283 | 284 | kernel_blacklist = &kern_bl; 285 | 286 | struct kmod_ctx *kctx = kmod_new(nullptr, nullptr); 287 | if (!kctx) { 288 | err(1, "kmod_new"); 289 | } 290 | 291 | kmod_load_resources(kctx); 292 | 293 | /* modules_load, modules-load, module_blacklist */ 294 | FILE *cmdl = std::fopen("/proc/cmdline", "rb"); 295 | if (cmdl) { 296 | auto len = std::fread(kerncmd, 1, sizeof(kerncmd) - 1, cmdl); 297 | if ((len > 0) && (kerncmd[len - 1] == '\n')) { 298 | /* may end with a trailing newline */ 299 | kerncmd[len - 1] = '\0'; 300 | } 301 | for (char *p = kerncmd; (p = std::strstr(p, "module"));) { 302 | /* inside of a param, skip */ 303 | if ((p != kerncmd) && p[-1] && (p[-1] != ' ')) { 304 | p += 6; 305 | continue; 306 | } 307 | /* find a = */ 308 | char *e = std::strpbrk(p, "= "); 309 | /* no useful data anymore */ 310 | if (!e) { 311 | break; 312 | } 313 | /* located end earlier */ 314 | if (*e == ' ') { 315 | p = e + 1; 316 | continue; 317 | } 318 | bool load = false; 319 | if ( 320 | !std::strncmp(p, "modules_load", e - p) || 321 | !std::strncmp(p, "modules-load", e - p) 322 | ) { 323 | load = true; 324 | } else if (std::strncmp(p, "module_blacklist", e - p)) { 325 | /* invalid */ 326 | p = e + 1; 327 | continue; 328 | } 329 | /* now parse the list after e */ 330 | p = e + 1; 331 | for (;;) { 332 | auto w = std::strcspn(p, ", "); 333 | if (!w) { 334 | /* maybe had a trailing comma */ 335 | break; 336 | } 337 | char c = p[w]; 338 | p[w] = '\0'; 339 | if (load) { 340 | cmdl_mods.push_back(p); 341 | } else { 342 | kernel_blacklist->emplace(p); 343 | } 344 | if (c == ',') { 345 | /* the list continues, move past the comma */ 346 | p += w + 1; 347 | continue; 348 | } else if (c == ' ') { 349 | /* the list ends, move past the space */ 350 | p += w + 1; 351 | break; 352 | } 353 | /* everything ends */ 354 | p += w; 355 | break; 356 | } 357 | } 358 | } 359 | 360 | if (is_static_mods) { 361 | ret = do_static_modules(kctx); 362 | goto do_ret; 363 | } else if (is_load) { 364 | ret = do_load(kctx, argv[2]); 365 | goto do_ret; 366 | } 367 | 368 | for (char const **p = paths; *p; ++p) { 369 | DIR *dfd = opendir(*p); 370 | if (!dfd) { 371 | continue; 372 | } 373 | struct dirent *dp; 374 | while ((dp = readdir(dfd))) { 375 | /* must be a regular file */ 376 | if (dp->d_type != DT_REG) { 377 | continue; 378 | } 379 | /* check if it matches .conf */ 380 | char const *dn = dp->d_name; 381 | auto sl = std::strlen(dn); 382 | if ((sl <= 5) || strcmp(dn + sl - 5, ".conf")) { 383 | continue; 384 | } 385 | /* check if already in map */ 386 | if (got_map.find(dn) != got_map.end()) { 387 | continue; 388 | } 389 | /* otherwise use its full name */ 390 | std::string fp = *p; 391 | fp.push_back('/'); 392 | fp += dp->d_name; 393 | got_map.emplace(dn, std::move(fp)); 394 | } 395 | closedir(dfd); 396 | } 397 | 398 | /* construct a sorted vector of names, backed by map memory */ 399 | for (auto &p: got_map) { 400 | ord_list.push_back(&p.first); 401 | } 402 | std::sort(ord_list.begin(), ord_list.end(), [](auto a, auto b) { 403 | return (*a < *b); 404 | }); 405 | 406 | /* load modules from command line */ 407 | for (auto modn: cmdl_mods) { 408 | if (do_load(kctx, modn)) { 409 | ret = 2; 410 | } 411 | } 412 | /* now register or print each conf */ 413 | for (auto &c: ord_list) { 414 | if (!load_conf(kctx, got_map[*c].data(), line, len)) { 415 | ret = 2; 416 | } 417 | } 418 | do_ret: 419 | std::free(line); 420 | if (kctx) { 421 | kmod_unref(kctx); 422 | } 423 | return ret; 424 | } 425 | -------------------------------------------------------------------------------- /early/helpers/seedrng.cc: -------------------------------------------------------------------------------- 1 | /* Based on code from . */ 2 | 3 | #ifndef _GNU_SOURCE 4 | #define _GNU_SOURCE 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #ifndef LOCALSTATEDIR 26 | #define LOCALSTATEDIR "/var/lib" 27 | #endif 28 | 29 | #define SEED_DIR LOCALSTATEDIR "/seedrng" 30 | #define CREDITABLE_SEED "seed.credit" 31 | #define NON_CREDITABLE_SEED "seed.no-credit" 32 | /* q66: if a file called seed.skip-credit exists in seedrng's state directory, 33 | * the seeds will never credit the rng, even if the seed file is creditable 34 | * 35 | * this replaces the upstream SEEDRNG_SKIP_CREDIT env var mechanism 36 | */ 37 | #define SKIP_CREDIT "seed.skip-credit" 38 | 39 | enum blake2s_lengths { 40 | BLAKE2S_BLOCK_LEN = 64, 41 | BLAKE2S_HASH_LEN = 32, 42 | BLAKE2S_KEY_LEN = 32 43 | }; 44 | 45 | enum seedrng_lengths { 46 | MAX_SEED_LEN = 512, 47 | MIN_SEED_LEN = BLAKE2S_HASH_LEN 48 | }; 49 | 50 | struct blake2s_state { 51 | uint32_t h[8]; 52 | uint32_t t[2]; 53 | uint32_t f[2]; 54 | uint8_t buf[BLAKE2S_BLOCK_LEN]; 55 | unsigned int buflen; 56 | unsigned int outlen; 57 | }; 58 | 59 | #define le32_to_cpup(a) le32toh(*(a)) 60 | #define cpu_to_le32(a) htole32(a) 61 | #ifndef ARRAY_SIZE 62 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 63 | #endif 64 | #ifndef DIV_ROUND_UP 65 | #define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) 66 | #endif 67 | 68 | static inline void cpu_to_le32_array(uint32_t *buf, unsigned int words) 69 | { 70 | while (words--) { 71 | *buf = cpu_to_le32(*buf); 72 | ++buf; 73 | } 74 | } 75 | 76 | static inline void le32_to_cpu_array(uint32_t *buf, unsigned int words) 77 | { 78 | while (words--) { 79 | *buf = le32_to_cpup(buf); 80 | ++buf; 81 | } 82 | } 83 | 84 | static inline uint32_t ror32(uint32_t word, unsigned int shift) 85 | { 86 | return (word >> (shift & 31)) | (word << ((-shift) & 31)); 87 | } 88 | 89 | static const uint32_t blake2s_iv[8] = { 90 | 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, 91 | 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL 92 | }; 93 | 94 | static const uint8_t blake2s_sigma[10][16] = { 95 | { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, 96 | { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, 97 | { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 }, 98 | { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, 99 | { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 }, 100 | { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, 101 | { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 }, 102 | { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, 103 | { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 }, 104 | { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, 105 | }; 106 | 107 | static void blake2s_set_lastblock(struct blake2s_state *state) 108 | { 109 | state->f[0] = -1; 110 | } 111 | 112 | static void blake2s_increment_counter(struct blake2s_state *state, const uint32_t inc) 113 | { 114 | state->t[0] += inc; 115 | state->t[1] += (state->t[0] < inc); 116 | } 117 | 118 | static void blake2s_init_param(struct blake2s_state *state, const uint32_t param) 119 | { 120 | int i; 121 | 122 | memset(state, 0, sizeof(*state)); 123 | for (i = 0; i < 8; ++i) 124 | state->h[i] = blake2s_iv[i]; 125 | state->h[0] ^= param; 126 | } 127 | 128 | static void blake2s_init(struct blake2s_state *state, const size_t outlen) 129 | { 130 | blake2s_init_param(state, 0x01010000 | outlen); 131 | state->outlen = outlen; 132 | } 133 | 134 | static void blake2s_compress(struct blake2s_state *state, const uint8_t *block, size_t nblocks, const uint32_t inc) 135 | { 136 | uint32_t m[16]; 137 | uint32_t v[16]; 138 | int i; 139 | 140 | while (nblocks > 0) { 141 | blake2s_increment_counter(state, inc); 142 | memcpy(m, block, BLAKE2S_BLOCK_LEN); 143 | le32_to_cpu_array(m, ARRAY_SIZE(m)); 144 | memcpy(v, state->h, 32); 145 | v[ 8] = blake2s_iv[0]; 146 | v[ 9] = blake2s_iv[1]; 147 | v[10] = blake2s_iv[2]; 148 | v[11] = blake2s_iv[3]; 149 | v[12] = blake2s_iv[4] ^ state->t[0]; 150 | v[13] = blake2s_iv[5] ^ state->t[1]; 151 | v[14] = blake2s_iv[6] ^ state->f[0]; 152 | v[15] = blake2s_iv[7] ^ state->f[1]; 153 | 154 | #define G(r, i, a, b, c, d) do { \ 155 | a += b + m[blake2s_sigma[r][2 * i + 0]]; \ 156 | d = ror32(d ^ a, 16); \ 157 | c += d; \ 158 | b = ror32(b ^ c, 12); \ 159 | a += b + m[blake2s_sigma[r][2 * i + 1]]; \ 160 | d = ror32(d ^ a, 8); \ 161 | c += d; \ 162 | b = ror32(b ^ c, 7); \ 163 | } while (0) 164 | 165 | #define ROUND(r) do { \ 166 | G(r, 0, v[0], v[ 4], v[ 8], v[12]); \ 167 | G(r, 1, v[1], v[ 5], v[ 9], v[13]); \ 168 | G(r, 2, v[2], v[ 6], v[10], v[14]); \ 169 | G(r, 3, v[3], v[ 7], v[11], v[15]); \ 170 | G(r, 4, v[0], v[ 5], v[10], v[15]); \ 171 | G(r, 5, v[1], v[ 6], v[11], v[12]); \ 172 | G(r, 6, v[2], v[ 7], v[ 8], v[13]); \ 173 | G(r, 7, v[3], v[ 4], v[ 9], v[14]); \ 174 | } while (0) 175 | ROUND(0); 176 | ROUND(1); 177 | ROUND(2); 178 | ROUND(3); 179 | ROUND(4); 180 | ROUND(5); 181 | ROUND(6); 182 | ROUND(7); 183 | ROUND(8); 184 | ROUND(9); 185 | 186 | #undef G 187 | #undef ROUND 188 | 189 | for (i = 0; i < 8; ++i) 190 | state->h[i] ^= v[i] ^ v[i + 8]; 191 | 192 | block += BLAKE2S_BLOCK_LEN; 193 | --nblocks; 194 | } 195 | } 196 | 197 | static void blake2s_update(struct blake2s_state *state, const void *inp, size_t inlen) 198 | { 199 | const size_t fill = BLAKE2S_BLOCK_LEN - state->buflen; 200 | auto *in = static_cast(inp); 201 | 202 | if (!inlen) 203 | return; 204 | if (inlen > fill) { 205 | memcpy(state->buf + state->buflen, in, fill); 206 | blake2s_compress(state, state->buf, 1, BLAKE2S_BLOCK_LEN); 207 | state->buflen = 0; 208 | in += fill; 209 | inlen -= fill; 210 | } 211 | if (inlen > BLAKE2S_BLOCK_LEN) { 212 | const size_t nblocks = DIV_ROUND_UP(inlen, BLAKE2S_BLOCK_LEN); 213 | blake2s_compress(state, in, nblocks - 1, BLAKE2S_BLOCK_LEN); 214 | in += BLAKE2S_BLOCK_LEN * (nblocks - 1); 215 | inlen -= BLAKE2S_BLOCK_LEN * (nblocks - 1); 216 | } 217 | memcpy(state->buf + state->buflen, in, inlen); 218 | state->buflen += inlen; 219 | } 220 | 221 | static void blake2s_final(struct blake2s_state *state, uint8_t *out) 222 | { 223 | blake2s_set_lastblock(state); 224 | memset(state->buf + state->buflen, 0, BLAKE2S_BLOCK_LEN - state->buflen); 225 | blake2s_compress(state, state->buf, 1, state->buflen); 226 | cpu_to_le32_array(state->h, ARRAY_SIZE(state->h)); 227 | memcpy(out, state->h, state->outlen); 228 | } 229 | 230 | static ssize_t getrandom_full(void *buf, size_t count, unsigned int flags) 231 | { 232 | ssize_t ret, total = 0; 233 | uint8_t *p = static_cast(buf); 234 | 235 | do { 236 | ret = getrandom(p, count, flags); 237 | if (ret < 0 && errno == EINTR) 238 | continue; 239 | else if (ret < 0) 240 | return ret; 241 | total += ret; 242 | p += ret; 243 | count -= ret; 244 | } while (count); 245 | return total; 246 | } 247 | 248 | static ssize_t read_full(int fd, void *buf, size_t count) 249 | { 250 | ssize_t ret, total = 0; 251 | uint8_t *p = static_cast(buf); 252 | 253 | do { 254 | ret = read(fd, p, count); 255 | if (ret < 0 && errno == EINTR) 256 | continue; 257 | else if (ret < 0) 258 | return ret; 259 | else if (ret == 0) 260 | break; 261 | total += ret; 262 | p += ret; 263 | count -= ret; 264 | } while (count); 265 | return total; 266 | } 267 | 268 | static ssize_t write_full(int fd, const void *buf, size_t count) 269 | { 270 | ssize_t ret, total = 0; 271 | auto *p = static_cast(buf); 272 | 273 | do { 274 | ret = write(fd, p, count); 275 | if (ret < 0 && errno == EINTR) 276 | continue; 277 | else if (ret < 0) 278 | return ret; 279 | total += ret; 280 | p += ret; 281 | count -= ret; 282 | } while (count); 283 | return total; 284 | } 285 | 286 | static size_t determine_optimal_seed_len(void) 287 | { 288 | size_t ret = 0; 289 | char poolsize_str[11] = { 0 }; 290 | int fd = open("/proc/sys/kernel/random/poolsize", O_RDONLY); 291 | 292 | if (fd < 0 || read_full(fd, poolsize_str, sizeof(poolsize_str) - 1) < 0) { 293 | perror("Unable to determine pool size, falling back to 256 bits"); 294 | ret = MIN_SEED_LEN; 295 | } else 296 | ret = DIV_ROUND_UP(strtoul(poolsize_str, NULL, 10), 8); 297 | if (fd >= 0) 298 | close(fd); 299 | if (ret < MIN_SEED_LEN) 300 | ret = MIN_SEED_LEN; 301 | else if (ret > MAX_SEED_LEN) 302 | ret = MAX_SEED_LEN; 303 | return ret; 304 | } 305 | 306 | static int read_new_seed(uint8_t *seed, size_t len, bool *is_creditable) 307 | { 308 | ssize_t ret; 309 | int urandom_fd; 310 | 311 | *is_creditable = false; 312 | ret = getrandom_full(seed, len, GRND_NONBLOCK); 313 | if (ret == (ssize_t)len) { 314 | *is_creditable = true; 315 | return 0; 316 | } else if (ret < 0 && errno == ENOSYS) { 317 | struct pollfd random_fd = {}; 318 | random_fd.fd = open("/dev/random", O_RDONLY); 319 | random_fd.events = POLLIN; 320 | if (random_fd.fd < 0) 321 | return -errno; 322 | *is_creditable = poll(&random_fd, 1, 0) == 1; 323 | close(random_fd.fd); 324 | } else if (getrandom_full(seed, len, GRND_INSECURE) == (ssize_t)len) 325 | return 0; 326 | urandom_fd = open("/dev/urandom", O_RDONLY); 327 | if (urandom_fd < 0) 328 | return -1; 329 | ret = read_full(urandom_fd, seed, len); 330 | if (ret == (ssize_t)len) 331 | ret = 0; 332 | else 333 | ret = -errno ? -errno : -EIO; 334 | close(urandom_fd); 335 | errno = -ret; 336 | return ret ? -1 : 0; 337 | } 338 | 339 | static int seed_rng(uint8_t *seed, size_t len, bool credit) 340 | { 341 | struct { 342 | int entropy_count; 343 | int buf_size; 344 | uint8_t buffer[MAX_SEED_LEN]; 345 | } req = {}; 346 | req.entropy_count = credit ? len * 8 : 0; 347 | req.buf_size = len; 348 | int random_fd, ret; 349 | 350 | if (len > sizeof(req.buffer)) { 351 | errno = EFBIG; 352 | return -1; 353 | } 354 | memcpy(req.buffer, seed, len); 355 | 356 | random_fd = open("/dev/urandom", O_RDONLY); 357 | if (random_fd < 0) 358 | return -1; 359 | ret = syscall(SYS_ioctl, random_fd, RNDADDENTROPY, &req); 360 | if (ret) 361 | ret = -errno ? -errno : -EIO; 362 | close(random_fd); 363 | errno = -ret; 364 | return ret ? -1 : 0; 365 | } 366 | 367 | static int seed_from_file_if_exists(const char *filename, int dfd, bool credit, struct blake2s_state *hash) 368 | { 369 | uint8_t seed[MAX_SEED_LEN]; 370 | ssize_t seed_len; 371 | int fd = -1, ret = 0; 372 | 373 | fd = openat(dfd, filename, O_RDONLY); 374 | if (fd < 0 && errno == ENOENT) 375 | return 0; 376 | else if (fd < 0) { 377 | ret = -errno; 378 | perror("Unable to open seed file"); 379 | goto out; 380 | } 381 | seed_len = read_full(fd, seed, sizeof(seed)); 382 | if (seed_len < 0) { 383 | ret = -errno; 384 | perror("Unable to read seed file"); 385 | goto out; 386 | } 387 | if ((unlinkat(dfd, filename, 0) < 0 || fsync(dfd) < 0) && seed_len) { 388 | ret = -errno; 389 | perror("Unable to remove seed after reading, so not seeding"); 390 | goto out; 391 | } 392 | if (!seed_len) 393 | goto out; 394 | 395 | blake2s_update(hash, &seed_len, sizeof(seed_len)); 396 | blake2s_update(hash, seed, seed_len); 397 | 398 | printf("Seeding %zd bits %s crediting\n", seed_len * 8, credit ? "and" : "without"); 399 | if (seed_rng(seed, seed_len, credit) < 0) { 400 | ret = -errno; 401 | perror("Unable to seed"); 402 | } 403 | 404 | out: 405 | if (fd >= 0) 406 | close(fd); 407 | errno = -ret; 408 | return ret ? -1 : 0; 409 | } 410 | 411 | static bool skip_credit(int dfd) 412 | { 413 | struct stat buf; 414 | 415 | if (fstatat(dfd, SKIP_CREDIT, &buf, AT_SYMLINK_NOFOLLOW)) 416 | return false; 417 | 418 | return S_ISREG(buf.st_mode); 419 | } 420 | 421 | int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) 422 | { 423 | static const char seedrng_prefix[] = "SeedRNG v1 Old+New Prefix"; 424 | static const char seedrng_failure[] = "SeedRNG v1 No New Seed Failure"; 425 | int fd = -1, dfd = -1, program_ret = 0; 426 | uint8_t new_seed[MAX_SEED_LEN]; 427 | size_t new_seed_len; 428 | bool new_seed_creditable; 429 | struct timespec realtime = {}, boottime = {}; 430 | struct blake2s_state hash; 431 | 432 | umask(0077); 433 | if (getuid()) { 434 | errno = EACCES; 435 | perror("This program requires root"); 436 | return 1; 437 | } 438 | 439 | blake2s_init(&hash, BLAKE2S_HASH_LEN); 440 | blake2s_update(&hash, seedrng_prefix, strlen(seedrng_prefix)); 441 | clock_gettime(CLOCK_REALTIME, &realtime); 442 | clock_gettime(CLOCK_BOOTTIME, &boottime); 443 | blake2s_update(&hash, &realtime, sizeof(realtime)); 444 | blake2s_update(&hash, &boottime, sizeof(boottime)); 445 | 446 | if (mkdir(SEED_DIR, 0700) < 0 && errno != EEXIST) { 447 | perror("Unable to create seed directory"); 448 | return 1; 449 | } 450 | 451 | dfd = open(SEED_DIR, O_DIRECTORY | O_RDONLY); 452 | if (dfd < 0 || flock(dfd, LOCK_EX) < 0) { 453 | perror("Unable to lock seed directory"); 454 | program_ret = 1; 455 | goto out; 456 | } 457 | 458 | if (seed_from_file_if_exists(NON_CREDITABLE_SEED, dfd, false, &hash) < 0) 459 | program_ret |= 1 << 1; 460 | if (seed_from_file_if_exists(CREDITABLE_SEED, dfd, !skip_credit(dfd), &hash) < 0) 461 | program_ret |= 1 << 2; 462 | 463 | new_seed_len = determine_optimal_seed_len(); 464 | if (read_new_seed(new_seed, new_seed_len, &new_seed_creditable) < 0) { 465 | perror("Unable to read new seed"); 466 | new_seed_len = BLAKE2S_HASH_LEN; 467 | strncpy((char *)new_seed, seedrng_failure, new_seed_len); 468 | program_ret |= 1 << 3; 469 | } 470 | blake2s_update(&hash, &new_seed_len, sizeof(new_seed_len)); 471 | blake2s_update(&hash, new_seed, new_seed_len); 472 | blake2s_final(&hash, new_seed + new_seed_len - BLAKE2S_HASH_LEN); 473 | 474 | printf("Saving %zu bits of %s seed for next boot\n", new_seed_len * 8, new_seed_creditable ? "creditable" : "non-creditable"); 475 | fd = openat(dfd, NON_CREDITABLE_SEED, O_WRONLY | O_CREAT | O_TRUNC, 0400); 476 | if (fd < 0) { 477 | perror("Unable to open seed file for writing"); 478 | program_ret |= 1 << 4; 479 | goto out; 480 | } 481 | if (write_full(fd, new_seed, new_seed_len) != (ssize_t)new_seed_len || fsync(fd) < 0) { 482 | perror("Unable to write seed file"); 483 | program_ret |= 1 << 5; 484 | goto out; 485 | } 486 | if (new_seed_creditable && renameat(dfd, NON_CREDITABLE_SEED, dfd, CREDITABLE_SEED) < 0) { 487 | perror("Unable to make new seed creditable"); 488 | program_ret |= 1 << 6; 489 | } 490 | out: 491 | if (fd >= 0) 492 | close(fd); 493 | if (dfd >= 0) 494 | close(dfd); 495 | return program_ret; 496 | } 497 | -------------------------------------------------------------------------------- /early/helpers/zram.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Zram setup helper program 3 | * 4 | * This utility reads zram configuration files in the right order. 5 | * 6 | * SPDX-License-Identifier: BSD-2-Clause 7 | * 8 | * Copyright (c) 2025 q66 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions 12 | * are met: 13 | * 1. Redistributions of source code must retain the above copyright 14 | * notice, this list of conditions and the following disclaimer. 15 | * 2. Redistributions in binary form must reproduce the above copyright 16 | * notice, this list of conditions and the following disclaimer in the 17 | * documentation and/or other materials provided with the distribution. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 | * SUCH DAMAGE. 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | /* search paths for conf files */ 49 | static char const *paths[] = { 50 | "/etc/dinit-zram.d", 51 | "/run/dinit-zram.d", 52 | "/usr/local/lib/dinit-zram.d", 53 | "/usr/lib/dinit-zram.d", 54 | nullptr 55 | }; 56 | static char const *sys_path = "/etc/dinit-zram.conf"; 57 | 58 | static void usage(FILE *f) { 59 | extern char const *__progname; 60 | std::fprintf(f, "Usage: %s zramN [config]\n" 61 | "\n" 62 | "Set up a zram device.\n", 63 | __progname 64 | ); 65 | } 66 | 67 | static std::string zram_size{}; 68 | static std::string zram_algo{}; 69 | static std::string zram_algo_params{}; 70 | static std::string zram_mem_limit{}; 71 | static std::string zram_backing_dev{}; 72 | static std::string zram_writeback_limit{}; 73 | static std::string zram_fmt = "mkswap -U clear %0"; 74 | 75 | static bool write_param( 76 | int fd, char const *zdev, char const *file, char const *value 77 | ) { 78 | if (file) { 79 | fd = openat(fd, file, O_WRONLY); 80 | if (fd < 0) { 81 | warn("could not open '/sys/block/%s/reset'", zdev); 82 | return false; 83 | } 84 | } 85 | auto wn = write(fd, value, std::strlen(value)); 86 | if (wn < 0) { 87 | warn("could not write '%s' to '%s' on '%s'", value, file, zdev); 88 | if (file) { 89 | close(fd); 90 | } 91 | return false; 92 | } 93 | return true; 94 | } 95 | 96 | static int zram_format(char const *zdevn) { 97 | /* prepare command */ 98 | std::vector args; 99 | std::string zdev = "/dev/"; 100 | zdev += zdevn; 101 | char *data = zram_fmt.data(); 102 | /* strip any spaces at the beginning */ 103 | while (std::isspace(*data)) { 104 | ++data; 105 | } 106 | for (;;) { 107 | auto sp = std::strchr(data, ' '); 108 | if (sp) { 109 | *sp = '\0'; 110 | } 111 | if (!std::strcmp(data, "%0")) { 112 | args.push_back(zdev.data()); 113 | } else { 114 | args.push_back(data); 115 | } 116 | if (!sp) { 117 | break; 118 | } 119 | data = sp + 1; 120 | } 121 | /* terminate */ 122 | args.push_back(nullptr); 123 | /* and run */ 124 | auto pid = fork(); 125 | if (pid < 0) { 126 | warn("fork failed"); 127 | return 1; 128 | } else if (pid == 0) { 129 | /* child */ 130 | execvp(args[0], args.data()); 131 | warn("exec failed"); 132 | return 1; 133 | } 134 | /* parent */ 135 | int st; 136 | while (waitpid(pid, &st, 0) < 0) { 137 | if (errno == EINTR) { 138 | continue; 139 | } 140 | break; 141 | } 142 | if (WIFEXITED(st)) { 143 | st = WEXITSTATUS(st); 144 | if (st) { 145 | warnx("format comamnd '%s' exited with status %d", args[0]); 146 | } 147 | return st; 148 | } else if (WIFSIGNALED(st)) { 149 | warnx("format command '%s' killed by signal %d", WTERMSIG(st)); 150 | } else if (WIFSTOPPED(st)) { 151 | warnx("format command '%s' stopped by signal %d", WSTOPSIG(st)); 152 | } 153 | warnx("format command '%s' ended with unknown status"); 154 | return 1; 155 | } 156 | 157 | static int setup_zram(char const *zdev, int znum) { 158 | if (zram_size.empty()) { 159 | warnx("no size specified for '%s'", zdev); 160 | return 1; 161 | } 162 | std::printf( 163 | "setting up device '%s' with size %s...\n", zdev, zram_size.data() 164 | ); 165 | auto dev_fd = open("/dev", O_DIRECTORY | O_PATH); 166 | if (dev_fd < 0) { 167 | warn("could not open dev directory"); 168 | return 1; 169 | } 170 | auto ctld_fd = open("/sys/class/zram-control", O_DIRECTORY | O_PATH); 171 | if (ctld_fd < 0) { 172 | warn("could not open zram control directory"); 173 | return 1; 174 | } 175 | struct stat st; 176 | if (fstatat(dev_fd, zdev, &st, 0)) { 177 | /* try requesting devices until we get one */ 178 | for (;;) { 179 | auto ha_fd = openat(ctld_fd, "hot_add", O_RDONLY); 180 | if (ha_fd < 0) { 181 | warn("could not open zram hot_add file"); 182 | close(dev_fd); 183 | close(ctld_fd); 184 | return 1; 185 | } 186 | char buf[32], *errp = nullptr; 187 | long devn; 188 | auto devnr = read(ha_fd, buf, sizeof(buf)); 189 | if (devnr <= 0) { 190 | warn("could not request new zram device"); 191 | goto err_case; 192 | } 193 | devn = std::strtol(buf, &errp, 10); 194 | if (!errp || (*errp && !std::isspace(*errp))) { 195 | warnx("invalid output from zram hot_add"); 196 | goto err_case; 197 | } 198 | if (devn < 0) { 199 | errno = devn; 200 | warn("could not request zram device"); 201 | goto err_case; 202 | } 203 | if (devn > znum) { 204 | warnx("could not request zram device"); 205 | goto err_case; 206 | } else if (devn == znum) { 207 | /* got the one */ 208 | break; 209 | } else { 210 | /* need to request more */ 211 | continue; 212 | } 213 | err_case: 214 | close(dev_fd); 215 | close(ctld_fd); 216 | close(ha_fd); 217 | return 1; 218 | } 219 | if (fstatat(dev_fd, zdev, &st, 0)) { 220 | warn("could not request zram device '%s'", zdev); 221 | close(dev_fd); 222 | close(ctld_fd); 223 | return 1; 224 | } 225 | } 226 | if (!S_ISBLK(st.st_mode)) { 227 | warnx("'%s' is not a block device", zdev); 228 | close(dev_fd); 229 | close(ctld_fd); 230 | return 1; 231 | } 232 | close(dev_fd); 233 | close(ctld_fd); 234 | /* now get /sys/block... */ 235 | auto bfd = open("/sys/block", O_DIRECTORY | O_PATH); 236 | if (bfd < 0) { 237 | warn("could not open '/sys/block'"); 238 | return 1; 239 | } 240 | /* and the zram device we need */ 241 | auto zfd = openat(bfd, zdev, O_DIRECTORY | O_PATH); 242 | if (zfd < 0) { 243 | warn("could not open '/sys/block/%s'", zdev); 244 | close(bfd); 245 | return 1; 246 | } 247 | close(bfd); 248 | /* and we can go wild, first reset though */ 249 | if (!write_param(zfd, zdev, "reset", "1")) { 250 | close(zfd); 251 | return 1; 252 | } 253 | /* set the algorithm if we have it, need that first */ 254 | if (zram_algo.size()) { 255 | if (!write_param(zfd, zdev, "comp_algorithm", zram_algo.data())) { 256 | close(zfd); 257 | return 1; 258 | } 259 | if (zram_algo_params.size() && !write_param( 260 | zfd, zdev, "algorithm_params", zram_algo_params.data() 261 | )) { 262 | close(zfd); 263 | return 1; 264 | } 265 | } 266 | /* set the writeback device if expected */ 267 | if (zram_backing_dev.size()) { 268 | if (!write_param( 269 | zfd, zdev, "backing_dev", zram_backing_dev.data() 270 | )) { 271 | close(zfd); 272 | return 1; 273 | } 274 | if (zram_writeback_limit.size()) { 275 | if (!write_param(zfd, zdev, "writeback_limit_enable", "1")) { 276 | close(zfd); 277 | return 1; 278 | } 279 | if (!write_param( 280 | zfd, zdev, "writeback_limit", zram_writeback_limit.data() 281 | )) { 282 | close(zfd); 283 | return 1; 284 | } 285 | } 286 | } 287 | /* set the size */ 288 | if (!write_param(zfd, zdev, "disksize", zram_size.data())) { 289 | close(zfd); 290 | return 1; 291 | } 292 | /* set the mem limit */ 293 | if (zram_mem_limit.size() && !write_param( 294 | zfd, zdev, "mem_limit", zram_mem_limit.data() 295 | )) { 296 | close(zfd); 297 | return 1; 298 | } 299 | std::printf("set up device, formatting...\n"); 300 | close(zfd); 301 | return zram_format(zdev); 302 | } 303 | 304 | static int stop_zram(char const *zdev) { 305 | auto bfd = open("/sys/block", O_DIRECTORY | O_PATH); 306 | if (bfd < 0) { 307 | warn("could not open '/sys/block'"); 308 | return 1; 309 | } 310 | auto zfd = openat(bfd, zdev, O_DIRECTORY | O_PATH); 311 | if (zfd < 0) { 312 | warn("could not open '/sys/block/%s'", zdev); 313 | close(bfd); 314 | return 1; 315 | } 316 | close(bfd); 317 | auto hrfd = open("/sys/class/zram-control/hot_remove", O_WRONLY); 318 | if (hrfd < 0) { 319 | warn("could not open zram hot_remove"); 320 | return 1; 321 | } 322 | if (write_param(zfd, zdev, "reset", "1")) { 323 | write_param(hrfd, zdev, nullptr, zdev + 4); 324 | } 325 | close(zfd); 326 | close(hrfd); 327 | return 0; 328 | } 329 | 330 | static bool load_conf( 331 | char const *s, char *&line, std::size_t &len, char const *zsect 332 | ) { 333 | FILE *f = std::fopen(s, "rb"); 334 | if (!f) { 335 | warnx("could not load '%s'", s); 336 | return false; 337 | } 338 | bool fret = true; 339 | bool in_sect = false; 340 | auto slen = std::strlen(zsect); 341 | for (ssize_t nread; (nread = getline(&line, &len, f)) != -1;) { 342 | /* strip leading whitespace and ignore comments, empty lines etc */ 343 | char *cline = line; 344 | while (std::isspace(*cline)) { 345 | ++cline; 346 | } 347 | if ((*cline == '#') || (*cline == ';') || !*cline) { 348 | continue; 349 | } 350 | /* strip leading spaces */ 351 | while (std::isspace(*cline)) { 352 | ++cline; 353 | } 354 | /* strip trailing spaces */ 355 | auto rl = std::strlen(line); 356 | while (std::isspace(line[rl - 1])) { 357 | line[--rl] = '\0'; 358 | } 359 | if (*cline == '[') { 360 | in_sect = !std::strncmp(cline + 1, zsect, slen); 361 | if ((cline[slen + 1] != ']') || cline[slen + 2]) { 362 | warnx("invalid syntax: '%s'", cline); 363 | return false; 364 | } 365 | continue; 366 | } 367 | /* skip sections not relevant to us */ 368 | if (!in_sect) { 369 | continue; 370 | } 371 | auto *eq = std::strchr(cline, '='); 372 | if (!eq) { 373 | warnx("invalid syntax: '%s'", cline); 374 | return false; 375 | } 376 | *eq = '\0'; 377 | auto *key = cline; 378 | auto *value = eq + 1; 379 | /* strip spaces before assignment */ 380 | while ((eq != cline) && std::isspace(*(eq - 1))) { 381 | *--eq = '\0'; 382 | } 383 | /* strip spaces after assignment */ 384 | while (std::isspace(*value)) { 385 | ++value; 386 | } 387 | if (!*value) { 388 | warnx("empty value for key '%s'", key); 389 | return false; 390 | } 391 | if (!std::strcmp(key, "size")) { 392 | zram_size = value; 393 | } else if (!std::strcmp(key, "algorithm")) { 394 | zram_algo = value; 395 | /* parse the parameters */ 396 | char *algop = zram_algo.data(); 397 | auto *paren = std::strchr(algop, '('); 398 | if (paren) { 399 | char *endp = std::strchr(paren + 1, ')'); 400 | if (!endp || endp[1]) { 401 | warnx("malformed algorithm value '%s'", zram_algo.data()); 402 | return false; 403 | } 404 | char *pbeg = paren + 1; 405 | while ((paren != algop) && std::isspace(*(paren - 1))) { 406 | --paren; 407 | } 408 | *paren = '\0'; 409 | /* just in case the contents of parens are all spaces */ 410 | while ((pbeg != endp) && std::isspace(*pbeg)) { 411 | ++pbeg; 412 | } 413 | /* terminate at ) */ 414 | *endp = '\0'; 415 | /* now algop is just algorithm name, write it into params */ 416 | if (pbeg != endp) { 417 | zram_algo_params += "algo="; 418 | zram_algo_params += algop; 419 | for (;;) { 420 | /* strip leading spaces */ 421 | while (std::isspace(*pbeg)) { 422 | ++pbeg; 423 | } 424 | auto *cpend = std::strchr(pbeg, ','); 425 | char *comma = nullptr; 426 | if (cpend) { 427 | comma = cpend + 1; 428 | *cpend = '\0'; 429 | } else { 430 | cpend = endp; 431 | } 432 | /* strip trailing spaces */ 433 | while ((cpend != pbeg) && std::isspace(*(cpend - 1))) { 434 | --cpend; 435 | } 436 | *cpend = '\0'; 437 | if (pbeg == cpend) { 438 | warnx("algorithm parameter must not be empty"); 439 | return false; 440 | } 441 | zram_algo_params.push_back(' '); 442 | zram_algo_params += pbeg; 443 | if (!comma) { 444 | break; 445 | } 446 | pbeg = comma; 447 | } 448 | } 449 | /* finally shrink the algorithm name just in case */ 450 | zram_algo.resize(paren - algop); 451 | } 452 | } else if (!std::strcmp(key, "format")) { 453 | zram_fmt = value; 454 | } else if (!std::strcmp(key, "mem_limit")) { 455 | zram_mem_limit = value; 456 | } else if (!std::strcmp(key, "writeback_limit")) { 457 | zram_writeback_limit = value; 458 | } else if (!std::strcmp(key, "backing_dev")) { 459 | zram_backing_dev = value; 460 | } else { 461 | warnx("unknown key '%s'", key); 462 | return false; 463 | } 464 | } 465 | std::fclose(f); 466 | return fret; 467 | } 468 | 469 | int main(int argc, char **argv) { 470 | if (geteuid() != 0) { 471 | errx(1, "this program must be run as root"); 472 | } 473 | 474 | if ((argc != 2) && (argc != 3)) { 475 | warnx("incorrect number of arguments"); 476 | usage(stderr); 477 | return 1; 478 | } 479 | 480 | char const *zramname = argv[1]; 481 | if (std::strncmp(zramname, "zram", 4)) { 482 | warnx("incorrect device specified"); 483 | usage(stderr); 484 | return 1; 485 | } 486 | char *errp = nullptr; 487 | auto znum = std::strtoul(zramname + 4, &errp, 10); 488 | if (!errp || *errp || (znum > 99)) { 489 | warnx("incorrect device specified"); 490 | usage(stderr); 491 | return 1; 492 | } 493 | 494 | struct stat st; 495 | /* ensure we've got zram loaded */ 496 | if (stat("/sys/class/zram-control", &st)) { 497 | errx(1, "zram is not loaded"); 498 | } 499 | 500 | char *line = nullptr; 501 | std::size_t len = 0; 502 | 503 | if (argc == 3) { 504 | if (!std::strcmp(argv[2], "stop")) { 505 | return stop_zram(zramname); 506 | } 507 | if (access(argv[2], R_OK)) { 508 | err(1, "could not access '%s'", argv[2]); 509 | } 510 | if (!load_conf(argv[2], line, len, zramname)) { 511 | return 1; 512 | } 513 | std::free(line); 514 | return setup_zram(zramname, znum); 515 | } 516 | 517 | std::unordered_map got_map; 518 | 519 | for (char const **p = paths; *p; ++p) { 520 | int dfd = open(*p, O_RDONLY | O_DIRECTORY); 521 | if (dfd < 0) { 522 | continue; 523 | } 524 | int dupfd = dup(dfd); 525 | if (dupfd < 0) { 526 | err(1, "dupfd"); 527 | } 528 | DIR *dirp = fdopendir(dupfd); 529 | if (!dirp) { 530 | err(1, "fdopendir"); 531 | } 532 | struct dirent *dp; 533 | while ((dp = readdir(dirp))) { 534 | /* must be a regular file or a symlink to regular file; we cannot 535 | * use d_type (nonportable anyway) because that will get DT_LNK 536 | * for symlinks (it does not follow) 537 | */ 538 | struct stat st; 539 | if ((fstatat(dfd, dp->d_name, &st, 0) < 0) || !S_ISREG(st.st_mode)) { 540 | continue; 541 | } 542 | /* check if it matches .conf */ 543 | char const *dn = dp->d_name; 544 | auto sl = std::strlen(dn); 545 | if ((sl <= 5) || strcmp(dn + sl - 5, ".conf")) { 546 | continue; 547 | } 548 | /* check if already in map */ 549 | if (got_map.find(dn) != got_map.end()) { 550 | continue; 551 | } 552 | /* otherwise use its full name */ 553 | std::string fp = *p; 554 | fp.push_back('/'); 555 | fp += dp->d_name; 556 | got_map.emplace(dn, std::move(fp)); 557 | } 558 | close(dfd); 559 | closedir(dirp); 560 | } 561 | 562 | std::vector ord_list; 563 | 564 | /* construct a sorted vector of names, backed by map memory */ 565 | for (auto &p: got_map) { 566 | ord_list.push_back(&p.first); 567 | } 568 | 569 | std::sort(ord_list.begin(), ord_list.end(), [](auto a, auto b) { 570 | return (*a < *b); 571 | }); 572 | 573 | for (auto &c: ord_list) { 574 | if (!load_conf(got_map[*c].data(), line, len, zramname)) { 575 | return 1; 576 | } 577 | } 578 | /* global dinit-zram.conf is last if it exists */ 579 | if (!access(sys_path, R_OK)) { 580 | char const *asysp = strchr(sys_path, '/') + 1; 581 | /* only load if no file called dinit-zram.conf was already handled */ 582 | if (got_map.find(asysp) == got_map.end()) { 583 | if (!load_conf(sys_path, line, len, zramname)) { 584 | return 1; 585 | } 586 | } 587 | } 588 | std::free(line); 589 | 590 | return setup_zram(zramname, znum); 591 | } 592 | -------------------------------------------------------------------------------- /early/helpers/mnt.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * A helper for mounts 3 | * 4 | * SPDX-License-Identifier: BSD-2-Clause 5 | * 6 | * Copyright (c) 2024 q66 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 1. Redistributions of source code must retain the above copyright 12 | * notice, this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 | * SUCH DAMAGE. 28 | */ 29 | 30 | #ifndef _GNU_SOURCE 31 | #define _GNU_SOURCE 32 | #endif 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | /* fallback; not accurate but good enough for early boot */ 54 | static int mntpt_noproc(char const *inpath, struct stat *st) { 55 | dev_t sdev; 56 | ino_t sino; 57 | char *path; 58 | size_t slen; 59 | 60 | sdev = st->st_dev; 61 | sino = st->st_ino; 62 | 63 | /* can't detect file bindmounts without proc */ 64 | if (!S_ISDIR(st->st_mode)) { 65 | return 1; 66 | } 67 | 68 | slen = strlen(inpath); 69 | path = static_cast(malloc(slen + 4)); 70 | if (!path) { 71 | return 1; 72 | } 73 | 74 | snprintf(path, slen + 4, "%s/..", inpath); 75 | if (stat(path, st)) { 76 | return 1; 77 | } 78 | 79 | /* different device -> mount point 80 | * same inode -> most likely root 81 | */ 82 | free(path); 83 | return (st->st_dev == sdev) && (st->st_ino != sino); 84 | } 85 | 86 | static int do_is(char const *mntpt) { 87 | struct stat st; 88 | FILE *sf; 89 | struct mntent *mn; 90 | char *path; 91 | int retval = 1; 92 | 93 | /* symbolic link or not given */ 94 | if (lstat(mntpt, &st) || S_ISLNK(st.st_mode)) { 95 | return 1; 96 | } 97 | 98 | sf = setmntent("/proc/self/mounts", "r"); 99 | if (!sf) { 100 | return mntpt_noproc(mntpt, &st); 101 | } 102 | 103 | path = realpath(mntpt, nullptr); 104 | if (!path) { 105 | return 1; 106 | } 107 | 108 | while ((mn = getmntent(sf))) { 109 | if (!strcmp(mn->mnt_dir, path)) { 110 | retval = 0; 111 | break; 112 | } 113 | } 114 | 115 | endmntent(sf); 116 | free(path); 117 | return retval; 118 | } 119 | 120 | static constexpr unsigned long MS_TMASK = MS_BIND | MS_MOVE | MS_REMOUNT; 121 | static constexpr unsigned long MS_AMASK = MS_NOATIME | MS_RELATIME; 122 | 123 | struct mntopt { 124 | char const *name; 125 | unsigned long flagmask; 126 | unsigned long flagset; 127 | bool invert; 128 | }; 129 | 130 | static mntopt known_opts[] = { 131 | {"async", MS_SYNCHRONOUS, MS_SYNCHRONOUS, true}, 132 | {"atime", MS_AMASK, MS_NOATIME, true}, 133 | {"bind", MS_TMASK, MS_BIND, false}, 134 | {"dev", MS_NODEV, MS_NODEV, true}, 135 | {"diratime", MS_NODIRATIME, MS_NODIRATIME, true}, 136 | {"dirsync", MS_DIRSYNC, MS_DIRSYNC, false}, 137 | {"exec", MS_NOEXEC, MS_NOEXEC, true}, 138 | {"iversion", MS_I_VERSION, MS_I_VERSION, false}, 139 | {"lazytime", MS_LAZYTIME, MS_LAZYTIME, false}, 140 | {"loud", MS_SILENT, MS_SILENT, true}, 141 | {"mand", MS_MANDLOCK, MS_MANDLOCK, false}, 142 | {"move", MS_TMASK, MS_MOVE, false}, 143 | {"noatime", MS_AMASK, MS_NOATIME, false}, 144 | {"nodev", MS_NODEV, MS_NODEV, false}, 145 | {"nodiratime", MS_NODIRATIME, MS_NODIRATIME, false}, 146 | {"noexec", MS_NOEXEC, MS_NOEXEC, false}, 147 | {"noiversion", MS_I_VERSION, MS_I_VERSION, true}, 148 | {"nolazytime", MS_LAZYTIME, MS_LAZYTIME, true}, 149 | {"nomand", MS_MANDLOCK, MS_MANDLOCK, true}, 150 | {"norelatime", MS_AMASK, MS_RELATIME, true}, 151 | {"nostrictatime", MS_STRICTATIME, MS_STRICTATIME, true}, 152 | {"nosuid", MS_NOSUID, MS_NOSUID, false}, 153 | {"nosymfollow", MS_NOSYMFOLLOW, MS_NOSYMFOLLOW, false}, 154 | {"private", MS_PRIVATE, MS_PRIVATE, false}, 155 | {"rbind", MS_TMASK, MS_BIND | MS_REC, false}, 156 | {"relatime", MS_AMASK, MS_RELATIME, false}, 157 | {"remount", MS_TMASK, MS_REMOUNT, false}, 158 | {"ro", MS_RDONLY, MS_RDONLY, false}, 159 | {"rprivate", MS_PRIVATE, MS_PRIVATE | MS_REC, false}, 160 | {"rshared", MS_SHARED, MS_SHARED | MS_REC, false}, 161 | {"rslave", MS_SLAVE, MS_SLAVE | MS_REC, false}, 162 | {"runbindable", MS_UNBINDABLE, MS_UNBINDABLE | MS_REC, false}, 163 | {"rw", MS_RDONLY, MS_RDONLY, true}, 164 | {"silent", MS_SILENT, MS_SILENT, false}, 165 | {"shared", MS_SHARED, MS_SHARED, false}, 166 | {"slave", MS_SLAVE, MS_SLAVE, false}, 167 | {"strictatime", MS_STRICTATIME, MS_STRICTATIME, false}, 168 | {"suid", MS_NOSUID, MS_NOSUID, true}, 169 | {"symfollow", MS_NOSYMFOLLOW, MS_NOSYMFOLLOW, true}, 170 | {"sync", MS_SYNCHRONOUS, MS_SYNCHRONOUS, false}, 171 | {"unbindable", MS_UNBINDABLE, MS_UNBINDABLE, false}, 172 | }; 173 | 174 | static unsigned long parse_mntopts( 175 | char *opts, unsigned long flags, unsigned long &oflags, std::string &eopts, 176 | std::string *loopdev = nullptr, std::string *offset = nullptr, 177 | std::string *sizelimit = nullptr 178 | ) { 179 | if (!opts) { 180 | return flags; 181 | } 182 | for (char *optn; (optn = strsep(&opts, ","));) { 183 | if (!optn[0]) { 184 | continue; 185 | } 186 | mntopt *optv = nullptr; 187 | for (size_t i = 0; i < (sizeof(known_opts) / sizeof(mntopt)); ++i) { 188 | auto cmpv = std::strcmp(optn, known_opts[i].name); 189 | if (cmpv == 0) { 190 | optv = &known_opts[i]; 191 | flags &= ~optv->flagmask; 192 | oflags &= ~optv->flagmask; 193 | if (optv->invert) { 194 | flags &= ~optv->flagset; 195 | oflags &= ~optv->flagset; 196 | } else { 197 | flags |= optv->flagset; 198 | oflags |= optv->flagset; 199 | } 200 | break; 201 | } else if (cmpv < 0) { 202 | /* no point in searching further */ 203 | break; 204 | } 205 | } 206 | /* not recognized or manually handled */ 207 | if (!optv) { 208 | /* skip stuff that is not to be passed */ 209 | if (((optn[0] == 'X') || (optn[0] == 'x')) && (optn[1] == '-')) { 210 | continue; 211 | } 212 | if (!std::strcmp(optn, "defaults")) { 213 | /* this resets some of the flags */ 214 | flags &= ~(MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS); 215 | oflags &= ~(MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_SYNCHRONOUS); 216 | continue; 217 | } 218 | if (loopdev) { 219 | if (!std::strncmp(optn, "loop", 4) && ((optn[4] == '=') || !optn[4])) { 220 | *loopdev = optn; 221 | continue; 222 | } 223 | auto *eq = std::strchr(optn, '='); 224 | if (eq) { 225 | /* maybe params */ 226 | if (!std::strncmp(optn, "offset", eq - optn)) { 227 | *offset = eq + 1; 228 | continue; 229 | } else if (!std::strncmp(optn, "sizelimit", eq - optn)) { 230 | *sizelimit = eq + 1; 231 | continue; 232 | } 233 | } 234 | } 235 | if (!eopts.empty()) { 236 | eopts.push_back(','); 237 | } 238 | eopts += optn; 239 | } 240 | } 241 | return flags; 242 | } 243 | 244 | static std::string unparse_mntopts(unsigned long flags, std::string const &eopts) { 245 | std::string ret{}; 246 | for (size_t i = 0; i < (sizeof(known_opts) / sizeof(mntopt)); ++i) { 247 | auto &ko = known_opts[i]; 248 | if (ko.invert || !(flags & ko.flagset)) { 249 | continue; 250 | } 251 | switch (ko.flagset) { 252 | case MS_PRIVATE: 253 | case MS_SHARED: 254 | case MS_SLAVE: 255 | case MS_UNBINDABLE: 256 | /* these should not be passed through */ 257 | continue; 258 | case MS_REC: 259 | if (!(flags & MS_BIND)) { 260 | continue; 261 | } 262 | break; 263 | } 264 | if (!ret.empty()) { 265 | ret.push_back(','); 266 | } 267 | ret += ko.name; 268 | } 269 | /* TODO: filter these too... */ 270 | if (!eopts.empty()) { 271 | if (!ret.empty()) { 272 | ret.push_back(','); 273 | } 274 | ret += eopts; 275 | } 276 | return ret; 277 | } 278 | 279 | static int parse_umntopts(char *opts) { 280 | if (!opts) { 281 | return 0; 282 | } 283 | int flags = 0; 284 | for (char *s; (s = strsep(&opts, ","));) { 285 | if (!std::strcmp(s, "force")) { 286 | flags |= MNT_FORCE; 287 | } else if (!std::strcmp(s, "detach")) { 288 | flags |= MNT_DETACH; 289 | } 290 | } 291 | return flags; 292 | } 293 | 294 | static int do_mount_helper( 295 | char const *tgt, char const *src, char const *fstype, 296 | unsigned long flags, std::string const &eopts 297 | ) { 298 | char hname[256]; 299 | snprintf(hname, sizeof(hname), "/sbin/mount.%s", fstype); 300 | if (access(hname, X_OK) < 0) { 301 | return -1; 302 | } 303 | auto opts = unparse_mntopts(flags, eopts); 304 | auto cpid = fork(); 305 | if (cpid < 0) { 306 | warn("fork failed"); 307 | return 1; 308 | } 309 | if (cpid == 0) { 310 | /* child, exec the helper */ 311 | execl(hname, hname, "-o", opts.c_str(), src, tgt, 0); 312 | abort(); 313 | } 314 | int status; 315 | while (waitpid(cpid, &status, 0) < 0) { 316 | if (errno == EINTR) { 317 | continue; 318 | } 319 | warn("waitpid failed"); 320 | return 1; 321 | } 322 | return 0; 323 | } 324 | 325 | static int do_mount_raw( 326 | char const *tgt, char const *src, char const *fstype, 327 | unsigned long flags, unsigned long iflags, std::string &eopts, 328 | bool helper = false 329 | ) { 330 | unsigned long pflags = flags; 331 | unsigned long pmask = MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE; 332 | /* propagation flags need to be set separately! */ 333 | if (pflags & pmask) { 334 | pflags &= pmask | MS_REC; 335 | flags &= ~(pmask | MS_REC); 336 | } 337 | if (helper) { 338 | /* if false, helper may still be tried but *after* internal mount */ 339 | auto hret = do_mount_helper(tgt, src, fstype, iflags, eopts); 340 | if (hret >= 0) { 341 | return hret; 342 | } 343 | } 344 | if (mount(src, tgt, fstype, flags, eopts.data()) < 0) { 345 | int serrno = errno; 346 | /* try a helper if regular mount fails */ 347 | int ret = do_mount_helper(tgt, src, fstype, iflags, eopts); 348 | if (ret < 0) { 349 | errno = serrno; 350 | warn("failed to mount filesystem '%s'", tgt); 351 | return 1; 352 | } 353 | return ret; 354 | } 355 | /* propagation flags should change separately */ 356 | if ((pflags & pmask) && (mount(src, tgt, fstype, pflags, nullptr) < 0)) { 357 | warn("failed to change propagation flags of '%s'", tgt); 358 | return 1; 359 | } 360 | return 0; 361 | } 362 | 363 | static bool loop_match( 364 | int fd, struct stat const &fst, uint64_t offset, uint64_t sizelimit, 365 | unsigned long &flags 366 | ) { 367 | loop_info64 linf; 368 | if (fd <= 0) { 369 | return false; 370 | } 371 | if (ioctl(fd, LOOP_GET_STATUS64, &linf)) { 372 | return false; 373 | } 374 | if ( 375 | (linf.lo_device == fst.st_dev) && 376 | (linf.lo_inode == fst.st_ino) && 377 | (linf.lo_offset == offset) && 378 | (linf.lo_sizelimit == sizelimit) 379 | ) { 380 | if (linf.lo_flags & LO_FLAGS_READ_ONLY) { 381 | flags |= MS_RDONLY; 382 | } 383 | return true; 384 | } 385 | return false; 386 | } 387 | 388 | static int open_loop( 389 | int mode, struct stat const &fst, uint64_t offset, 390 | uint64_t sizelimit, std::string &src, bool &configure, 391 | unsigned long &flags 392 | ) { 393 | char dbuf[64]; 394 | 395 | /* first open /dev as a base point for everything */ 396 | auto dfd = open("/dev", O_DIRECTORY | O_RDONLY); 397 | if (dfd < 0) { 398 | warn("could not open /dev"); 399 | return -1; 400 | } 401 | /* internal version for fdopendir */ 402 | auto dfdd = dup(dfd); 403 | if (dfdd < 0) { 404 | warn("could not dup /dev fd"); 405 | close(dfd); 406 | return -1; 407 | } 408 | /* now open it for looping... */ 409 | auto *dr = fdopendir(dfdd); 410 | if (!dr) { 411 | warn("could not fdopendir /dev"); 412 | close(dfd); 413 | return -1; 414 | } 415 | /* then try finding a loop device that is preconfigured with 416 | * the params we need, and if we find one, just use it 417 | */ 418 | for (;;) { 419 | errno = 0; 420 | auto *dp = readdir(dr); 421 | if (!dp) { 422 | if (errno == 0) { 423 | closedir(dr); 424 | break; 425 | } 426 | warn("could not read from /dev"); 427 | close(dfd); 428 | closedir(dr); 429 | return -1; 430 | } 431 | if (std::strncmp(dp->d_name, "loop", 4)) { 432 | /* irrelevant */ 433 | continue; 434 | } 435 | if (!std::strcmp(dp->d_name, "loop-control")) { 436 | /* also not */ 437 | continue; 438 | } 439 | /* potential loopdev */ 440 | auto lfd = openat(dfd, dp->d_name, mode); 441 | if (loop_match(lfd, fst, offset, sizelimit, flags)) { 442 | std::snprintf(dbuf, sizeof(dbuf), "/dev/%s", dp->d_name); 443 | src = dbuf; 444 | configure = false; 445 | closedir(dr); 446 | close(dfd); 447 | return lfd; 448 | } 449 | close(lfd); 450 | } 451 | /* did not find a preconfigured one, so grab a free one */ 452 | auto cfd = openat(dfd, "loop-control", O_RDWR); 453 | if (cfd < 0) { 454 | warn("could not open /dev/loop-control"); 455 | close(dfd); 456 | return -1; 457 | } 458 | auto rv = ioctl(cfd, LOOP_CTL_GET_FREE, 0); 459 | if (rv < 0) { 460 | warn("could not find a free loop device"); 461 | close(cfd); 462 | close(dfd); 463 | return -1; 464 | } 465 | close(cfd); 466 | std::snprintf(dbuf, sizeof(dbuf), "/dev/loop%d", rv); 467 | /* try opening with the wanted mode */ 468 | src = dbuf; 469 | auto ret = openat(dfd, &dbuf[5], mode); 470 | close(dfd); 471 | return ret; 472 | } 473 | 474 | static int setup_loop( 475 | std::string const &loopdev, std::string const &offsetp, 476 | std::string const &sizelimitp, std::string &src, int &afd, 477 | unsigned long &flags 478 | ) { 479 | char const *lsrc = loopdev.data(); 480 | auto *eq = std::strchr(lsrc, '='); 481 | /* loop file descriptor and source file descriptor */ 482 | int lfd = -1, ffd = -1; 483 | /* parse the options */ 484 | uint64_t sizelimit = 0, offset = 0; 485 | if (!offsetp.empty()) { 486 | char *errp = nullptr; 487 | offset = std::strtoull(offsetp.data(), &errp, 10); 488 | if (!errp || *errp) { 489 | warnx("failed to parse loop offset"); 490 | return -1; 491 | } 492 | } 493 | if (!sizelimitp.empty()) { 494 | char *errp = nullptr; 495 | sizelimit = std::strtoull(sizelimitp.data(), &errp, 10); 496 | if (!errp || *errp) { 497 | warnx("failed to parse loop sizelimit"); 498 | return -1; 499 | } 500 | } 501 | /* open the source file first... */ 502 | int lmode = (flags & MS_RDONLY) ? O_RDONLY : O_RDWR; 503 | ffd = open(src.data(), lmode); 504 | /* try readonly as a fallback */ 505 | if (ffd < 0 && (lmode != O_RDONLY) && (errno == EROFS)) { 506 | lmode = O_RDONLY; 507 | flags |= MS_RDONLY; 508 | ffd = open(src.data(), lmode); 509 | } 510 | if (ffd < 0) { 511 | warn("failed to open source file '%s'", src.data()); 512 | return -1; 513 | } 514 | /* stat it for later checking */ 515 | struct stat fst; 516 | if (fstat(ffd, &fst)) { 517 | warn("failed to stat source file"); 518 | close(ffd); 519 | return -1; 520 | } 521 | /* pre-create a loop configuration */ 522 | struct loop_config loopc; 523 | std::memset(&loopc, 0, sizeof(loopc)); 524 | loopc.fd = ffd; 525 | loopc.info.lo_offset = offset; 526 | loopc.info.lo_sizelimit = sizelimit; 527 | loopc.info.lo_flags = LO_FLAGS_AUTOCLEAR | ( 528 | (lmode == O_RDONLY) ? LO_FLAGS_READ_ONLY : 0 529 | ); 530 | if (src.size() >= LO_NAME_SIZE) { 531 | std::memcpy(loopc.info.lo_file_name, src.data(), LO_NAME_SIZE - 1); 532 | } else { 533 | std::memcpy(loopc.info.lo_file_name, src.data(), src.size()); 534 | } 535 | /* now see if we have to configure at all */ 536 | bool configure = true; 537 | if (!eq || !eq[1]) { 538 | /* find unused loop device, or a preconfigured one */ 539 | lfd = open_loop(lmode, fst, offset, sizelimit, src, configure, flags); 540 | } else { 541 | lfd = open(eq + 1, lmode); 542 | if (loop_match(lfd, fst, offset, sizelimit, flags)) { 543 | configure = false; 544 | } 545 | src = eq + 1; 546 | } 547 | if (lfd < 0) { 548 | warn("failed to open loop device"); 549 | close(ffd); 550 | return -1; 551 | } 552 | /* if the loop is preconfigured, we're good; src was already set */ 553 | if (!configure) { 554 | afd = lfd; 555 | return 0; 556 | } 557 | /* finally configure */ 558 | if (ioctl(lfd, LOOP_CONFIGURE, &loopc)) { 559 | warn("failed to configure the loop device"); 560 | close(ffd); 561 | close(lfd); 562 | return -1; 563 | } 564 | close(ffd); 565 | afd = lfd; 566 | return 0; 567 | } 568 | 569 | static int setup_src( 570 | char const *src, char *opts, unsigned long &flags, unsigned long &iflags, 571 | std::string &asrc, std::string &eopts 572 | ) { 573 | /* potential loop device */ 574 | std::string loopdev{}; 575 | /* parameters for loop */ 576 | std::string offset{}; 577 | std::string sizelimit{}; 578 | /* do the initial parse pass */ 579 | iflags = 0; 580 | flags = parse_mntopts( 581 | opts, MS_SILENT, iflags, eopts, &loopdev, &offset, &sizelimit 582 | ); 583 | /* if loop was requested, set it up */ 584 | int afd = -1; 585 | auto oflags = flags; 586 | asrc = src; 587 | /* resolve special syntax e.g. PARTLABEL=foo */ 588 | #define RESOLVE_PFX(name, lname) \ 589 | if (!std::strncmp(asrc.data(), name "=", sizeof(name))) { \ 590 | std::string rsrc = "/dev/disk/by-" lname "/"; \ 591 | rsrc += asrc.data() + sizeof(name); \ 592 | asrc = std::move(rsrc); \ 593 | } 594 | RESOLVE_PFX("LABEL", "label") 595 | else RESOLVE_PFX("UUID", "uuid") 596 | else RESOLVE_PFX("PARTLABEL", "partlabel") 597 | else RESOLVE_PFX("PARTUUID", "partuuid") 598 | else RESOLVE_PFX("ID", "id") 599 | /* if no loop device, bail */ 600 | if (loopdev.empty()) { 601 | return 0; 602 | } 603 | auto ret = setup_loop(loopdev, offset, sizelimit, asrc, afd, flags); 604 | if (ret < 0) { 605 | return ret; 606 | } 607 | if (!(oflags & MS_RDONLY) && (flags & MS_RDONLY)) { 608 | iflags |= MS_RDONLY; 609 | warnx("Source file write-protected, mounting read-only."); 610 | } 611 | return afd; 612 | } 613 | 614 | static int do_mount( 615 | char const *tgt, char const *src, char const *fstype, char *opts 616 | ) { 617 | std::string asrc{}; 618 | std::string eopts{}; 619 | unsigned long flags; 620 | unsigned long iflags; 621 | auto afd = setup_src(src, opts, flags, iflags, asrc, eopts); 622 | if (afd < 0) { 623 | return 1; 624 | } 625 | auto ret = do_mount_raw(tgt, asrc.data(), fstype, flags, iflags, eopts); 626 | /* close after mount is done so it does not autodestroy */ 627 | if (afd > 0) { 628 | close(afd); 629 | } 630 | return ret; 631 | } 632 | 633 | static int do_try( 634 | char const *tgt, char const *src, char const *fstype, char *opts 635 | ) { 636 | /* already mounted */ 637 | if (do_is(tgt) == 0) { 638 | return 0; 639 | } 640 | return do_mount(tgt, src, fstype, opts); 641 | } 642 | 643 | static int do_try_maybe( 644 | char const *tgt, char const *src, char const *fstype, char *opts 645 | ) { 646 | struct stat st; 647 | /* don't bother if we can't mount it there */ 648 | if (stat(tgt, &st) || !S_ISDIR(st.st_mode)) { 649 | return 0; 650 | } 651 | return do_try(tgt, src, fstype, opts); 652 | } 653 | 654 | static int do_remount(char const *tgt, char *opts) { 655 | unsigned long rmflags = MS_SILENT | MS_REMOUNT; 656 | unsigned long iflags = 0; 657 | std::string mtab_eopts{}; 658 | struct mntent *mn = nullptr; 659 | /* preserve existing params */ 660 | FILE *sf = setmntent("/proc/self/mounts", "r"); 661 | if (!sf) { 662 | warn("could not open mtab"); 663 | return 1; 664 | } 665 | while ((mn = getmntent(sf))) { 666 | if (!strcmp(mn->mnt_dir, tgt)) { 667 | /* found root */ 668 | rmflags = parse_mntopts(mn->mnt_opts, rmflags, iflags, mtab_eopts); 669 | break; 670 | } else { 671 | mn = nullptr; 672 | } 673 | } 674 | endmntent(sf); 675 | if (!mn) { 676 | warnx("could not locate '%s' mount", tgt); 677 | return 1; 678 | } 679 | rmflags = parse_mntopts(opts, rmflags, iflags, mtab_eopts); 680 | /* and remount... */ 681 | if (do_mount_raw( 682 | mn->mnt_dir, mn->mnt_fsname, mn->mnt_type, rmflags, 683 | iflags | MS_REMOUNT, mtab_eopts 684 | )) { 685 | return 1; 686 | } 687 | return 0; 688 | } 689 | 690 | static int do_umount(char const *tgt, char *opts) { 691 | if (umount2(tgt, parse_umntopts(opts)) < 0) { 692 | warn("umount2"); 693 | return 1; 694 | } 695 | return 0; 696 | } 697 | 698 | static int do_prepare(char *root_opts) { 699 | char procsys_opts[] = "nosuid,noexec,nodev"; 700 | char dev_opts[] = "mode=0755,nosuid"; 701 | char shm_opts[] = "mode=1777,nosuid,nodev"; 702 | /* first set umask to an unrestricted value */ 703 | umask(0); 704 | /* first try mounting procfs and fail if we can't */ 705 | if (do_try("/proc", "proc", "proc", procsys_opts)) { 706 | return 1; 707 | } 708 | /* ensure a new enough kernel is used to avoid bugs and missing 709 | * syscalls and whatever other issues that are likely to happen 710 | */ 711 | utsname ubuf; 712 | if (uname(&ubuf)) { 713 | warn("could not get uname"); 714 | return 1; 715 | } 716 | char *ustr = ubuf.release; 717 | char *uerr = nullptr; 718 | auto umaj = std::strtoul(ustr, &uerr, 10); 719 | if ((umaj < 5) || !uerr || (*uerr != '.')) { 720 | warnx("kernels older than 5.x are not supported"); 721 | return 1; 722 | } 723 | if (umaj == 5) { 724 | ustr = uerr + 1; 725 | uerr = nullptr; 726 | auto umin = std::strtoul(ustr, &uerr, 10); 727 | if (umin < 10) { 728 | warnx("kernels older than 5.10 are not supported"); 729 | return 1; 730 | } 731 | } 732 | /* try remounting / with the params we want; this may fail depending on fs */ 733 | do_remount("/", root_opts); 734 | /* other initial pseudofs... */ 735 | if (do_try("/sys", "sysfs", "sysfs", procsys_opts)) { 736 | return 1; 737 | } 738 | if (do_try("/dev", "dev", "devtmpfs", dev_opts)) { 739 | return 1; 740 | } 741 | /* mountpoints for pts, shm; if these fail the mount will too */ 742 | mkdir("/dev/pts", 0755); 743 | mkdir("/dev/shm", 0755); 744 | /* try getting the tty group */ 745 | auto *ttyg = getgrnam("tty"); 746 | char pts_opts[128]; 747 | snprintf( 748 | pts_opts, sizeof(pts_opts), "mode=0620,gid=%u,nosuid,noexec", 749 | ttyg ? unsigned(ttyg->gr_gid) : 5 750 | ); 751 | if (do_try("/dev/pts", "devpts", "devpts", pts_opts)) { 752 | return 1; 753 | } 754 | if (do_try("/dev/shm", "shm", "tmpfs", shm_opts)) { 755 | return 1; 756 | } 757 | /* stdio symlinks if necessary */ 758 | if ((symlink("/proc/self/fd", "/dev/fd") < 0) && (errno != EEXIST)) { 759 | warn("could not create /dev/fd"); 760 | return 1; 761 | } 762 | if ((symlink("/proc/self/fd/0", "/dev/stdin") < 0) && (errno != EEXIST)) { 763 | warn("could not create /dev/stdin"); 764 | return 1; 765 | } 766 | if ((symlink("/proc/self/fd/1", "/dev/stdout") < 0) && (errno != EEXIST)) { 767 | warn("could not create /dev/stdout"); 768 | return 1; 769 | } 770 | if ((symlink("/proc/self/fd/2", "/dev/stderr") < 0) && (errno != EEXIST)) { 771 | warn("could not create /dev/stderr"); 772 | return 1; 773 | } 774 | /* auxiliary pseudofs */ 775 | if (do_try_maybe("/sys/kernel/security", "securityfs", "securityfs", nullptr)) { 776 | warn("could not mount /sys/kernel/security"); 777 | return 1; 778 | } 779 | if (do_try_maybe("/sys/firmware/efi/efivars", "efivarfs", "efivarfs", procsys_opts)) { 780 | warn("could not mount /sys/firmware/efi/efivars"); 781 | return 1; 782 | } 783 | if (do_try_maybe("/sys/fs/selinux", "selinuxfs", "selinuxfs", nullptr)) { 784 | warn("could not mount /sys/fs/selinux"); 785 | return 1; 786 | } 787 | /* success! */ 788 | return 0; 789 | } 790 | 791 | static int do_root_rw() { 792 | /* remount / with requested parameters; if present in fstab, use those, 793 | * if not present, leave as-is except clear the rdonly flag 794 | */ 795 | unsigned long rmflags = MS_SILENT | MS_REMOUNT; 796 | unsigned long iflags = 0; 797 | std::string fstab_eopts{}; 798 | struct mntent *mn = nullptr; 799 | /* look up requested root mount in fstab first */ 800 | FILE *sf = setmntent("/etc/fstab", "r"); 801 | if (sf) { 802 | while ((mn = getmntent(sf))) { 803 | if (!strcmp(mn->mnt_dir, "/")) { 804 | /* found root */ 805 | rmflags = parse_mntopts( 806 | mn->mnt_opts, rmflags, iflags, fstab_eopts 807 | ); 808 | break; 809 | } else { 810 | mn = nullptr; 811 | } 812 | } 813 | endmntent(sf); 814 | } else if (errno != ENOENT) { 815 | warn("could not open fstab"); 816 | return 1; 817 | } 818 | /* if not found, look it up in mtab instead, and strip ro flag */ 819 | if (!mn) { 820 | sf = setmntent("/proc/self/mounts", "r"); 821 | if (!sf) { 822 | warn("could not open mtab"); 823 | return 1; 824 | } 825 | while ((mn = getmntent(sf))) { 826 | if (!strcmp(mn->mnt_dir, "/")) { 827 | /* found root */ 828 | rmflags = parse_mntopts( 829 | mn->mnt_opts, rmflags, iflags, fstab_eopts 830 | ); 831 | break; 832 | } else { 833 | mn = nullptr; 834 | } 835 | } 836 | rmflags &= ~MS_RDONLY; 837 | iflags &= ~MS_RDONLY; 838 | endmntent(sf); 839 | } 840 | if (!mn) { 841 | warnx("could not locate root mount"); 842 | return 1; 843 | } 844 | /* and remount... */ 845 | if (do_mount_raw( 846 | mn->mnt_dir, mn->mnt_fsname, mn->mnt_type, rmflags, 847 | iflags | MS_REMOUNT, fstab_eopts 848 | )) { 849 | return 1; 850 | } 851 | return 0; 852 | } 853 | 854 | static int do_getent(char const *tab, const char *mntpt, char const *ent) { 855 | FILE *sf = setmntent(tab, "r"); 856 | if (!sf) { 857 | warn("could not open '%s'", tab); 858 | return 1; 859 | } 860 | for (struct mntent *mn; (mn = getmntent(sf));) { 861 | if (strcmp(mn->mnt_dir, mntpt)) { 862 | continue; 863 | } 864 | if (!std::strcmp(ent, "fsname")) { 865 | printf("%s\n", mn->mnt_fsname); 866 | } else if (!std::strcmp(ent, "type")) { 867 | printf("%s\n", mn->mnt_type); 868 | } else if (!std::strcmp(ent, "opts")) { 869 | printf("%s\n", mn->mnt_opts); 870 | } else if (!std::strcmp(ent, "freq")) { 871 | printf("%d\n", mn->mnt_freq); 872 | } else if (!std::strcmp(ent, "passno")) { 873 | printf("%d\n", mn->mnt_passno); 874 | } else { 875 | warnx("invalid field '%s'", ent); 876 | return 1; 877 | } 878 | } 879 | return 0; 880 | } 881 | 882 | static struct option lopts[] = { 883 | {"from", required_argument, 0, 's'}, 884 | {"to", required_argument, 0, 'm'}, 885 | {"type", required_argument, 0, 't'}, 886 | {"options", required_argument, 0, 'o'}, 887 | {nullptr, 0, 0, 0} 888 | }; 889 | 890 | static char *unesc_mnt(char *beg) { 891 | char *dest = beg; 892 | char const *src = beg; 893 | while (*src) { 894 | char const *val; 895 | unsigned char cv = '\0'; 896 | /* not escape */ 897 | if (*src != '\\') { 898 | *dest++ = *src++; 899 | continue; 900 | } 901 | /* double slash */ 902 | if (src[1] == '\\') { 903 | ++src; 904 | *dest++ = *src++; 905 | continue; 906 | } 907 | /* else unscape */ 908 | val = src + 1; 909 | for (int i = 0; i < 3; ++i) { 910 | if (*val >= '0' && *val <= '7') { 911 | cv <<= 3; 912 | cv += *val++ - '0'; 913 | } else { 914 | break; 915 | } 916 | } 917 | if (cv) { 918 | *dest++ = cv; 919 | src = val; 920 | } else { 921 | *dest++ = *src++; 922 | } 923 | } 924 | *dest = '\0'; 925 | return beg; 926 | } 927 | 928 | static int is_mounted( 929 | int mfd, char const *from, char const *to, std::vector &data 930 | ) { 931 | auto off = lseek(mfd, 0, SEEK_SET); 932 | if (off < 0) { 933 | warn("failed to seek mounts"); 934 | return -1; 935 | } 936 | auto *buf = data.data(); 937 | auto cap = data.capacity(); 938 | auto rn = read(mfd, buf, cap); 939 | if (rn < 0) { 940 | warn("failed to read mounts"); 941 | return -1; 942 | } 943 | if (std::size_t(rn) == cap) { 944 | /* double and try again from scratch to avoid races */ 945 | data.reserve(cap * 2); 946 | return is_mounted(mfd, from, to, data); 947 | } 948 | /* terminate so we have a safe string */ 949 | buf[rn] = '\0'; 950 | /* now we have all the mounts; we can go over them line by line... */ 951 | for (;;) { 952 | auto *p = std::strchr(buf, '\n'); 953 | if (p) { 954 | *p = '\0'; 955 | } 956 | /* now parse the current line... get just the source first */ 957 | auto sp = std::strchr(buf, ' '); 958 | if (!sp) { 959 | /* weird line? should not happen */ 960 | goto next; 961 | } 962 | *sp = '\0'; 963 | if (std::strcmp(buf, from)) { 964 | /* unmatched source, so it's not this */ 965 | goto next; 966 | } 967 | buf = sp + 1; 968 | /* matched source, now try dest */ 969 | sp = std::strchr(buf, ' '); 970 | if (!sp) { 971 | /* malformed line again */ 972 | goto next; 973 | } 974 | *sp = '\0'; 975 | /* unescape */ 976 | if (!std::strcmp(unesc_mnt(buf), to)) { 977 | /* yay */ 978 | return 0; 979 | } 980 | next: 981 | if (!p) { 982 | break; 983 | } 984 | buf = p + 1; 985 | } 986 | /* not mounted */ 987 | return 1; 988 | } 989 | 990 | static int sigpipe[2]; 991 | 992 | static void sig_handler(int sign) { 993 | write(sigpipe[1], &sign, sizeof(sign)); 994 | } 995 | 996 | static int do_supervise(int argc, char **argv) { 997 | char *from = nullptr, *to = nullptr, *type = nullptr, *options = nullptr; 998 | for (;;) { 999 | int idx = 0; 1000 | auto c = getopt_long(argc, argv, "", lopts, &idx); 1001 | if (c == -1) { 1002 | break; 1003 | } 1004 | switch (c) { 1005 | case 's': 1006 | from = optarg; 1007 | break; 1008 | case 'm': 1009 | to = optarg; 1010 | break; 1011 | case 't': 1012 | type = optarg; 1013 | break; 1014 | case 'o': 1015 | options = optarg; 1016 | break; 1017 | case '?': 1018 | return 1; 1019 | default: 1020 | warnx("unknown argument '%c'", c); 1021 | return 1; 1022 | } 1023 | } 1024 | if (optind < argc) { 1025 | warnx("supervise takes no positional arguments"); 1026 | return 1; 1027 | } 1028 | if (!from || !to || !type) { 1029 | warnx("one of the following is missing: --from, --to, --type"); 1030 | return 1; 1031 | } 1032 | /* set up termination signals */ 1033 | struct sigaction sa{}; 1034 | sa.sa_handler = sig_handler; 1035 | sa.sa_flags = SA_RESTART; 1036 | sigemptyset(&sa.sa_mask); 1037 | sigaction(SIGTERM, &sa, nullptr); 1038 | sigaction(SIGINT, &sa, nullptr); 1039 | /* we will be polling 2 descriptors; sigpipe and mounts */ 1040 | pollfd pfd[2]; 1041 | /* set up a selfpipe for signals */ 1042 | if (pipe(sigpipe) < 0) { 1043 | warn("pipe failed"); 1044 | return 1; 1045 | } 1046 | pfd[0].fd = sigpipe[0]; 1047 | pfd[0].events = POLLIN; 1048 | pfd[0].revents = 0; 1049 | /* set up mounts for polling... */ 1050 | int mfd = open("/proc/self/mounts", O_RDONLY); 1051 | if (mfd < 0) { 1052 | warn("could not open mounts"); 1053 | return 1; 1054 | } 1055 | pfd[1].fd = mfd; 1056 | pfd[1].events = POLLPRI; 1057 | pfd[1].revents = 0; 1058 | /* prepare flags for mounting, figure out loopdev etc */ 1059 | std::string asrc{}; 1060 | std::string eopts{}; 1061 | std::vector mdata{}; 1062 | unsigned long flags; 1063 | unsigned long iflags; 1064 | auto afd = setup_src(from, options, flags, iflags, asrc, eopts); 1065 | if (afd < 0) { 1066 | return 1; 1067 | } 1068 | /* reserve some sufficient buffer for mounts */ 1069 | mdata.reserve(8192); 1070 | /* find if source is already mounted */ 1071 | auto ism = is_mounted(mfd, asrc.data(), to, mdata); 1072 | if (ism > 0) { 1073 | if (do_mount_raw(to, asrc.data(), type, flags, iflags, eopts)) { 1074 | return 1; 1075 | } 1076 | /* a successful mount means that mounts did change and we 1077 | * should definitely receive at least one POLLPRI on the fd 1078 | */ 1079 | } else if (ism < 0) { 1080 | return 1; 1081 | } else { 1082 | /* monitor the existing mount */ 1083 | } 1084 | for (;;) { 1085 | auto pret = poll(pfd, 2, -1); 1086 | if (pret < 0) { 1087 | if (errno == EINTR) { 1088 | continue; 1089 | } 1090 | warn("poll failed"); 1091 | return 1; 1092 | } 1093 | if (pfd[0].revents & POLLIN) { 1094 | int sign; 1095 | if (read(pfd[0].fd, &sign, sizeof(sign)) != sizeof(sign)) { 1096 | warn("signal read failed"); 1097 | return 1; 1098 | } 1099 | /* received a termination signal, so unmount and quit */ 1100 | for (;;) { 1101 | ism = is_mounted(mfd, asrc.data(), to, mdata); 1102 | if (ism < 0) { 1103 | return 1; 1104 | } else if (ism > 0) { 1105 | return 0; 1106 | } 1107 | if (umount2(to, MNT_DETACH) < 0) { 1108 | warn("umount failed"); 1109 | return 1; 1110 | } 1111 | } 1112 | // do unmount 1113 | return 0; 1114 | } 1115 | if (pfd[1].revents & POLLPRI) { 1116 | ism = is_mounted(mfd, asrc.data(), to, mdata); 1117 | if (ism > 0) { 1118 | /* mount disappeared, exit */ 1119 | warnx("mount '%s' has vanished", to); 1120 | return 1; 1121 | } else if (ism < 0) { 1122 | return 1; 1123 | } else { 1124 | /* mount is ok... */ 1125 | continue; 1126 | } 1127 | } 1128 | } 1129 | return 0; 1130 | } 1131 | 1132 | int main(int argc, char **argv) { 1133 | char *rsl = std::strrchr(argv[0], '/'); 1134 | if (rsl && !std::strcmp(rsl + 1, "mnt-service")) { 1135 | return do_supervise(argc, argv); 1136 | } 1137 | 1138 | if (argc < 2) { 1139 | errx(1, "not enough arguments"); 1140 | } 1141 | 1142 | if (!std::strcmp(argv[1], "is")) { 1143 | if (argc != 3) { 1144 | errx(1, "incorrect number of arguments"); 1145 | } 1146 | return do_is(argv[2]); 1147 | } else if (!std::strcmp(argv[1], "supervise")) { 1148 | return do_supervise(argc - 1, &argv[1]); 1149 | } else if (!std::strcmp(argv[1], "prepare")) { 1150 | if (argc != 3) { 1151 | errx(1, "incorrect number of arguments"); 1152 | } 1153 | return do_prepare(argv[2]); 1154 | } else if (!std::strcmp(argv[1], "root-rw")) { 1155 | if (argc != 2) { 1156 | errx(1, "incorrect number of arguments"); 1157 | } 1158 | return do_root_rw(); 1159 | } else if (!std::strcmp(argv[1], "try")) { 1160 | if ((argc < 5) || (argc > 6)) { 1161 | errx(1, "incorrect number of arguments"); 1162 | } 1163 | return do_try(argv[2], argv[3], argv[4], (argc < 6) ? nullptr : argv[5]); 1164 | } else if (!std::strcmp(argv[1], "mnt")) { 1165 | if ((argc < 5) || (argc > 6)) { 1166 | errx(1, "incorrect number of arguments"); 1167 | } 1168 | return do_mount(argv[2], argv[3], argv[4], (argc < 6) ? nullptr : argv[5]); 1169 | } else if (!std::strcmp(argv[1], "umnt")) { 1170 | if ((argc < 3) || (argc > 4)) { 1171 | errx(1, "incorrect number of arguments"); 1172 | } 1173 | return do_umount(argv[2], (argc < 4) ? nullptr : argv[3]); 1174 | } else if (!std::strcmp(argv[1], "rmnt")) { 1175 | if (argc != 4) { 1176 | errx(1, "incorrect number of arguments"); 1177 | } 1178 | return do_remount(argv[2], argv[3]); 1179 | } else if (!std::strcmp(argv[1], "getent")) { 1180 | if (argc != 5) { 1181 | errx(1, "incorrect number of arguments"); 1182 | } 1183 | return do_getent(argv[2], argv[3], argv[4]); 1184 | } 1185 | 1186 | warnx("unknown command '%s'", argv[1]); 1187 | return 1; 1188 | } 1189 | --------------------------------------------------------------------------------