#!/bin/sh
#
# Copyright (c) 2017 FUJITSU LIMITED. All rights reserved.
# Author: Xiao Yang <yangx.jy@cn.fujitsu.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
# the GNU General Public License for more details.
#
# Test unshare command with some basic options.
# 1) If we run unshare with "--user", UID in the newly created user namespace
#    is set to 65534.
# 2) If we run unshare with "--user", GID in the newly created user namespace
#    is set to 65534.
# 3) If we run with "--user --map-root-user", UID in the newly created user
#    namespace is set to 0.
# 4) If we run with "--user --map-root-user", GID in the newly created user
#    is set to 0.
# 5) If we run with "--mount", mount and unmount events do not propagate to
#    its parent mount namespace.
# 6) If we run with "--mount --propagation shared", mount and unmount events
#    propagate to its parent mount namespace.
# 7) If we run with "--user --map-root-user --mount", mount and unmount events
#    do not propagate to its parent mount namespace.
# 8) Even if we run with "--user --map-root-user --mount --propagation shared",
#    mount and unmount events do not propagate to its parent mount namespace
#    because the shared mount is reduced to a slave mount.
#
#    Please see the following URL for detailed information:
#    http://man7.org/linux/man-pages/man7/user_namespaces.7.html
#    http://man7.org/linux/man-pages/man7/mount_namespaces.7.html
#

TST_CNT=8
TST_SETUP=setup
TST_CLEANUP=cleanup
TST_TESTFUNC=do_test
TST_NEEDS_ROOT=1
TST_NEEDS_TMPDIR=1
TST_NEEDS_CMDS="unshare id mount umount"
. tst_test.sh

max_userns_path="/proc/sys/user/max_user_namespaces"
max_mntns_path="/proc/sys/user/max_mnt_namespaces"
default_max_userns=-1
default_max_mntns=-1

setup()
{
	# On some distributions(e.g RHEL7.4), the default value of
	# max_user_namespaces or max_mnt_namespaces is set to 0.
	# We need to change the default value to run unshare command.
	if [ -f "${max_userns_path}" ]; then
		default_max_userns=$(cat "${max_userns_path}")
		echo 1024 > "${max_userns_path}"
	fi

	if [ -f "${max_mntns_path}" ]; then
		default_max_mntns=$(cat "${max_mntns_path}")
		echo 1024 > "${max_mntns_path}"
	fi

	mkdir -p dir_A dir_B
	touch dir_A/A dir_B/B
}

cleanup()
{
	# Restore the default value to 0.
	[ ${default_max_userns} -ne -1 ] && \
		echo ${default_max_userns} > "${max_userns_path}"
	[ ${default_max_mntns} -ne -1 ] && \
		echo ${default_max_mntns} > "${max_mntns_path}"
}

check_id()
{
	local act_id="$1"
	local exp_id="$2"
	local cmd="$3"

	if [ ${act_id} -ne ${exp_id} ]; then
		tst_res TFAIL "$cmd got wrong uid/gid"
	else
		tst_res TPASS "$cmd got correct uid/gid"
	fi
}

check_mount()
{
	local tst_dir="$1"
	local exp_stat="$2"
	local cmd="$3"

	case ${exp_stat} in
	unmounted)
		if ls "${tst_dir}" | grep -qw 'A'; then
			tst_res TFAIL "$cmd got bind info"
			umount ${tst_dir}
			return
		fi
		;;
	mounted)
		if ! ls "${tst_dir}" | grep -qw 'A'; then
			tst_res TFAIL "$cmd did not get bind info"
			return
		fi
		umount ${tst_dir}
		;;
	esac

	tst_res TPASS "$cmd got bind info as expected"
}

unshare_test()
{
	local unshare_opts="$1"
	local verify_cmd="$2"
	local exp_result="$3"

	local unshare_cmd="unshare ${unshare_opts} ${verify_cmd}"

	eval ${unshare_cmd} > temp 2>&1
	if [ $? -ne 0 ]; then
		# unrecognized option or invalid option is returned if the
		# option is not supported by unshare command(e.g. RHEL6).
		# Invalid argument or Operation not permitted is returned
		# if the feature is not supported by kernel(e.g. RHEL7).
		grep -q -E "unrecognized option|invalid option|Invalid argument|Operation not permitted" temp
		if [ $? -eq 0 ]; then
			tst_res TCONF "${unshare_cmd} not supported."
		else
			tst_res TFAIL "${unshare_cmd} failed."
		fi
		return
	fi

	case ${verify_cmd} in
	id*)
		check_id "$(cat temp)" "${exp_result}" "${unshare_cmd}"
		;;
	mount*)
		check_mount "dir_B" "${exp_result}" "${unshare_cmd}"
		;;
	esac
}

do_test()
{
	case $1 in
	1) unshare_test "--user" "id -u" "65534";;
	2) unshare_test "--user" "id -g" "65534";;
	3) unshare_test "--user --map-root-user" "id -u" "0";;
	4) unshare_test "--user --map-root-user" "id -g" "0";;
	5) unshare_test "--mount" "mount --bind dir_A dir_B" "unmounted";;
	6) unshare_test "--mount --propagation shared" \
			"mount --bind dir_A dir_B" "mounted";;
	7) unshare_test "--user --map-root-user --mount" \
			"mount --bind dir_A dir_B" "unmounted";;
	8) unshare_test "--user --map-root-user --mount --propagation shared" \
			"mount --bind dir_A dir_B" "unmounted";;
	esac
}

tst_run