Subject: [PATCH] pkginstall framework modifications
To: None <tech-pkg@NetBSD.org>
From: Johnny C. Lam <jlam@pkgsrc.org>
List: tech-pkg
Date: 04/21/2006 18:19:04
--C7zPtVaVf+AK4Oqc
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

I've attached patch that adds two new abilities to the pkginstall
framework:

(1) Allow specifying the numeric UID and GID for users and groups in
    /etc/mk.conf by setting PKG_UID.<user> and PKG_GID.<group> to
    those values.  If these values are specified, then the +USERGROUP
    script will now verify that existing users and groups match the
    requested UIDs and GIDs for the package, and otherwise create them
    with these UIDs and GIDs.  For example:

	PKG_UID.courier=	10001
	PKG_GID.mail=		6

    In this example, the courier-authlib binary package will be created
    to use uid 10001 for the "courier" user and gid 6 for the "mail"
    group.

(2) Allow asking that users and groups be created prior to configuring
    or building a package by setting CREATE_USERGROUPS_PHASE to
    "configure" or "build".  Because the reason for this is typically
    to hardcode the UIDs and GIDs of requested users and groups directly
    into the package's executables, these hardcoded values will be
    automatically determined and put into the +USERGROUP script.  For
    example:

	CREATE_USERGROUPS_PHASE=	configure

	PKG_GROUPS=	qmail nofiles
	PKG_USERS+=     qmaill:nofiles
	PKG_USERS+=     qmailq:qmail

    In this example, the users and groups are created before the
    configure phase when building qmail, and the qmail binary package's
    +INSTALL script will try to create (or verify) users and groups
    with the same UIDs and GIDs that were used during the build.

As part of these changes, the format for PKG_USERS and PKG_GROUPS has
changed slightly -- the optional parts of the corresponding entries
are no longer used and cannot be specified.  Instead, the following
variables should be set:

	PKG_GID.<group> is the group's numeric GID.
	PKG_UID.<user> is the user's numeric UID.
	PKG_GECOS.<user> is the user's description.
	PKG_HOME.<user> is the user's home directory.
	PKG_SHELL.<user> is the user's login shell.

I have a separate patch (not included) which modifies all packages to
use the new variables instead of the old format.

Comments?

	Cheers,

	-- Johnny Lam <jlam@pkgsrc.org>

--C7zPtVaVf+AK4Oqc
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="usergroup.diff"

Index: doc/guide/files/pkginstall.xml
===================================================================
RCS file: /cvsroot/pkgsrc/doc/guide/files/pkginstall.xml,v
retrieving revision 1.10
diff -u -r1.10 pkginstall.xml
--- doc/guide/files/pkginstall.xml	12 Feb 2006 14:44:59 -0000	1.10
+++ doc/guide/files/pkginstall.xml	21 Apr 2006 17:50:58 -0000
@@ -426,33 +426,41 @@
 
 <para>Users can be created by adding entries to the
 <varname>PKG_USERS</varname> variable.  Each entry has the following
-syntax, which mimics <filename>/etc/passwd</filename>:</para>
+syntax:</para>
 
 <programlisting>
-    user:group[:[userid][:[descr][:[home][:shell]]]]
+    user:group
 </programlisting>
 
-<para>Only the user and group are required; everything else is optional,
-but the colons must be in the right places when specifying optional bits.
-By default, a new user will have home directory
-<filename>/nonexistent</filename>, and login shell
-<filename>/sbin/nologin</filename> unless they are specified as part of the
-user element.  Note that if the description contains spaces, then spaces
-should be backslash-escaped, as in:</para>
+<para>Further specification of user details may be done by setting per-user
+variables.
+<varname>PKG_UID.<replaceable>user</replaceable></varname> is the numeric
+UID for the user.
+<varname>PKG_GECOS.<replaceable>user</replaceable></varname> is the user's
+description or comment.
+<varname>PKG_HOME.<replaceable>user</replaceable></varname> is the user's
+home directory, and defaults to <filename>/nonexistent</filename> if not
+specified.
+<varname>PKG_SHELL.<replaceable>user</replaceable></varname> is the user's
+shell, and defaults to <filename>/sbinno/login</filename> if not specified.
+</para>
 
