#!/bin/bash # # Set up the control group hierarchy. # # Copyright IBM Corporation. 2008 # # Authors: Balbir Singh # This program is free software; you can redistribute it and/or modify it # under the terms of version 2.1 of the GNU Lesser General Public License # as published by the Free Software Foundation. # # This program is distributed in the hope that it would be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # cgconfig Control Groups Configuration Startup # chkconfig: - 5 95 # description: Sets up the control group filesystem. On the unified (cgroup # v2) hierarchy it mounts /sys/fs/cgroup, makes the available # controllers usable by child cgroups and delegates a subtree to # each user. If /etc/cgconfig.conf declares v1 mount points it # falls back to running cgconfigparser on it instead. ### BEGIN INIT INFO # Short-Description: Create and setup control group filesystem(s) # Description: Create and setup control group filesystem(s) # Should-Start: ypbind # Should-Stop: ypbind PIDFILE=/var/run/${servicename} RUNLEVEL=S NEEDS="+local_fs +syslog" ### END INIT INFO . /etc/init.d/smgl_init # get correct location of binaries from configure prefix=/usr/;exec_prefix=${prefix};sbindir=${exec_prefix}/sbin servicename=cgconfig # auto_init insists PROGRAM be executable, and the unified hierarchy needs # nothing from libcgroup itself; cgconfigparser is only used for the v1 path. PROGRAM=/bin/mount CGCONFIGPARSER=$sbindir/cgconfigparser CONFIG_FILE=/etc/cgconfig.conf lockfile=/var/lock/${servicename} # read the config CREATE_DEFAULT=yes CGROUP_ROOT=/sys/fs/cgroup CGROUP_MOUNT_OPTIONS=nsdelegate,memory_recursiveprot # Controllers to hand down to child cgroups; empty means every controller the # kernel offers. CGROUP_CONTROLLERS= # Parent of the per-user delegated subtrees. USER_SLICE=user.slice # Delegate a subtree to every user in this UID range, plus DELEGATE_USERS. DELEGATE=yes DELEGATE_UID_MIN=1000 DELEGATE_UID_MAX=60000 DELEGATE_USERS= if [ -e /etc/sysconfig/cgconfig ]; then . /etc/sysconfig/cgconfig fi # # cgroup v2 (unified hierarchy) # # A controller belongs to exactly one hierarchy, so v1 and v2 cannot both be # used for it; whichever is mounted first wins. This is why the v1 path below # is only taken when the admin has explicitly asked for it. # cgroup_fstype() { awk -v root="$CGROUP_ROOT" '$2 == root { type = $3 } END { print type }' \ /proc/self/mounts } # True when /etc/cgconfig.conf asks for v1 mount points, i.e. when the admin # has deliberately configured the legacy hierarchies. wants_cgroup_v1() { [ -s "$CONFIG_FILE" ] && grep -q '^[[:space:]]*mount[[:space:]]*{' "$CONFIG_FILE" } mount_cgroup2() { case "$(cgroup_fstype)" in (cgroup2) return 0 ;; (cgroup) print_status warning "$CGROUP_ROOT holds a cgroup v1 hierarchy" echo "$servicename: replace the $CGROUP_ROOT line in /etc/fstab with" echo "$servicename: none $CGROUP_ROOT cgroup2 $CGROUP_MOUNT_OPTIONS 0 0" echo "$servicename: the controllers cannot be handed to v2 while v1 holds them" return 1 ;; esac mkdir -p "$CGROUP_ROOT" && mount -t cgroup2 -o "$CGROUP_MOUNT_OPTIONS" cgroup2 "$CGROUP_ROOT" } # A cgroup can only offer a controller to its children after listing it in # cgroup.subtree_control, and it can only offer what its own parent gave it in # cgroup.controllers. Enable them one at a time so that one controller the # kernel refuses does not take the rest down with it. enable_controllers() { local cg=$1 avail want c avail=$(cat "$cg/cgroup.controllers" 2>/dev/null) || return 0 want=${CGROUP_CONTROLLERS:-$avail} for c in $want; do case " $avail " in (*" $c "*) echo "+$c" > "$cg/cgroup.subtree_control" 2>/dev/null ;; esac done return 0 } # Delegation hands a subtree to an unprivileged user. The kernel checks the # owner of the directory itself, of cgroup.procs and cgroup.threads (moving a # process) and of cgroup.subtree_control (handing controllers further down); # nothing else must become writable, or the user could escape the subtree. delegate_cgroup() { local cg=$1 owner=$2 f mkdir -p "$cg" || return 1 chown "$owner" "$cg" || return 1 for f in cgroup.procs cgroup.threads cgroup.subtree_control; do if [ -e "$cg/$f" ]; then chown "$owner" "$cg/$f" || return 1 fi done return 0 } delegated_users() { awk -F: -v min="$DELEGATE_UID_MIN" -v max="$DELEGATE_UID_MAX" \ '$3 >= min && $3 <= max { print $3 }' /etc/passwd local user uid for user in $DELEGATE_USERS; do uid=$(id -u "$user" 2>/dev/null) && echo "$uid" done } setup_user_slices() { local slice=$CGROUP_ROOT/$USER_SLICE uid mkdir -p "$slice" || return 1 # user.slice itself never holds processes - sessions go into a scope # below their own user-.slice - so it is free to pass controllers # down. enable_controllers "$slice" for uid in $(delegated_users | sort -un); do delegate_cgroup "$slice/user-$uid.slice" "$uid" || print_status warning "could not delegate user-$uid.slice" done return 0 } start_v2() { mount_cgroup2 || return 1 # The root cgroup is the only one exempt from the "no internal # processes" rule, so it can keep the boot-time processes and still # pass controllers down. enable_controllers "$CGROUP_ROOT" if [ "$DELEGATE" = "yes" ]; then setup_user_slices || return 1 fi print_status success return 0 } stop_v2() { local slice=$CGROUP_ROOT/$USER_SLICE cg # rmdir on a cgroup that still holds processes fails, so this only # reaps the ones that are genuinely finished. for cg in "$slice"/*/*/ "$slice"/*/ "$slice"/; do [ -d "$cg" ] && rmdir "$cg" 2>/dev/null done return 0 } # # cgroup v1 (legacy, driven by /etc/cgconfig.conf) # create_default_groups() { defaultcgroup= if [ -f /etc/cgrules.conf ]; then read user ctrl defaultcgroup <<< \ $(grep -m1 '^\*[[:space:]]\+' /etc/cgrules.conf) if [ -n "$defaultcgroup" -a "$defaultcgroup" = "*" ]; then print_status warning "/etc/cgrules.conf incorrect" print_status warning "Overriding it" defaultcgroup= fi fi if [ -z $defaultcgroup ] then defaultcgroup=sysdefault/ fi # # Find all mounted subsystems and create comma-separated list # of controllers. # controllers=`lssubsys 2>/dev/null | tr '\n' ',' | sed s/.$//` # # Create the default group, ignore errors when the default group # already exists. # cgcreate -f 664 -d 775 -g $controllers:$defaultcgroup 2>/dev/null # # special rule for cpusets # if echo $controllers | grep -q -w cpuset; then cpus=`cgget -nv -r cpuset.cpus /` cgset -r cpuset.cpus=$cpus $defaultcgroup mems=`cgget -nv -r cpuset.mems /` cgset -r cpuset.mems=$mems $defaultcgroup fi # # Classify everything to default cgroup. Ignore errors, some processes # may exit after ps is run and before cgclassify moves them. # cgclassify -g $controllers:$defaultcgroup `ps --no-headers -eL o tid` \ 2>/dev/null || : } start_v1() { if [ -f "$lockfile" ] && lscgroup > /dev/null 2>&1; then print_status warning "cgroups are already mounted" return 0 fi && $CGCONFIGPARSER -l $CONFIG_FILE evaluate_retval || ( print_status failure "Failed to parse $CONFIG_FILE" && return 1 ) if [ $CREATE_DEFAULT = "yes" ]; then create_default_groups fi touch "$lockfile" || ( print_status failure "Failed to touch $lockfile" && return 1 ) } stop_v1() { cgclear -l $CONFIG_FILE rm -f "$lockfile" evaluate_retval } # # init script entry points # start() { echo -n "Starting cgconfig service: " if wants_cgroup_v1; then start_v1 else start_v2 fi } stop() { echo -n "Stopping cgconfig service: " if [ "$(cgroup_fstype)" = cgroup2 ]; then stop_v2 print_status success else stop_v1 fi } status() { case "$(cgroup_fstype)" in (cgroup2) print_status success "${servicename}: unified hierarchy on $CGROUP_ROOT" ;; (cgroup) print_status success "${servicename}: legacy v1 hierarchy on $CGROUP_ROOT" ;; (*) print_status failure "${servicename} not started" ;; esac return 0 } restart() { stop sleep 1 start } .