Hello everyone,

I've made a PR on github to fix the user/group creation when using a
ROOT different than /.

This is the link of the PR: https://github.com/gentoo/gentoo/pull/19204

Could you please take a moment to take a look at it ?

To test it, you need to create a new minimalist ROOT with:

$ export ROOT=/tmp/test-root
$ export SYSROOT=/tmp/sysroot
$ mkdir -p ${ROOT}/etc/portage
$ cp {,${ROOT}}/etc/portage/make.conf
$ ln -s /var/db/repos/gentoo/profiles/default/linux/<arch>/<profile>
${ROOT}/etc/portage/make.profile
$ emerge baselayout coreutils

Once it is done, you can emerge a new user for this ROOT, with this
patch, the user will be created in ROOT, without the user will be
installed on your system.

Note, this PR work with other arch too.

Thanks for your attention

--
Jérémy
diff --git a/eclass/acct-user.eclass b/eclass/acct-user.eclass
index ee4358b5c75..efa637a7efa 100644
--- a/eclass/acct-user.eclass
+++ b/eclass/acct-user.eclass
@@ -194,8 +194,15 @@ eislocked() {
 	*)
 		# NB: 'no password' and 'locked' are indistinguishable
 		# but we also expire the account which is more clear
-		[[ $(getent shadow "$1" | cut -d: -f2) == '!'* ]] &&
-			[[ $(getent shadow "$1" | cut -d: -f8) == 1 ]]
+		local shadow
+		if [[ -n "${ROOT}" ]]; then
+			shadow=$(grep "^$1:" "${ROOT}/etc/shadow")
+		else
+			shadow=$(getent shadow "$1")
+		fi
+
+		[[ $( echo ${shadow} | cut -d: -f2) == '!'* ]] &&
+			[[ $(echo ${shadow} | cut -d: -f8) == 1 ]]
 		;;
 	esac
 }
@@ -222,14 +229,22 @@ elockuser() {
 	eislocked "$1"
 	[[ $? -eq 0 ]] && return 0
 
+	local opts
+	[[ -n ${ROOT} ]] && opts=( --prefix "${ROOT}" )
+
 	case ${CHOST} in
 	*-freebsd*|*-dragonfly*)
-		pw lock "$1" || die "Locking account $1 failed"
-		pw user mod "$1" -e 1 || die "Expiring account $1 failed"
+		pw lock "${opts[@]}" "$1" || die "Locking account $1 failed"
+		pw user mod "${opts[@]}" "$1" -e 1 || die "Expiring account $1 failed"
 		;;
 
 	*-netbsd*)
-		usermod -e 1 -C yes "$1" || die "Locking account $1 failed"
+		if [[ -n "${ROOT}" ]]; then
+			ewarn "NetBSD's usermod does not support --prefix <dir> option."
+			ewarn "Please use: usermod ${opts[@]} -e 1 -C yes \"$1\" in a chroot"
+		else
+			usermod "${opts[@]}" -e 1 -C yes "$1" || die "Locking account $1 failed"
+		fi
 		;;
 
 	*-openbsd*)
@@ -237,7 +252,7 @@ elockuser() {
 		;;
 
 	*)
-		usermod -e 1 -L "$1" || die "Locking account $1 failed"
+		usermod "${opts[@]}" -e 1 -L "$1" || die "Locking account $1 failed"
 		;;
 	esac
 
@@ -265,14 +280,22 @@ eunlockuser() {
 	eislocked "$1"
 	[[ $? -eq 1 ]] && return 0
 
+	local opts
+	[[ -n ${ROOT} ]] && opts=( --prefix "${ROOT}" )
+
 	case ${CHOST} in
 	*-freebsd*|*-dragonfly*)
-		pw user mod "$1" -e 0 || die "Unexpiring account $1 failed"
-		pw unlock "$1" || die "Unlocking account $1 failed"
+		pw user mod "${opts[@]}" "$1" -e 0 || die "Unexpiring account $1 failed"
+		pw unlock "${opts[@]}" "$1" || die "Unlocking account $1 failed"
 		;;
 
 	*-netbsd*)