-<programlisting>
-    foo:foogrp::The\ Foomister
-</programlisting>
-
-<para>Similarly, groups can be created using the
+<para>Similarly, groups can be created by adding entries to the
 <varname>PKG_GROUPS</varname> variable, whose syntax is:</para>
 
 <programlisting>
-    group[:groupid]
+    group
 </programlisting>
 
-<para>As before, only the group name is required; the numeric identifier is
-optional.</para>
+<para>The numeric GID of the group may be set by defining 
+<varname>PKG_GID.<replaceable>group</replaceable></varname>.</para>
+
+<para>If a package needs to create the users and groups at an earlier
+stage, then it can set <varname>CREATE_USERGROUPS_PHASE</varname> to
+either <literal>configure</literal> or <literal>build</literal> to
+indicate the phase before which the users and groups are created.  In
+this case, the numeric UIDs and GIDs of the created users and groups
+are automatically hardcoded into the final installation scripts.</para>
 
 </sect1>
 
Index: mk/install/bsd.pkginstall.mk
===================================================================
RCS file: /cvsroot/pkgsrc/mk/install/bsd.pkginstall.mk,v
retrieving revision 1.46
diff -u -r1.46 bsd.pkginstall.mk
--- mk/install/bsd.pkginstall.mk	16 Apr 2006 04:27:17 -0000	1.46
+++ mk/install/bsd.pkginstall.mk	21 Apr 2006 17:50:58 -0000
@@ -111,21 +111,42 @@
 # PKG_USERS represents the users to create for the package.  It is a
 #	space-separated list of elements of the form
 #
-#		user:group[:[userid][:[descr][:[home][:shell]]]]
+#		user:group
 #
-#	Only the user and group are required; everything else is optional,
-#	but the colons must be in the right places when specifying optional
-#	bits.  Note that if the description contains spaces, they must
-#	be escaped as usual, e.g.
+# The following variables are optional and specify further details of
+# the user accounts listed in PKG_USERS:
+#
+# PKG_UID.<user> is the hardcoded numeric UID for <user>.
+# PKG_GECOS.<user> is <user>'s description, as well as contact info.
+# PKG_HOME.<user> is the home directory for <user>.
+# PKG_SHELL.<user> is the login shell for <user>.
 #
-#		foo:foogrp::The\ Foomister
 #
 # PKG_GROUPS represents the groups to create for the package.  It is a
 #	space-separated list of elements of the form
 #
-#		group[:groupid]
+#		group
+#
+# The following variables are optional and specify further details of
+# the user accounts listed in PKG_GROUPS:
+#
+# PKG_GID.<group> is the hardcoded numeric GID for <group>.
+#
+# For example:
+#
+#	PKG_GROUPS+=	mail
+#	PKG_USERS+=	courier:mail
+#
+#	PKG_GECOS.courier=	Courier authlib and mail user
+#
+# CREATE_USERGROUPS_PHASE is set to the phase just before which users
+#	and groups need to be created.  Valid values are "configure" and
+#	"build".  If not defined, then by default users and groups are
+#	created prior to installation by the pre-install-script target.
+#	If this is defined, then the numeric UIDs and GIDs of users and
+#	groups required by this package are hardcoded into the +INSTALL
+#	script.
 #
-#	Only the group is required; the groupid is optional.
 #
 PKG_GROUPS?=		# empty
 PKG_USERS?=		# empty
@@ -162,19 +183,43 @@
 _INSTALL_UNPACK_TMPL+=		${_INSTALL_USERGROUP_FILE}
 _INSTALL_DATA_TMPL+=		${_INSTALL_USERGROUP_DATAFILE}
 
