[arch-projects] [INITSCRIPTS][PATCH 6/6] Introduce cgroups
Sébastien Luttringer
seblu at seblu.net
Mon Jun 11 17:10:40 EDT 2012
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
More information about the arch-projects
mailing list