-		usermod -e 0 -C no "$1" || die "Unlocking account $1 failed"
+		if [[ -n "${ROOT}" ]]; then
+			ewarn "NetBSD's usermod does not support --prefix <dir> option."
+			ewarn "Please use: \"usermod ${opts[@]} -e 0 -C no $1\" in a chroot"
+		else
+			usermod "${opts[@]}" -e 0 -C no "$1" || die "Unlocking account $1 failed"
+		fi
 		;;
 
 	*-openbsd*)
@@ -281,7 +304,7 @@ eunlockuser() {
 
 	*)
 		# silence warning if account does not have a password
-		usermod -e "" -U "$1" 2>/dev/null || die "Unlocking account $1 failed"
+		usermod "${opts[@]}" -e "" -U "$1" 2>/dev/null || die "Unlocking account $1 failed"
 		;;
 	esac
 
@@ -417,7 +440,13 @@ acct-user_pkg_preinst() {
 		# default ownership to user:group
 		if [[ -z ${_ACCT_USER_HOME_OWNER} ]]; then
 			local group_array=( ${_ACCT_USER_GROUPS} )
-			_ACCT_USER_HOME_OWNER=${ACCT_USER_NAME}:${group_array[0]}
+			if [[ -n "${ROOT}" ]]; then
+				local euid=$(egetent passwd ${ACCT_USER_NAME} | cut -d: -f3)
+				local egid=$(egetent passwd ${ACCT_USER_NAME} | cut -d: -f4)
+				_ACCT_USER_HOME_OWNER=${euid}:${egid}
+			else
+				_ACCT_USER_HOME_OWNER=${ACCT_USER_NAME}:${group_array[0]}
+			fi
 		fi
 		# Path might be missing due to INSTALL_MASK, etc.
 		# https://bugs.gentoo.org/691478
diff --git a/eclass/user-info.eclass b/eclass/user-info.eclass
index 15e9238ab44..5e2d3f70490 100644
--- a/eclass/user-info.eclass
+++ b/eclass/user-info.eclass
@@ -6,7 +6,6 @@
 # [email protected] (Linux)
 # Michał Górny <[email protected]> (NetBSD)
 # @BLURB: Read-only access to user and group information
-
 if [[ -z ${_USER_INFO_ECLASS} ]]; then
 _USER_INFO_ECLASS=1
 
@@ -17,6 +16,7 @@ _USER_INFO_ECLASS=1
 # dscl (Mac OS X 10.5), and pw (FreeBSD) used in enewuser()/enewgroup().
 #
 # Supported databases: group passwd
+# Warning: This function can be used only in pkg_* phases when ROOT is valid.
 egetent() {
 	local db=$1 key=$2
 
@@ -37,18 +37,31 @@ egetent() {
 		# lookup by uid/gid
 		local opts
 		if [[ ${key} == [[:digit:]]* ]] ; then
-			[[ ${db} == "user" ]] && opts="-u" || opts="-g"
+			[[ ${db} == "user" ]] && opts=( -u ) || opts=( -g )
 		fi
 
+		# Handle different ROOT
+		[[ -n ${ROOT} ]] && opts+=( -R "${ROOT}" )
+
 		pw show ${db} ${opts} "${key}" -q
 		;;
 	*-openbsd*)
-		grep "${key}:\*:" /etc/${db}
+		grep "${key}:\*:" "${EROOT}/etc/${db}"
 		;;
 	*)
-		# ignore nscd output if we're not running as root
-		type -p nscd >/dev/null && nscd -i "${db}" 2>/dev/null
-		getent "${db}" "${key}"
+		# getent does not support -R option, if we are working on a different
+		# ROOT than /, fallback to grep technique.
+		if [[ -z ${ROOT} ]]; then
+			# ignore nscd output if we're not running as root
+			type -p nscd >/dev/null && nscd -i "${db}" 2>/dev/null
+			getent "${db}" "${key}"
+		else
+			if [[ ${key} =~ ^[[:digit:]]+$ ]]; then
+				grep -E "^([^:]*:){2}${key}" "${ROOT}/etc/${db}"
+			else
+				grep "^${key}:" "${ROOT}/etc/${db}"
+			fi
+		fi
 		;;
 	esac
 }