+.for _group_ in ${PKG_GROUPS}
+.  if defined(CREATE_USERGROUPS_PHASE)
+# Determine the numeric GID of each group.
+USE_TOOLS+=	perl
+PKG_GID.${_group_}_cmd=							\
+	if ${TEST} ! -x ${PERL5}; then ${ECHO} ""; exit 0; fi;		\
+	${PERL5} -le 'print scalar getgrnam shift' ${_group_}
+PKG_GID.${_group_}?=	${PKG_GID.${_group_}_cmd:sh:M*}
+.  endif
+_PKG_GROUPS+=	${_group_}:${PKG_GID.${_group_}}
+.endfor
+
+.for _entry_ in ${PKG_USERS}
+.  if defined(CREATE_USERGROUPS_PHASE)
+# Determine the numeric UID of each user.
+USE_TOOLS+=	perl
+PKG_UID.${_entry_:C/\:.*//}_cmd=					\
+	if ${TEST} ! -x ${PERL5}; then ${ECHO} ""; exit 0; fi;		\
+	${PERL5} -le 'print scalar getpwnam shift' ${_entry_:C/\:.*//}
+PKG_UID.${_entry_:C/\:.*//}?=	${PKG_UID.${_entry_:C/\:.*//}_cmd:sh:M*}
+.  endif
+_PKG_USERS+=	${_user_::=${_entry_:C/\:.*//}}${_entry_}:${PKG_UID.${_user_}}:${PKG_GECOS.${_user_}:Q}:${PKG_HOME.${_user_}:Q}:${PKG_SHELL.${_user_}:Q}
+.endfor
+
 ${_INSTALL_USERGROUP_DATAFILE}:
 	${_PKG_SILENT}${_PKG_DEBUG}${MKDIR} ${.TARGET:H}
 	${_PKG_SILENT}${_PKG_DEBUG}${RM} -f ${.TARGET} ${.TARGET}.tmp
 	${_PKG_SILENT}${_PKG_DEBUG}${TOUCH} ${TOUCH_ARGS} ${.TARGET}.tmp
 	${_PKG_SILENT}${_PKG_DEBUG}					\
-	set -- dummy ${PKG_GROUPS}; shift;				\
+	set -- dummy ${_PKG_GROUPS:C/\:*$//}; shift;			\
 	exec 1>>${.TARGET}.tmp;						\
 	while ${TEST} $$# -gt 0; do					\
 		i="$$1"; shift;						\
 		${ECHO} "# GROUP: $$i";					\
 	done
 	${_PKG_SILENT}${_PKG_DEBUG}					\
-	set -- dummy ${PKG_USERS}; shift;				\
+	set -- dummy ${_PKG_USERS:C/\:*$//}; shift;			\
 	exec 1>>${.TARGET}.tmp;						\
 	while ${TEST} $$# -gt 0; do					\
 		i="$$1"; shift;						\
@@ -196,6 +241,48 @@
 		${TOUCH} ${TOUCH_ARGS} ${.TARGET};			\
 	fi
 
+_INSTALL_USERGROUP_UNPACKER=	${_PKGINSTALL_DIR}/usergroup-unpack
+
+${_INSTALL_USERGROUP_UNPACKER}:						\
+		${_INSTALL_USERGROUP_FILE}				\
+		${_INSTALL_USERGROUP_DATAFILE}
+	${_PKG_SILENT}${_PKG_DEBUG}${MKDIR} ${.TARGET:H}
+	${_PKG_SILENT}${_PKG_DEBUG}					\
+	exec 1>${.TARGET}.tmp;						\
+	${ECHO} "#!${SH}";						\
+	${ECHO} "";							\
+	${ECHO} "CAT="${CAT:Q};						\
+	${ECHO} "CHMOD="${CHMOD:Q};					\
+	${ECHO} "SED="${SED:Q};						\
+	${ECHO} "";							\
+	${ECHO} "SELF=\$$0";						\
+	${ECHO} "STAGE=UNPACK";						\
+	${ECHO} "";							\
+	${CAT}	${_INSTALL_USERGROUP_FILE}				\
+		${_INSTALL_USERGROUP_DATAFILE}
+	${_PKG_SILENT}${_PKG_DEBUG}${MV} -f ${.TARGET}.tmp ${.TARGET}
+	${_PKG_SILENT}${_PKG_DEBUG}${CHMOD} +x ${.TARGET}
+
+.if defined(CREATE_USERGROUPS_PHASE)
+.  if !empty(CREATE_USERGROUPS_PHASE:M*configure)
+pre-configure: create-usergroup
+.  elif !empty(CREATE_USERGROUPS_PHASE:M*build)
+pre-build: create-usergroup
+.  endif
+.endif
+
+.PHONY: create-usergroup
+create-usergroup: ${_INSTALL_USERGROUP_UNPACKER}
+	${_PKG_SILENT}${_PKG_DEBUG}cd ${_PKGINSTALL_DIR} &&		\
+		${SH} ${_INSTALL_USERGROUP_UNPACKER}
+	${_PKG_SILENT}${_PKG_DEBUG}${TEST} -f ./+USERGROUP &&		\
+		./+USERGROUP ADD ${_PKG_DBDIR}/${PKGNAME} &&		\
+		./+USERGROUP CHECK-ADD ${_PKG_DBDIR}/${PKGNAME}
+	${_PKG_SILENT}${_PKG_DEBUG}${RM} -f ${_INSTALL_USERGROUP_FILE}
+	${_PKG_SILENT}${_PKG_DEBUG}${RM} -f ${_INSTALL_USERGROUP_DATAFILE}
+	${_PKG_SILENT}${_PKG_DEBUG}${RM} -f ${_INSTALL_USERGROUP_UNPACKER}
+	${_PKG_SILENT}${_PKG_DEBUG}${RM} -f ./+USERGROUP
+
 # SPECIAL_PERMS are lists that look like:
 #		file user group mode
 #	At post-install time, file (it may be a directory) is changed to be
Index: mk/install/usergroup
===================================================================
RCS file: /cvsroot/pkgsrc/mk/install/usergroup,v
retrieving revision 1.14
diff -u -r1.14 usergroup
--- mk/install/usergroup	19 Mar 2006 23:58:14 -0000	1.14
+++ mk/install/usergroup	21 Apr 2006 17:50:58 -0000
@@ -44,11 +44,13 @@
 #
 # Only the group is required; the groupid is optional.
 #
+AWK="@AWK@"
 CAT="@CAT@"
 CHGRP="@CHGRP@"
+CHOWN="@CHOWN@"
 ECHO="@ECHO@"
 GREP="@GREP@"
-ID="@ID@"
+LS="@LS@"
 MKDIR="@MKDIR@"
 PWD_CMD="@PWD_CMD@"
 RM="@RM@"
@@ -77,36 +79,6 @@
 	;;
 esac
 
-group_exists()
-{
-	_group="$1"
-	case $_group in
-	"")	return 2 ;;
-	esac
-	# Check using chgrp to work properly in an NIS environment.
-	testfile="./grouptest.tmp.$$"
-	${ECHO} > $testfile
-	if ${CHGRP} $_group $testfile >/dev/null 2>&1; then
-		${RM} -f $testfile
-		return 0
-	fi
-	${RM} -f $testfile
-	return 1
-}
-
-user_exists()
-{
-	_user="$1"
-	case $_user in
-	"")	return 2 ;;
-	esac
-	# Check using id to work properly in an NIS environment.
-	if ${ID} $_user >/dev/null 2>&1; then
-		return 0
-	fi
-	return 1
-}
-
 listwrap()
 {
 	_length=$1
@@ -150,7 +122,7 @@
 		token="$shadow_dir/${PKGNAME}"
 		if ${TEST} ! -d "$shadow_dir"; then
 			${MKDIR} $shadow_dir
-			group_exists $group &&
+			group_exists $group $groupid &&
 				${ECHO} "${PKGNAME}" > $preexist
 		fi
 		if ${TEST} -f "$token" && \
@@ -161,11 +133,11 @@
 		fi
 		case ${_PKG_CREATE_USERGROUP} in
 		yes)
-			if group_exists $group; then
-				:
-			else
-				addgroup "$group" "$groupid"
-			fi
+			group_exists $group $groupid
+			case $? in
+			0)	;;
+			1)	addgroup "$group" "$groupid" ;;
+			esac
 			;;
 		esac
 	done; }
@@ -187,7 +159,7 @@
 		token="$shadow_dir/${PKGNAME}"
 		if ${TEST} ! -d "$shadow_dir"; then
 			${MKDIR} $shadow_dir
-			user_exists $user &&
+			user_exists $user $userid &&
 				${ECHO} "${PKGNAME}" > $preexist
 		fi
 		if ${TEST} -f "$token" && \
@@ -198,12 +170,14 @@
 		fi
 		case ${_PKG_CREATE_USERGROUP} in
 		yes)
-			if user_exists $user && group_exists $group; then
-				:
-			else
-				adduser "$user" "$group" "$userid"	\
+			group_exists $group || continue
+			user_exists $user $userid
+			case $? in
+			0)	;;
+			1)	adduser "$user" "$group" "$userid"	\
 					"$descr" "$home" "$shell"
