[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