Subject: Re: [PATCH] pkginstall framework modifications
To: None <tech-pkg@NetBSD.org>
From: Johnny C. Lam <jlam@pkgsrc.org>
List: tech-pkg
Date: 04/22/2006 00:04:08
--u3/rZRmxL6MmkK24
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Fri, Apr 21, 2006 at 06:19:04PM +0000, Johnny C. Lam wrote:
> 
> (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.

I've created a new patch that include a few more changes:

(1) The CREATE_USERGROUPS_PHASE variable has been renamed to
    USERGROUP_PHASE for brevity.

(2) If USERGROUP_PHASE is defined, then we use the usergroup-check
    script to check if the requested users and groups already exist
    prior to running the +USERGROUP script to verify or create them.
    The benefit of using this script as opposed to directly running
    the +USERGROUP script is that the latter requires root privileges
    to make the checks, whereas usergroup-check can be run as a normal
    user.  This fixes building packages by non-root users provided
    that all requested users and groups already exist.

This new patch complete replaces the old one.

	Cheers,

	-- Johnny Lam <jlam@pkgsrc.org>

--u3/rZRmxL6MmkK24
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="usergroup.diff"

? mk/emulator
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 23:56:35 -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>USERGROUP_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 23:56:36 -0000
@@ -111,21 +111,41 @@
 # 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
 #
-#	Only the group is required; the groupid is optional.
+#	PKG_GECOS.courier=	Courier authlib and mail user
+#
+# USERGROUP_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.
 #
 PKG_GROUPS?=		# empty
 PKG_USERS?=		# empty
@@ -162,19 +182,43 @@
 _INSTALL_UNPACK_TMPL+=		${_INSTALL_USERGROUP_FILE}
 _INSTALL_DATA_TMPL+=		${_INSTALL_USERGROUP_DATAFILE}
 
+.for _group_ in ${PKG_GROUPS}
+.  if defined(USERGROUP_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(USERGROUP_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 +240,57 @@
 		${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(USERGROUP_PHASE)
+.  if !empty(USERGROUP_PHASE:M*configure)
+pre-configure: create-usergroup
+.  elif !empty(USERGROUP_PHASE:M*build)
+pre-build: create-usergroup
+.  endif
+.endif
+
+_INSTALL_USERGROUP_CHECK=						\
+	${SETENV} PERL5=${PERL5:Q}					\
+	${SH} ${PKGSRCDIR}/mk/install/usergroup-check
+
+.PHONY: create-usergroup
+create-usergroup: ${_INSTALL_USERGROUP_UNPACKER}
+	${_PKG_SILENT}${_PKG_DEBUG}					\
+	if ${_INSTALL_USERGROUP_CHECK} -g ${_PKG_GROUPS:C/\:*$//} &&	\
+	   ${_INSTALL_USERGROUP_CHECK} -u ${_PKG_USERS:C/\:*$//}; then	\
+		exit 0;							\
+	fi;								\
+	cd ${_PKGINSTALL_DIR} &&					\
+	${SH} ${_INSTALL_USERGROUP_UNPACKER} &&				\
+	${TEST} -f ./+USERGROUP &&					\
+	./+USERGROUP ADD ${_PKG_DBDIR}/${PKGNAME} &&			\
+	./+USERGROUP CHECK-ADD ${_PKG_DBDIR}/${PKGNAME} &&		\
+	${RM} -f ${_INSTALL_USERGROUP_FILE:Q}				\
+		${_INSTALL_USERGROUP_DATAFILE:Q}			\
+		${_INSTALL_USERGROUP_UNPACKER:Q}			\
+		./+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 23:56:36 -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/usergroup-check
===================================================================
RCS file: mk/install/usergroup-check
diff -N mk/install/usergroup-check
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ mk/install/usergroup-check	21 Apr 2006 23:56:36 -0000
@@ -0,0 +1,126 @@
+#!/bin/sh
+#
+# $NetBSD$
+#
+# Copyright (c) 2006 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# This code is derived from software contributed to The NetBSD Foundation
+# by Johnny C. Lam.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. All advertising materials mentioning features or use of this software
+#    must display the following acknowledgement:
+#        This product includes software developed by the NetBSD
+#        Foundation, Inc. and its contributors.
+# 4. Neither the name of The NetBSD Foundation nor the names of its
+#    contributors may be used to endorse or promote products derived
+#    from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+######################################################################
+#
+# NAME
+#	usergroup-check -- verify that users/groups match numeric IDs 
+#
+# SYNOPSIS
+#	usergroup-check -g [group_entry ...]
+#	usergroup-check -u [user_entry ...]
+#
+# DESCRIPTION
+#       usergroup-check checks for the existence of users and groups
+#	and verifies that they match the requested numeric IDs if
+#	given.  The group_entry format matches that of /etc/group and
+#	the user_entry format matches that of /etc/passwd, though the
+#	field contents may be empty.
+#
+#	usergroup-check exits 0 if the users and groups exist and match
+#	the numeric IDs, and >0 otherwise.
+#
+# OPTIONS
+#	The following command line arguments are supported.
+#
+#	-g      Indicates that the subsequent arguments are group entries.
+#
+#	-u      Indicates that the subsequent arguments are user entries.
+#
+######################################################################
+
+: ${PERL5=perl}
+
+self="${0##*/}"
+
+usage() {
+	echo 1>&2 "usage: $self -g [group_entry ...]"
+	echo 1>&2 "       $self -u [user_entry ...]"
+}
+
+if test $# -lt 1; then
+	usage; exit 1
+fi
+
+check=
+case "$1" in
+-g)	check=groups ;;
+-u)	check=users ;;
+*)	usage; exit 1 ;;
+esac
+shift
+
+missing_groups=
+missing_users=
+
+case $check in
+groups)
+	while test $# -gt 0; do
+		entry="$1"; shift
+		( SAVEIFS="$IFS"; IFS=":"
+		  set -- $entry; group="$1"; groupid="$2"
+		  IFS="$SAVEIFS"
+		  gid=`${PERL5} -le 'print scalar getgrnam shift' $group`
+		  test -n "$gid" || exit 1
+		  case "$groupid" in
+		  ""|$gid)     exit 0 ;;
+		  *)            exit 1 ;;
+		  esac ) || missing_groups="$missing_groups $i"
+	done
+	;;
+
+users)
+	missing_users=
+	while test $# -gt 0; do
+		entry="$1"; shift
+		( SAVEIFS="$IFS"; IFS=":"
+		  set -- $entry; user="$1"; userid="$3"
+		  IFS="$SAVEIFS"
+		  gid=`${PERL5} -le 'print scalar getpwnam shift' $user`
+		  test -n "$gid" || exit 1
+		  case "$userid" in
+		  ""|$gid)     exit 0 ;;
+		  *)            exit 1 ;;
+		  esac ) || missing_users="$missing_users $i"
+	done
+	;;
+esac
+
+test -z "$missing_groups" -a -z "$missing_users" || exit 1
+exit 0
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 23:56:36 -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 23:56:36 -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 23:56:36 -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()
 {

--u3/rZRmxL6MmkK24--