-			fi
+				;;
+			esac
 			;;
 		esac
 	done; }
@@ -276,7 +250,7 @@
 		IFS="$SAVEIFS"
 		case $group in
 		"")	continue ;;
-		*)	group_exists $group && continue ;;
+		*)	group_exists $group $groupid && continue ;;
 		esac
 		case "$printed_header" in
 		yes)	;;
@@ -288,7 +262,7 @@
 		esac
 		case $groupid in
 		"")	${ECHO} "	$group" ;;
-		*)	${ECHO} "	$group ($groupid)" ;;
+		*)	${ECHO} "	$group (gid = $groupid)" ;;
 		esac
 	done
 	case "$printed_header" in
@@ -307,7 +281,7 @@
 		IFS="$SAVEIFS"
 		case $user in
 		"")	continue ;;
-		*)	user_exists $user && continue ;;
+		*)	user_exists $user $userid && continue ;;
 		esac
 		case "$printed_header" in
 		yes)	;;
@@ -317,11 +291,11 @@
 			${ECHO} ""
 			;;
 		esac
-		: ${home:="${PKG_USER_HOME}"}
-		: ${shell:="${PKG_USER_SHELL}"}
+		: ${home:="@PKG_USER_HOME@"}
+		: ${shell:="@PKG_USER_SHELL@"}
 		case $userid in
 		"")	${ECHO} "	$user: $group, $home, $shell" ;;
