This patch use Linux kernel cgroups to manage daemons. No controler is used. Cgroups are only used to track launched process and prevent escape. Notable changes: ---------------- - /run/daemons have switched to /sys/fs/cgroups/initscripts/daemons We now embded running dameon information into cgroup filesystem directly. No need to maintain both hierarchies. - DAEMONS array in rc.conf understand a new modifier: - Some daemons can be launched out of its cgroup container. This will avoid stopping it remove all its children (e.g: sshd) - rc scripts must be launched with rc.d tool. Launching daemon directly from /etc/rc.d/x inherits current environment and it may differ from environment at startup and leads to errors. Here is one stone hits two. We clean environment and we use this to auto cgroup launched daemons without need to update each rc script. - hooks calling directly rc scripts will fail (e.g: sshd). They must be fixed to use proper initscripts functions (run_daemon ssh stop). - rc.d tools now handle same syntax as DAEMONS array. By example, rc.d start @sshd or rc.d start -- -sshd This patch should be fully backward compatible with existant rc.d scripts but not with hooks. --- Makefile | 2 - bash-completion | 4 +- functions | 123 +++++++++++++++++++++++++++++++++++++++++++------------ rc.conf.5.txt | 1 + rc.d | 49 +++++++++++----------- rc.d.8.txt | 6 +++ rc.multi | 6 +-- rc.single | 2 +- rc.sysinit | 7 +++- tmpfiles.conf | 5 --- zsh-completion | 4 +- 11 files changed, 139 insertions(+), 70 deletions(-) delete mode 100644 tmpfiles.conf diff --git a/Makefile b/Makefile index 825eb11..cbde5b2 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,6 @@ DIRS := \ /etc/rc.d/functions.d \ /etc/logrotate.d \ /etc/profile.d \ - /usr/lib/tmpfiles.d \ /usr/sbin \ /usr/share/bash-completion/completions \ /usr/share/zsh/site-functions \ @@ -28,7 +27,6 @@ install: installdirs doc install -m755 -t $(DESTDIR)/usr/sbin rc.d install -m644 -t $(DESTDIR)/usr/share/man/man5 rc.conf.5 install -m644 -t $(DESTDIR)/usr/share/man/man8 rc.d.8 - install -m644 tmpfiles.conf $(DESTDIR)/usr/lib/tmpfiles.d/initscripts.conf install -m644 -T bash-completion $(DESTDIR)/usr/share/bash-completion/completions/rc.d install -m644 -T zsh-completion $(DESTDIR)/usr/share/zsh/site-functions/_rc.d diff --git a/bash-completion b/bash-completion index 4b4593b..dd8b006 100644 --- a/bash-completion +++ b/bash-completion @@ -12,9 +12,9 @@ _rc_d() elif [[ "$arg" == help ]]; then COMPREPLY=() elif [[ "$arg" == start ]]; then - COMPREPLY=($(comm -23 <(cd /etc/rc.d && compgen -f -X 'functions*' "$cur"|sort) <(cd /run/daemons/ && compgen -f "$cur"|sort))) + COMPREPLY=($(comm -23 <(cd /etc/rc.d && compgen -f -X 'functions*' "$cur"|sort) <(cd /sys/fs/cgroup/initscripts/daemons/ && compgen -d "$cur"|sort))) elif [[ "$arg" =~ stop|restart|reload ]]; then - COMPREPLY=($(cd /run/daemons/ && compgen -f "$cur"|sort)) + COMPREPLY=($(cd /sys/fs/cgroup/initscripts/daemons/ && compgen -d "$cur"|sort)) else COMPREPLY=($(compgen -W "${options} $(cd /etc/rc.d && compgen -f -X 'functions*')" -- "$cur")) fi diff --git a/functions b/functions index fd52317..0669810 100644 --- a/functions +++ b/functions @@ -11,14 +11,6 @@ localevars=(LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY vconsolevars=(KEYMAP KEYMAP_TOGGLE FONT FONT_MAP FONT_UNIMAP) -if [[ $1 == "start" ]]; then - if [[ $STARTING ]]; then - echo "A daemon is starting another daemon, this is unlikely to work as intended." - else - export STARTING=1 - fi -fi - # width: calc_columns () { STAT_COL=80 @@ -198,44 +190,116 @@ in_array() { # daemons: +# Deprecated function used by daemon scripts to tell a daemon is running add_daemon() { - [[ -d /run/daemons ]] || mkdir -p /run/daemons - >| /run/daemons/"$1" + true } +# Deprecated function used by daemon scripts to tell a daemon is no more running rm_daemon() { - rm -f /run/daemons/"$1" + true +} + +# Return array with daemon name and prefix in fixed order +# $1 daemon name with prefix +# return ('daemon' '@' '-' '!') +strip_daemon() { + local name=$1 + while (( ${#name} > 0 )); do + case ${name:0:1} in + '!'|'@'|'-') name=${name:1};; + *) break;; + esac + done + echo $name } +# Return 1 if daemon $1 is running, else 0 +# Daemons started without running process return 1 +# Keep check on /run/daemons/$1 (useful until first user reboot) ck_daemon() { - [[ ! -f /run/daemons/$1 ]] + [[ ! -d /sys/fs/cgroup/initscripts/daemons/$1 && ! -f /run/daemons/$1 ]] } -# Check if $1 is a valid daemon name +# Return 0 if $1 is a valid daemon name have_daemon() { [[ -f /etc/rc.d/$1 && -x /etc/rc.d/$1 ]] } # Check if $1 is started at boot +# Return 1 if autostarted, otherwise 0 ck_autostart() { local daemon for daemon in "${DAEMONS[@]}"; do - [[ $1 = "${daemon#@}" ]] && return 1 + [[ $daemon =~ ^[@-]*${1}$ ]] && return 1 done return 0 } -start_daemon() { - have_daemon "$1" && /etc/rc.d/"$1" start +# Run action $2 for daemon $1 +# This function understand prefixes: ! @ - +run_daemon() { + local background=0 + local cgroup=1 + local name=$1 + while (( ${#name} > 0 )); do + case ${name:0:1} in + '!') return 1;; + '@') background=1;; + '-') cgroup=0;; + *) break;; + esac + name=${name:1} + done + have_daemon "$name" || return 1 + # start action register daemon + if [[ $2 == start ]]; then + if [[ -d /sys/fs/cgroup/initscripts/daemons ]]; then + mkdir -p -m755 /sys/fs/cgroup/initscripts/daemons/"$name" + elif [[ -d /run/daemons ]]; then + >| /run/daemons/"$name" + fi + fi + # daemon script stuff + ( + # use cgroup jail + (( $cgroup )) && [[ -w "/sys/fs/cgroup/initscripts/daemons/$name/tasks" ]] && + echo $BASHPID > "/sys/fs/cgroup/initscripts/daemons/$name/tasks" + # execute script + if (( $background )); then + stat_bkgd "${2^} $name" + exec_daemon /etc/rc.d/"$name" "$2" >/dev/null & + else + exec_daemon /etc/rc.d/"$name" "$2" + fi + ) + # stop action unregister and cleanup + if [[ $2 == stop ]]; then + kill_daemon "$1" + rmdir "/sys/fs/cgroup/initscripts/daemons/$1" 2>/dev/null + # remove legacy file (useful until first user reboot) + rm -f "/run/daemons/$1" 2>/dev/null + fi } -start_daemon_bkgd() { - stat_bkgd "Starting $1" - (start_daemon "$1") >/dev/null & +# Execute a daemon script code properly +# This means with a clean environment and under the root directory +exec_daemon() { + ENV=('PATH=/bin:/usr/bin:/sbin:/usr/sbin' + 'CONSOLE=/dev/console' + 'TERM=linux' + 'INITSCRIPTS_LAUNCHER=1') + cd / + exec env -i "${ENV[@]}" bash -l "$@" } -stop_daemon() { - have_daemon "$1" && /etc/rc.d/"$1" stop +# use cgroup to kill all process runned by dameon $1 +kill_daemon() { + [[ -r /sys/fs/cgroup/initscripts/daemons/$1/tasks ]] || return + d=$(< "/sys/fs/cgroup/initscripts/daemons/$1/tasks") + [[ -n $d ]] && { kill -15 $d &>/dev/null; sleep .25; } || return + d=$(< "/sys/fs/cgroup/initscripts/daemons/$1/tasks") + [[ -n $d ]] && kill -9 $d &>/dev/null } # Status functions @@ -277,15 +341,15 @@ add_omit_pids() { } # Stop all daemons -# This function should *never* ever perform any other actions beside calling stop_daemon()! +# This function should *never* ever perform any other actions beside stoping daemons # It might be used by a splash system etc. to get a list of daemons to be stopped. stop_all_daemons() { # Find daemons NOT in the DAEMONS array. Shut these down first local daemon - for daemon in /run/daemons/*; do - [[ -f $daemon ]] || continue + for daemon in /sys/fs/cgroup/initscripts/daemons/*; do + [[ -d $daemon ]] || continue daemon=${daemon##*/} - ck_autostart "$daemon" && stop_daemon "$daemon" + ck_autostart "$daemon" && run_daemon "$daemon" stop done # Shutdown daemons in reverse order @@ -293,7 +357,7 @@ stop_all_daemons() { for (( i=${#DAEMONS[@]}-1; i>=0; i-- )); do [[ ${DAEMONS[i]} = '!'* ]] && continue daemon=${DAEMONS[i]#@} - ck_daemon "$daemon" || stop_daemon "$daemon" + ck_daemon "$daemon" || run_daemon "$daemon" stop done } @@ -696,5 +760,12 @@ for f in /etc/rc.d/functions.d/*; do [[ -e $f ]] && . "$f" done +# initscripts must not be called directly +if [[ $0 =~ /etc/rc.d/* && -z $INITSCRIPTS_LAUNCHER ]]; then + printf "${C_FAIL}You have to run your initscript with rc.d tools${C_CLEAR}\n" >&2 + printf "e.g: ${C_MAIN}rc.d ${1:-start} ${0##*/}${C_CLEAR}\n" >&2 + exit 42 +fi + # End of file # vim: set ts=2 sw=2 noet: diff --git a/rc.conf.5.txt b/rc.conf.5.txt index 726ba23..a102f4c 100644 --- a/rc.conf.5.txt +++ b/rc.conf.5.txt @@ -210,6 +210,7 @@ DAEMONS[[D]] Daemons to start at boot-up (in this order) - prefix a daemon with a ! to disable it - prefix a daemon with a @ to start it up in the background + - prefix a daemon with a - to start it outside a cgroup If you are sure nothing else touches your hardware clock (such as ntpd or a dual-boot), you might want to enable 'hwclock'. Note that this will only diff --git a/rc.d b/rc.d index 77852d7..fad1e18 100755 --- a/rc.d +++ b/rc.d @@ -23,6 +23,8 @@ e.g: $name list $name list sshd gpm $name list --started gpm $name start sshd gpm + $name start -- -sshd + $name start -- @acpid -sshd gpm $name help EOF exit ${1:-1} @@ -30,8 +32,7 @@ EOF # filter list of daemons filter_daemons() { - local -a new_daemons=() - for daemon in "${daemons[@]}"; do + for daemon in "${!daemons[@]}"; do # check if daemons is valid if ! have_daemon "$daemon"; then printf "${C_FAIL}:: ${C_DONE}Daemon script ${C_FAIL}${daemon}${C_DONE} does \ @@ -39,13 +40,11 @@ not exist or is not executable.${C_CLEAR}\n" >&2 exit 2 fi # check filter - (( ${filter[started]} )) && ck_daemon "$daemon" && continue - (( ${filter[stopped]} )) && ! ck_daemon "$daemon" && continue - (( ${filter[auto]} )) && ck_autostart "$daemon" && continue - (( ${filter[noauto]} )) && ! ck_autostart "$daemon" && continue - new_daemons+=("$daemon") + (( ${filter[started]} )) && ck_daemon "$daemon" && unset daemons["$daemon"] && continue + (( ${filter[stopped]} )) && ! ck_daemon "$daemon" && unset daemons["$daemon"] && continue + (( ${filter[auto]} )) && ck_autostart "$daemon" && unset daemons["$daemon"] && continue + (( ${filter[noauto]} )) && ! ck_autostart "$daemon" && unset daemons["$daemon"] && continue done - daemons=("${new_daemons[@]}") } (( $# < 1 )) && usage @@ -53,7 +52,7 @@ not exist or is not executable.${C_CLEAR}\n" >&2 # ret store the return code of rc.d declare -i ret=0 # daemons store daemons on which action will be executed -declare -a daemons=() +declare -A daemons=() # filter store current filter mode declare -A filter=([started]=0 [stopped]=0 [auto]=0 [noauto]=0) @@ -80,7 +79,7 @@ shift # get initial daemons list for daemon; do - daemons+=("$daemon") + daemons[$(strip_daemon "$daemon")]=$daemon done # going into script directory @@ -91,10 +90,15 @@ case $action in usage 0 2>&1 ;; list) - # list take all daemons by default - [[ -z $daemons ]] && for d in *; do have_daemon "$d" && daemons+=("$d"); done - filter_daemons - for daemon in "${daemons[@]}"; do + # list take all daemons if not specified + if (( ${#daemons[*]} == 0)); then + for d in *; do + have_daemon "$d" && daemons[$(strip_daemon "$d")]=$daemon + done + fi + # use filters if needed + (( ${#filter[*]} > 0 )) && filter_daemons + for daemon in "${!daemons[@]}"; do # print running / stopped satus if ! ck_daemon "$daemon"; then s_status="${C_OTHER}[${C_DONE}STARTED${C_OTHER}]" @@ -112,18 +116,11 @@ case $action in ;; *) # other actions need an explicit daemons list - [[ -z $daemons ]] && usage - filter_daemons - # set same environment variables as init - runlevel=$(/sbin/runlevel) - ENV=('PATH=/bin:/usr/bin:/sbin:/usr/sbin' - "PREVLEVEL=${runlevel%% *}" - "RUNLEVEL=${runlevel##* }" - "CONSOLE=${CONSOLE:-/dev/console}" - "TERM=$TERM") - cd / - for daemon in "${daemons[@]}"; do - env -i "${ENV[@]}" "/etc/rc.d/$daemon" "$action" + (( ${#daemons[*]} == 0 )) && usage + # use filters if needed + (( ${#filter[*]} > 0 )) && filter_daemons + for daemon in "${!daemons[@]}"; do + run_daemon "${daemons[$daemon]}" "$action" (( ret += !! $? )) # clamp exit value to 0/1 done ;; diff --git a/rc.d.8.txt b/rc.d.8.txt index 0f35884..3f7e41f 100644 --- a/rc.d.8.txt +++ b/rc.d.8.txt @@ -72,6 +72,12 @@ Examples[[E]] *rc.d start sshd gpm*:: Starts *sshd* and *gpm* scripts. +*rc.d start -- -sshd*:: + Starts *sshd* without cgroup jaling. + +*rc.d start -- @-sshd @acpid @gpm*:: + Starts *sshd*, *acpid* and *gpm* in background. + *rc.d stop crond*:: Stops the *crond* script. diff --git a/rc.multi b/rc.multi index d558753..d747113 100755 --- a/rc.multi +++ b/rc.multi @@ -16,11 +16,7 @@ run_hook multi_start # Start daemons for daemon in "${DAEMONS[@]}"; do - case ${daemon:0:1} in - '!') continue;; # Skip this daemon. - '@') start_daemon_bkgd "${daemon#@}";; - *) start_daemon "$daemon";; - esac + run_daemon "$daemon" start done [[ -x /etc/rc.local ]] && /etc/rc.local diff --git a/rc.single b/rc.single index aec026c..ec343fe 100755 --- a/rc.single +++ b/rc.single @@ -20,7 +20,7 @@ if [[ $PREVLEVEL != N ]]; then # Start/trigger UDev, load MODULES and settle UDev udevd_modprobe single - + # Removing leftover files remove_leftover fi diff --git a/rc.sysinit b/rc.sysinit index 12339b6..bba57b5 100755 --- a/rc.sysinit +++ b/rc.sysinit @@ -12,7 +12,7 @@ printhl "${C_H2}http://www.archlinux.org" printsep # mount the api filesystems -# /proc, /sys, /run, /dev, /run/lock, /dev/pts, /dev/shm +# /proc, /sys, /run, /dev, /run/lock, /dev/pts, /dev/shm, /sys/fs/cgroup/initscripts mountpoint -q /proc || mount -t proc proc /proc -o nosuid,noexec,nodev mountpoint -q /sys || mount -t sysfs sys /sys -o nosuid,noexec,nodev mountpoint -q /run || mount -t tmpfs run /run -o mode=0755,nosuid,nodev @@ -25,6 +25,11 @@ mountpoint -q /dev/shm || mount /dev/shm &>/dev/null || mount -t tmpfs shm /dev/shm -o mode=1777,nosuid,nodev mountpoint -q /proc/sys/fs/binfmt_misc || mount /proc/sys/fs/binfmt_misc &>/dev/null || mount -t binfmt_misc binfmt /proc/sys/fs/binfmt_misc +mountpoint -q /sys/fs/cgroup || + mount -n -t tmpfs cgroup /sys/fs/cgroup -o mode=0755,nosuid,noexec,nodev +mkdir -p /sys/fs/cgroup/initscripts +mount -t cgroup -o none,name=initscripts cgroup /sys/fs/cgroup/initscripts +mkdir -p /sys/fs/cgroup/initscripts/daemons if [[ ! -e /run/initramfs/fsck-root ]]; then # remount root ro to allow for fsck later on, we remount now to diff --git a/tmpfiles.conf b/tmpfiles.conf deleted file mode 100644 index 8f99a99..0000000 --- a/tmpfiles.conf +++ /dev/null @@ -1,5 +0,0 @@ -# -# /usr/lib/tmpfiles.d/arch.conf -# - -d /run/daemons 0755 root root - diff --git a/zsh-completion b/zsh-completion index d860e51..64cbc2a 100644 --- a/zsh-completion +++ b/zsh-completion @@ -18,10 +18,10 @@ _rc.d () { _arguments "*: :" ;; start) - _arguments "*: :($(comm -23 <(echo /etc/rc.d/*(N-*:t)|tr ' ' '\n') <(echo /run/daemons/*(N:t)|tr ' ' '\n')))" + _arguments "*: :($(comm -23 <(echo /etc/rc.d/*(N-*:t)|tr ' ' '\n') <(echo /sys/fs/cgroup/initscripts/daemons/*(N/:t)|tr ' ' '\n')))" ;; stop|restart|reload) - _arguments "*: :(/run/daemons/*(N:t))" + _arguments "*: :(/sys/fs/cgroup/initscripts/daemons/*(N/:t))" ;; *) _arguments "*: :(/etc/rc.d/*(N-*:t))" -- Sebastien "Seblu" Luttringer