@@ -145,7 +158,16 @@ egetgroups() {
 	[[ $# -eq 1 ]] || die "usage: egetgroups <user>"
 
 	local egroups_arr
-	read -r -a egroups_arr < <(id -G -n "$1")
+	if [[ -n "${ROOT}" ]]; then
+		local pgroup=$(egetent passwd "$1" | cut -d: -f1)
+		local sgroups=( $(grep -E ":([^:]*,)?$1(,[^:]*)?$" "${ROOT}/etc/group" | cut -d: -f1) )
+
+		# Remove primary group from list
+		sgroups=${sgroups#${pgroup}}
+		egroups_arr=( ${pgroup} ${sgroups[@]} )
+	else
+		read -r -a egroups_arr < <(id -G -n "$1")
+	fi
 
 	local g groups=${egroups_arr[0]}
 	# sort supplementary groups to make comparison possible
diff --git a/eclass/user.eclass b/eclass/user.eclass
index f8993b82774..a6bd3455a67 100644
--- a/eclass/user.eclass
+++ b/eclass/user.eclass
@@ -113,6 +113,9 @@ enewuser() {
 	# options to pass to useradd
 	local opts=()
 
+	# handle for ROOT != /
+	[[ -n ${ROOT} ]] && opts+=( --prefix "${ROOT}" )
+
 	# handle uid
 	local euid=$1; shift
 	if [[ -n ${euid} && ${euid} != -1 ]] ; then
@@ -203,13 +206,24 @@ enewuser() {
 		;;
 
 	*-netbsd*)
-		useradd "${opts[@]}" "${euser}" || die
+		if [[ -n "${ROOT}" ]]; then
+			ewarn "NetBSD's usermod does not support --prefix option."
+			ewarn "Please use: \"useradd ${opts[@]} ${euser}\" in a chroot"
+		else
+			useradd "${opts[@]}" "${euser}" || die
+		fi
 		;;
 
 	*-openbsd*)
-		# all ops the same, except the -g vs -g/-G ...
-		useradd -u ${euid} -s "${eshell}" \
-			-d "${ehome}" -g "${egroups}" "${euser}" || die
+		if [[ -n "${ROOT}" ]]; then
+			ewarn "OpenBSD's usermod does not support --prefix option."
+			ewarn "Please use: \"useradd ${opts[@]} ${euser}\" in a chroot"
+		else
+			# all ops the same, except the -g vs -g/-G ...
+			useradd -u ${euid} -s "${eshell}" \
+				-d "${ehome}" -g "${egroups}" "${euser}" || die
+		fi
+
 		;;
 
 	*)
@@ -220,6 +234,10 @@ enewuser() {
 	if [[ -n ${create_home} && ! -e ${ROOT}/${ehome} ]] ; then
 		elog " - Creating ${ehome} in ${ROOT}"
 		mkdir -p "${ROOT}/${ehome}"
+		# Use UID if we are in another ROOT than /
+		if [[ -n "${ROOT}" ]]; then
+			euser=$(egetent passwd ${euser} | cut -d: -f3)
+		fi
 		chown "${euser}" "${ROOT}/${ehome}"
 		chmod 755 "${ROOT}/${ehome}"
 	fi
@@ -282,6 +300,10 @@ enewgroup() {
 	fi
 	elog " - Groupid: ${egid}"
 
+	# handle different ROOT
+	local opts
+	[[ -n ${ROOT} ]] && opts=( --prefix "${ROOT}" )
+
 	# handle extra
 	if [[ $# -gt 0 ]] ; then
 		die "extra arguments no longer supported; please file a bug"
@@ -302,24 +324,29 @@ enewgroup() {
 	case ${CHOST} in
 	*-freebsd*|*-dragonfly*)
 		_enewgroup_next_gid
-		pw groupadd "${egroup}" -g ${egid} || die
+		pw groupadd "${opts[@]}" "${egroup}" -g ${egid} || die
 		;;
 
 	*-netbsd*)
-		_enewgroup_next_gid
-		groupadd -g ${egid} "${egroup}" || die
+		if [[ -n "${ROOT}" ]]; then
+			ewarn "NetBSD's usermod does not support --prefix <dir> option."
+			ewarn "Please use: \"groupadd -g ${egid} ${opts[@]} ${egroup}\" in a chroot"
+		else
+			_enewgroup_next_gid
+			groupadd -g ${egid} "${opts[@]}" "${egroup}" || die
+		fi
 		;;
 
 	*)
-		local opts
 		if [[ ${egid} == *[!0-9]* ]] ; then
 			# Non numeric; let groupadd figure out a GID for us
-			opts=""
+			#
+			true # Do nothing but keep the previous comment.
 		else
-			opts="-g ${egid}"
+			opts+=( -g ${egid} )
 		fi
 		# We specify -r so that we get a GID in the system range from login.defs
-		groupadd -r ${opts} "${egroup}" || die
+		groupadd -r "${opts[@]}" "${egroup}" || die
 		;;
 	esac
 }
@@ -349,6 +376,10 @@ esethome() {
 		return 1
 	fi
 
+	# Handle different ROOT
+	local opts
+	[[ -n ${ROOT} ]] && opts=( --prefix "${ROOT}" )
+
 	# handle homedir
 	local ehome=$1; shift
 	if [[ -z ${ehome} ]] ; then
@@ -379,15 +410,28 @@ esethome() {
 	# update the home directory
 	case ${CHOST} in
 	*-freebsd*|*-dragonfly*)
-		pw usermod "${euser}" -d "${ehome}" && return 0
+		pw usermod "${opts[@]}" "${euser}" -d "${ehome}" && return 0
 		[[ $? == 8 ]] && eerror "${euser} is in use, cannot update home"
 		eerror "There was an error when attempting to update the home directory for ${euser}"
 		eerror "Please update it manually on your system:"
 		eerror "\t pw usermod \"${euser}\" -d \"${ehome}\""
 		;;
 
+	*-netbsd*)
+		if [[ -n "${ROOT}" ]]; then
+			ewarn "NetBSD's usermod does not support --prefix <dir> option."
+			ewarn "Please use: \"usermod ${opts[@]} -d ${ehome} ${euser}\" in a chroot"
+		else
+			usermod "${opts[@]}" -d "${ehome}" "${euser}" && return 0
+			[[ $? == 8 ]] && eerror "${euser} is in use, cannot update home"
+			eerror "There was an error when attempting to update the home directory for ${euser}"
+			eerror "Please update it manually on your system (as root):"
+			eerror "\t usermod -d \"${ehome}\" \"${euser}\""
+		fi
+		;;
+
 	*)
-		usermod -d "${ehome}" "${euser}" && return 0
+		usermod "${opts[@]}" -d "${ehome}" "${euser}" && return 0
 		[[ $? == 8 ]] && eerror "${euser} is in use, cannot update home"
 		eerror "There was an error when attempting to update the home directory for ${euser}"
 		eerror "Please update it manually on your system (as root):"
@@ -418,6 +462,10 @@ esetshell() {
 		return 1
 	fi
 
+	# Handle different ROOT
+	local opts
+	[[ -n ${ROOT} ]] && opts=( --prefix "${ROOT}" )
+
 	# handle shell
 	local eshell=$1; shift
 	if [[ -z ${eshell} ]] ; then
@@ -440,15 +488,28 @@ esetshell() {
 	# update the shell
 	case ${CHOST} in
 	*-freebsd*|*-dragonfly*)
-		pw usermod "${euser}" -s "${eshell}" && return 0
+		pw usermod "${opts[@]}" "${euser}" -s "${eshell}" && return 0
 		[[ $? == 8 ]] && eerror "${euser} is in use, cannot update shell"
 		eerror "There was an error when attempting to update the shell for ${euser}"
 		eerror "Please update it manually on your system:"
 		eerror "\t pw usermod \"${euser}\" -s \"${eshell}\""
 		;;
 
+	*-netbsd*)
+		if [[ -n "${ROOT}" ]]; then
+			ewarn "NetBSD's usermod does not support --prefix <dir> option."
+			ewarn "Please use: \"usermod ${opts[@]} -s ${eshell} ${euser}\" in a chroot"
+		else
+			usermod "${opts[@]}" -s "${eshell}" "${euser}" && return 0
+			[[ $? == 8 ]] && eerror "${euser} is in use, cannot update shell"
+			eerror "There was an error when attempting to update the shell for ${euser}"
+			eerror "Please update it manually on your system (as root):"
+			eerror "\t usermod -s \"${eshell}\" \"${euser}\""
+		fi
+		;;
+
 	*)
-		usermod -s "${eshell}" "${euser}" && return 0
+		usermod "${opts[@]}" -s "${eshell}" "${euser}" && return 0
 		[[ $? == 8 ]] && eerror "${euser} is in use, cannot update shell"
 		eerror "There was an error when attempting to update the shell for ${euser}"
 		eerror "Please update it manually on your system (as root):"
@@ -478,6 +539,10 @@ esetcomment() {
 		return 1
 	fi
 
+	# Handle different ROOT
+	local opts
+	[[ -n ${ROOT} ]] && opts=( --prefix "${ROOT}" )
+
 	# handle comment
 	local ecomment=$1; shift
 	if [[ -z ${ecomment} ]] ; then
@@ -496,15 +561,28 @@ esetcomment() {
 	# update the comment
 	case ${CHOST} in
 	*-freebsd*|*-dragonfly*)
-		pw usermod "${euser}" -c "${ecomment}" && return 0
+		pw usermod "${opts[@]}" "${euser}" -c "${ecomment}" && return 0
 		[[ $? == 8 ]] && eerror "${euser} is in use, cannot update comment"
 		eerror "There was an error when attempting to update the comment for ${euser}"
 		eerror "Please update it manually on your system:"
 		eerror "\t pw usermod \"${euser}\" -c \"${ecomment}\""
 		;;
 
+	*-netbsd*)
+		if [[ -n "${ROOT}" ]]; then
+			ewarn "NetBSD's usermod does not support --prefix <dir> option."
+			ewarn "Please use: \"usermod ${opts[@]} -c ${ecomment} ${euser}\" in a chroot"
+		else
+			usermod "${opts[@]}" -c "${ecomment}" "${euser}" && return 0
+			[[ $? == 8 ]] && eerror "${euser} is in use, cannot update shell"
+			eerror "There was an error when attempting to update the shell for ${euser}"
+			eerror "Please update it manually on your system (as root):"
+			eerror "\t usermod -s \"${eshell}\" \"${euser}\""
+		fi
+		;;
+
 	*)
-		usermod -c "${ecomment}" "${euser}" && return 0
+		usermod "${opts[@]}" -c "${ecomment}" "${euser}" && return 0
 		[[ $? == 8 ]] && eerror "${euser} is in use, cannot update comment"
 		eerror "There was an error when attempting to update the comment for ${euser}"
 		eerror "Please update it manually on your system (as root):"
@@ -563,6 +641,9 @@ esetgroups() {
 	elog "Updating groups for user '${euser}' ..."
 	elog " - Groups: ${egroups}"
 
+	# Handle different ROOT
+	[[ -n ${ROOT} ]] && opts+=( --prefix "${ROOT}" )
+
 	# update the group
 	case ${CHOST} in
 	*-freebsd*|*-dragonfly*)
@@ -573,6 +654,19 @@ esetgroups() {
 		eerror "\t pw usermod \"${euser}\" ${opts[*]}"
 		;;
 
+	*-netbsd*)
+		if [[ -n "${ROOT}" ]]; then
+			ewarn "NetBSD's usermod does not support --prefix <dir> option."
+			ewarn "Please use: \"usermod ${opts[@]} ${euser}\" in a chroot"
+		else
+			usermod "${opts[@]}" "${euser}" && return 0
+			[[ $? == 8 ]] && eerror "${euser} is in use, cannot update shell"
+			eerror "There was an error when attempting to update the shell for ${euser}"
+			eerror "Please update it manually on your system (as root):"
+			eerror "\t usermod -s \"${eshell}\" \"${euser}\""
+		fi
+		;;
+
 	*)
 		usermod "${opts[@]}" "${euser}" && return 0
 		[[ $? == 8 ]] && eerror "${euser} is in use, cannot update groups"

Attachment: pgpxET9AiruhC.pgp
Description: OpenPGP digital signature

Reply via email to