-		*)	${ECHO} "	$user ($userid): $group, $home, $shell" ;;
+		*)	${ECHO} "	$user (uid = $userid): $group, $home, $shell" ;;
 		esac
 	done
 	case "$printed_header" in
@@ -343,7 +317,7 @@
 		IFS="$SAVEIFS"
 		case $user in
 		"")	continue ;;
-		*)	user_exists $user || continue ;;
+		*)	user_exists $user $userid || continue ;;
 		esac
 		shadow_dir="${PKG_REFCOUNT_USERS_DBDIR}/$user"
 		${TEST} ! -d "$shadow_dir" || continue	# refcount isn't zero
@@ -370,7 +344,7 @@
 		IFS="$SAVEIFS"
 		case $group in
 		"")	continue ;;
-		*)	group_exists $group || continue ;;
+		*)	group_exists $group $groupid || continue ;;
 		esac
 		shadow_dir="${PKG_REFCOUNT_GROUPS_DBDIR}/$group"
 		${TEST} ! -d "$shadow_dir" || continue	# refcount isn't zero
Index: mk/install/usergroupfuncs
===================================================================
RCS file: /cvsroot/pkgsrc/mk/install/usergroupfuncs,v
retrieving revision 1.3
diff -u -r1.3 usergroupfuncs
--- mk/install/usergroupfuncs	28 Oct 2005 20:05:46 -0000	1.3
+++ mk/install/usergroupfuncs	21 Apr 2006 17:50:58 -0000
@@ -1,15 +1,84 @@
 # $NetBSD: usergroupfuncs,v 1.3 2005/10/28 20:05:46 jlam Exp $
 #
