#!/sbin/openrc-run
# Copyright (c) 2017 The OpenRC Authors.
# See the Authors file at the top-level directory of this distribution and
# https://github.com/OpenRC/openrc/blob/HEAD/AUTHORS
#
# This file is part of OpenRC. It is subject to the license terms in
# the LICENSE file found in the top-level directory of this
# distribution and at https://github.com/OpenRC/openrc/blob/HEAD/LICENSE
# This file may not be copied, modified, propagated, or distributed
# except according to the terms contained in the LICENSE file.

description="Mount the control groups."

: "${cgroup_opts:="nodev,noexec,nosuid"}"

depend()
{
	keyword -docker -podman -prefix -systemd-nspawn -vserver -wsl
	after sysfs
}

cgroup1_base()
{
	grep -qw cgroup /proc/filesystems || return 0
	if ! mountinfo -q /sys/fs/cgroup; then
		ebegin "Mounting cgroup filesystem"
		local opts="${cgroup_opts},mode=755,size=${rc_cgroupsize:-10m}"
		mount -n -t tmpfs -o "${opts}" cgroup_root /sys/fs/cgroup
		eend $?
	fi

	if ! mountinfo -q /sys/fs/cgroup/openrc; then
		local agent="${RC_LIBEXECDIR}/sh/cgroup-release-agent.sh"
		mkdir /sys/fs/cgroup/openrc
		mount -n -t cgroup \
			-o none,${cgroup_opts},name=openrc,release_agent="$agent" \
			openrc /sys/fs/cgroup/openrc
		printf 1 > /sys/fs/cgroup/openrc/notify_on_release
	fi
	return 0
}

cgroup1_controllers()
{
	yesno "${rc_controller_cgroups:-YES}" && [ -e /proc/cgroups ] &&
	grep -qw cgroup /proc/filesystems || return 0
	while read -r name _ _ enabled _; do
		case "${enabled}" in
			1)	mountinfo -q "/sys/fs/cgroup/${name}" && continue
				local controller
				for controller in ${rc_cgroup_controllers}; do
					[ "${name}" = "blkio" ] && [ "${controller}" = "io" ] &&
						continue 2
					[ "${name}" = "${controller}" ] &&
						continue 2
				done
				mkdir "/sys/fs/cgroup/${name}"
				mount -n -t cgroup -o "${cgroup_opts},${name}" \
					"${name}" "/sys/fs/cgroup/${name}"
				yesno "${rc_cgroup_memory_use_hierarchy:-no}" &&
					[ "${name}" = memory ] &&
					echo 1 > /sys/fs/cgroup/memory/memory.use_hierarchy
				;;
		esac
	done < /proc/cgroups
	return 0
}

cgroup2_base()
{
	grep -qw cgroup2 /proc/filesystems || return 0
	local cgroup_path
	cgroup_path="$(cgroup2_find_path)"
	mkdir -p "${cgroup_path}"
	if ! mountinfo -q -f '^cgroup2$' "${cgroup_path}"; then
		mount -t cgroup2 cgroup2 -o "${cgroup_opts},nsdelegate" "${cgroup_path}" 2> /dev/null ||
			mount -t cgroup2 cgroup2 -o "${cgroup_opts}" "${cgroup_path}"
	fi
	mountinfo -q -f '^cgroup2$' "${cgroup_path}" &&
		mkdir -p "${cgroup_path}/openrc"
	return 0
}

cgroup2_process_is_kthread()
{
	local flags line pid
	pid="$1"

	[ -r "/proc/${pid}/stat" ] || return 1
	read -r line < "/proc/${pid}/stat" || return 1
	line="${line##*) }"
	set -- ${line}
	# After removing pid and comm, /proc/pid/stat field 9 (flags) is $7
	flags="$7"
	[ -n "${flags}" ] || return 1

	# PF_KTHREAD is 0x00200000 = 2097152 in decimal
	[ $(( flags & 2097152 )) -ne 0 ]
}

cgroup2_move_init_procs()
{
	grep -qw cgroup2 /proc/filesystems || return 0
	local cgroup_path init_cgroup pid pids
	cgroup_path="$(cgroup2_find_path)"
	[ -z "${cgroup_path}" ] && return 0
	[ ! -e "${cgroup_path}/cgroup.procs" ] && return 0
	init_cgroup="${cgroup_path}/openrc"
	[ ! -e "${init_cgroup}/cgroup.procs" ] && return 0
	pids=
	while read -r pid; do
		[ -n "${pid}" ] && pids="${pids} ${pid}"
	done < "${cgroup_path}/cgroup.procs"
	for pid in ${pids}; do
		[ -d "/proc/${pid}" ] || continue
		cgroup2_process_is_kthread "${pid}" && continue
		printf "%s" "${pid}" > "${init_cgroup}/cgroup.procs"
	done
	return 0
}

cgroup2_controllers()
{
	grep -qw cgroup2 /proc/filesystems || return 0
	local cgroup_path controller controllers rc_controller
	cgroup_path="$(cgroup2_find_path)"
	[ -z "${cgroup_path}" ] && return 0
	[ ! -e "${cgroup_path}/cgroup.controllers" ] && return 0
	[ ! -e "${cgroup_path}/cgroup.subtree_control" ] && return 0
	read -r controllers < "${cgroup_path}/cgroup.controllers"
	for controller in ${controllers}; do
	case "${rc_cgroup_mode:-unified}" in
		unified)
			echo "+${controller}" > "${cgroup_path}/cgroup.subtree_control"
			;;
		hybrid)
			for rc_controller in ${rc_cgroup_controllers}; do
				if [ "${controller}" = "${rc_controller}" ]; then
					echo "+${controller}" > "${cgroup_path}/cgroup.subtree_control"
				fi
			done
			;;
		esac
	done
	return 0
}

cgroups_hybrid()
{
	cgroup1_base
	cgroup2_base
	cgroup2_move_init_procs
	cgroup2_controllers
	cgroup1_controllers
	return 0
}

cgroups_legacy()
{
	cgroup1_base
	cgroup1_controllers
	return 0
}

cgroups_unified()
{
	cgroup2_base
	cgroup2_move_init_procs
	cgroup2_controllers
	return 0
}

mount_cgroups()
{
	case "${rc_cgroup_mode:-unified}" in
	hybrid) cgroups_hybrid ;;
	legacy) cgroups_legacy ;;
	unified) cgroups_unified ;;
	esac
	return 0
}

restorecon_cgroups()
{
	if [ -x /sbin/restorecon ]; then
		ebegin "Restoring SELinux contexts in /sys/fs/cgroup"
		restorecon -rF /sys/fs/cgroup >/dev/null 2>&1
		eend $?
	fi
	return 0
}

start()
{
	# set up kernel support for cgroups
	if [ -d /sys/fs/cgroup ]; then
		mount_cgroups
		restorecon_cgroups
	fi
	return 0
}