-# Default implementation of adduser() and addgroup() shell functions
-# for adding users and groups.  This implementation assumes there are
-# NetBSD/Solaris-compatible versions of useradd(8) and groupadd(8)
-# available through ${USERADD} and ${GROUPADD}, respectively.
+# Default implementations of user_exists() and group_exists() shell
+# functions for checking the existence of users and groups, and of
+# adduser() and addgroup() shell functions for adding users and groups.
+# Assume there are NetBSD/Solaris-compatible versions of useradd(8) and
+# groupadd(8) available through ${USERADD} and ${GROUPADD}, respectively.
 #
 # Platform-specific replacements for this file should be located at:
 #
 #	pkgsrc/mk/install/usergroupfuncs.${OPSYS}
 #
 
+# group_exists group groupid
+#	Returns 0 if $group exists and has gid $groupid
+#	Returns 1 if neither $group nor $groupid exist
+#	Returns 2 if $group or $groupid exist but don't match
+#	Returns 3 for all errors
+#	
+group_exists()
+{
+	_group="$1"; _groupid="$2"
+	${TEST} -n "$_group" || return 3
+
+	# Check using chgrp to work properly in an NSS/NIS environment.
+	_testfile="./grouptest.tmp.$$"
+	${ECHO} > $_testfile
+	if ${CHGRP} $_group $_testfile >/dev/null 2>&1; then
+		# $_group exists
+		_id=`${LS} -ln $_testfile 2>/dev/null | ${AWK} '{ print $4 }'`
+		${TEST} -n "$_groupid" || _groupid=$_id
+		if ${TEST} "$_groupid" = "$_id"; then
+			${RM} -f $_testfile; return 0
+		fi
+		${RM} -f $_testfile; return 2
+	elif ${CHGRP} $_groupid $_testfile >/dev/null 2>&1; then
+		_name=`${LS} -l $_testfile 2>/dev/null | ${AWK} '{ print $4 }'`
+		if ${TEST} "$_name" != "$_groupid"; then
+			# $_group doesn't exist, but $_groupid exists
+			${RM} -f $_testfile; return 2
+		fi
+		# neither $_group nor $_groupid exist
+		${RM} -f $_testfile; return 1
+	fi
+	${RM} -f $_testfile; return 3
+}
+
+# user_exists user userid
+#	Returns 0 if $user exists and has uid $userid
+#	Returns 1 if neither $user nor $userid exist
+#	Returns 2 if $user or $userid exist but don't match
+#	Returns 3 for all errors
+#	
+user_exists()
+{
+	_user="$1"; _userid="$2"
+	${TEST} -n "$_user" || return 3
+
+	# Check using chown to work properly in an NSS/NIS environment.
+	_testfile="./usertest.tmp.$$"
+	${ECHO} > $_testfile
+	if ${CHOWN} $_user $_testfile >/dev/null 2>&1; then
+		# $_user exists
+		_id=`${LS} -ln $_testfile 2>/dev/null | ${AWK} '{ print $3 }'`
+		${TEST} -n "$_userid" || _userid=$_id
+		if ${TEST} "$_userid" = "$_id"; then
+			${RM} -f $_testfile; return 0
+		fi
+		${RM} -f $_testfile; return 2
+	elif ${CHOWN} $_userid $_testfile >/dev/null 2>&1; then
+		_name=`${LS} -l $_testfile 2>/dev/null | ${AWK} '{ print $3 }'`
+		if ${TEST} "$_name" != "$_userid"; then
+			# $_user doesn't exist, but $_userid exists
+			${RM} -f $_testfile; return 2
+		fi
+		# neither $_user nor $_userid exist
+		${RM} -f $_testfile; return 1
+	fi
+	${RM} -f $_testfile; return 3
+}
+
 # adduser user group [userid] [descr] [home] [shell]
 adduser()
 {
Index: mk/install/usergroupfuncs.DragonFly
===================================================================
RCS file: /cvsroot/pkgsrc/mk/install/usergroupfuncs.DragonFly,v
retrieving revision 1.1
diff -u -r1.1 usergroupfuncs.DragonFly
--- mk/install/usergroupfuncs.DragonFly	28 Oct 2005 20:09:38 -0000	1.1
+++ mk/install/usergroupfuncs.DragonFly	21 Apr 2006 17:50:58 -0000
@@ -3,6 +3,74 @@
 # Platform-specific adduser and addgroup functionality
 # on top of pw(8).
 
+# group_exists group groupid
+#	Returns 0 if $group exists and has gid $groupid
+#	Returns 1 if neither $group nor $groupid exist
+#	Returns 2 if $group or $groupid exist but don't match
+#	Returns 3 for all errors
+#	
+group_exists()
+{
+	_group="$1"; _groupid="$2"
+	${TEST} -n "$_group" || return 3
+
+	# Check using chgrp to work properly in an NSS/NIS environment.
+	_testfile="./grouptest.tmp.$$"
+	${ECHO} > $_testfile
+	if ${CHGRP} $_group $_testfile >/dev/null 2>&1; then
+		# $_group exists
+		_id=`${LS} -ln $_testfile 2>/dev/null | ${AWK} '{ print $4 }'`
+		${TEST} -n "$_groupid" || _groupid=$_id
+		if ${TEST} "$_groupid" = "$_id"; then
+			${RM} -f $_testfile; return 0
+		fi
+		${RM} -f $_testfile; return 2
+	elif ${CHGRP} $_groupid $_testfile >/dev/null 2>&1; then
+		_name=`${LS} -l $_testfile 2>/dev/null | ${AWK} '{ print $4 }'`
+		if ${TEST} "$_name" != "$_groupid"; then
+			# $_group doesn't exist, but $_groupid exists
+			${RM} -f $_testfile; return 2
+		fi
+		# neither $_group nor $_groupid exist
+		${RM} -f $_testfile; return 1
+	fi
+	${RM} -f $_testfile; return 3
+}
+
+# user_exists user userid
+#	Returns 0 if $user exists and has uid $userid
+#	Returns 1 if neither $user nor $userid exist
+#	Returns 2 if $user or $userid exist but don't match
+#	Returns 3 for all errors
+#	
+user_exists()
+{
+	_user="$1"; _userid="$2"
+	${TEST} -n "$_user" || return 3
+
+	# Check using chown to work properly in an NSS/NIS environment.
+	_testfile="./usertest.tmp.$$"
+	${ECHO} > $_testfile
+	if ${CHOWN} $_user $_testfile >/dev/null 2>&1; then
+		# $_user exists
+		_id=`${LS} -ln $_testfile 2>/dev/null | ${AWK} '{ print $3 }'`
+		${TEST} -n "$_userid" || _userid=$_id
+		if ${TEST} "$_userid" = "$_id"; then
+			${RM} -f $_testfile; return 0
+		fi
+		${RM} -f $_testfile; return 2
+	elif ${CHOWN} $_userid $_testfile >/dev/null 2>&1; then
+		_name=`${LS} -l $_testfile 2>/dev/null | ${AWK} '{ print $3 }'`
+		if ${TEST} "$_name" != "$_userid"; then
+			# $_user doesn't exist, but $_userid exists
+			${RM} -f $_testfile; return 2
+		fi
+		# neither $_user nor $_userid exist
+		${RM} -f $_testfile; return 1
+	fi
+	${RM} -f $_testfile; return 3
+}
+
 # adduser user group [userid] [descr] [home] [shell]
 adduser()
 {
Index: mk/install/usergroupfuncs.FreeBSD
===================================================================
RCS file: /cvsroot/pkgsrc/mk/install/usergroupfuncs.FreeBSD,v
retrieving revision 1.1
diff -u -r1.1 usergroupfuncs.FreeBSD
--- mk/install/usergroupfuncs.FreeBSD	6 Nov 2005 19:45:08 -0000	1.1
+++ mk/install/usergroupfuncs.FreeBSD	21 Apr 2006 17:50:58 -0000
@@ -3,6 +3,74 @@
 # Platform-specific adduser and addgroup functionality
 # on top of pw(8).
 
+# group_exists group groupid
+#	Returns 0 if $group exists and has gid $groupid
+#	Returns 1 if neither $group nor $groupid exist
+#	Returns 2 if $group or $groupid exist but don't match
+#	Returns 3 for all errors
+#	
+group_exists()
+{
+	_group="$1"; _groupid="$2"
+	${TEST} -n "$_group" || return 3
+
+	# Check using chgrp to work properly in an NSS/NIS environment.
+	_testfile="./grouptest.tmp.$$"
+	${ECHO} > $_testfile
+	if ${CHGRP} $_group $_testfile >/dev/null 2>&1; then
+		# $_group exists
+		_id=`${LS} -ln $_testfile 2>/dev/null | ${AWK} '{ print $4 }'`
+		${TEST} -n "$_groupid" || _groupid=$_id
+		if ${TEST} "$_groupid" = "$_id"; then
+			${RM} -f $_testfile; return 0
+		fi
+		${RM} -f $_testfile; return 2
+	elif ${CHGRP} $_groupid $_testfile >/dev/null 2>&1; then
+		_name=`${LS} -l $_testfile 2>/dev/null | ${AWK} '{ print $4 }'`
+		if ${TEST} "$_name" != "$_groupid"; then
+			# $_group doesn't exist, but $_groupid exists
+			${RM} -f $_testfile; return 2
+		fi
+		# neither $_group nor $_groupid exist
+		${RM} -f $_testfile; return 1
+	fi
+	${RM} -f $_testfile; return 3
+}
+
+# user_exists user userid
+#	Returns 0 if $user exists and has uid $userid
+#	Returns 1 if neither $user nor $userid exist
+#	Returns 2 if $user or $userid exist but don't match
+#	Returns 3 for all errors
+#	
+user_exists()
+{
+	_user="$1"; _userid="$2"
+	${TEST} -n "$_user" || return 3
+
+	# Check using chown to work properly in an NSS/NIS environment.
+	_testfile="./usertest.tmp.$$"
+	${ECHO} > $_testfile
+	if ${CHOWN} $_user $_testfile >/dev/null 2>&1; then
+		# $_user exists
+		_id=`${LS} -ln $_testfile 2>/dev/null | ${AWK} '{ print $3 }'`
+		${TEST} -n "$_userid" || _userid=$_id
+		if ${TEST} "$_userid" = "$_id"; then
+			${RM} -f $_testfile; return 0
+		fi
+		${RM} -f $_testfile; return 2
+	elif ${CHOWN} $_userid $_testfile >/dev/null 2>&1; then
+		_name=`${LS} -l $_testfile 2>/dev/null | ${AWK} '{ print $3 }'`
+		if ${TEST} "$_name" != "$_userid"; then
+			# $_user doesn't exist, but $_userid exists
+			${RM} -f $_testfile; return 2
+		fi
+		# neither $_user nor $_userid exist
+		${RM} -f $_testfile; return 1
+	fi
+	${RM} -f $_testfile; return 3
+}
+
 # adduser user group [userid] [descr] [home] [shell]
 adduser()
 {

--C7zPtVaVf+AK4Oqc--