xref: /titanic_53/usr/src/cmd/projadd/projmod.pl (revision 7c478bd95313f5f23a4c958a745db2134aa03244)
1*7c478bd9Sstevel@tonic-gate#!/usr/perl5/bin/perl -w
2*7c478bd9Sstevel@tonic-gate#
3*7c478bd9Sstevel@tonic-gate# CDDL HEADER START
4*7c478bd9Sstevel@tonic-gate#
5*7c478bd9Sstevel@tonic-gate# The contents of this file are subject to the terms of the
6*7c478bd9Sstevel@tonic-gate# Common Development and Distribution License, Version 1.0 only
7*7c478bd9Sstevel@tonic-gate# (the "License").  You may not use this file except in compliance
8*7c478bd9Sstevel@tonic-gate# with the License.
9*7c478bd9Sstevel@tonic-gate#
10*7c478bd9Sstevel@tonic-gate# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11*7c478bd9Sstevel@tonic-gate# or http://www.opensolaris.org/os/licensing.
12*7c478bd9Sstevel@tonic-gate# See the License for the specific language governing permissions
13*7c478bd9Sstevel@tonic-gate# and limitations under the License.
14*7c478bd9Sstevel@tonic-gate#
15*7c478bd9Sstevel@tonic-gate# When distributing Covered Code, include this CDDL HEADER in each
16*7c478bd9Sstevel@tonic-gate# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17*7c478bd9Sstevel@tonic-gate# If applicable, add the following below this CDDL HEADER, with the
18*7c478bd9Sstevel@tonic-gate# fields enclosed by brackets "[]" replaced with your own identifying
19*7c478bd9Sstevel@tonic-gate# information: Portions Copyright [yyyy] [name of copyright owner]
20*7c478bd9Sstevel@tonic-gate#
21*7c478bd9Sstevel@tonic-gate# CDDL HEADER END
22*7c478bd9Sstevel@tonic-gate#
23*7c478bd9Sstevel@tonic-gate#
24*7c478bd9Sstevel@tonic-gate# Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
25*7c478bd9Sstevel@tonic-gate# Use is subject to license terms.
26*7c478bd9Sstevel@tonic-gate#
27*7c478bd9Sstevel@tonic-gate#ident	"%Z%%M%	%I%	%E% SMI"
28*7c478bd9Sstevel@tonic-gate#
29*7c478bd9Sstevel@tonic-gate
30*7c478bd9Sstevel@tonic-gaterequire 5.005;
31*7c478bd9Sstevel@tonic-gateuse strict;
32*7c478bd9Sstevel@tonic-gateuse locale;
33*7c478bd9Sstevel@tonic-gateuse Errno;
34*7c478bd9Sstevel@tonic-gateuse Fcntl;
35*7c478bd9Sstevel@tonic-gateuse File::Basename;
36*7c478bd9Sstevel@tonic-gateuse Getopt::Std;
37*7c478bd9Sstevel@tonic-gateuse Getopt::Long qw(:config no_ignore_case bundling);
38*7c478bd9Sstevel@tonic-gateuse POSIX qw(locale_h);
39*7c478bd9Sstevel@tonic-gateuse Sun::Solaris::Utils qw(textdomain gettext);
40*7c478bd9Sstevel@tonic-gateuse Sun::Solaris::Project qw(:ALL :PRIVATE);
41*7c478bd9Sstevel@tonic-gate
42*7c478bd9Sstevel@tonic-gate#
43*7c478bd9Sstevel@tonic-gate# Print a usage message and exit.
44*7c478bd9Sstevel@tonic-gate#
45*7c478bd9Sstevel@tonic-gatesub usage
46*7c478bd9Sstevel@tonic-gate{
47*7c478bd9Sstevel@tonic-gate	my (@msg) = @_;
48*7c478bd9Sstevel@tonic-gate	my $prog = basename($0);
49*7c478bd9Sstevel@tonic-gate	my $space = ' ' x length($prog);
50*7c478bd9Sstevel@tonic-gate	print(STDERR "$prog: @msg\n") if (@msg);
51*7c478bd9Sstevel@tonic-gate	printf(STDERR gettext(
52*7c478bd9Sstevel@tonic-gate	    "Usage: %s [-n] [-f filename]\n"), $prog);
53*7c478bd9Sstevel@tonic-gate	printf(STDERR gettext(
54*7c478bd9Sstevel@tonic-gate	    "       %s [-n] [-f filename] [-p projid [-o]] [-c comment]\n".
55*7c478bd9Sstevel@tonic-gate            "       %s [-a|-s|-r] [-U user[,user...]] [-G group[,group...]]\n".
56*7c478bd9Sstevel@tonic-gate            "       %s [-K name[=value[,value...]]] [-l new_projectname] ".
57*7c478bd9Sstevel@tonic-gate	    "project\n"), $prog, $space, $space, $space);
58*7c478bd9Sstevel@tonic-gate	exit(2);
59*7c478bd9Sstevel@tonic-gate}
60*7c478bd9Sstevel@tonic-gate
61*7c478bd9Sstevel@tonic-gate#
62*7c478bd9Sstevel@tonic-gate# Print a list of error messages and exit.
63*7c478bd9Sstevel@tonic-gate#
64*7c478bd9Sstevel@tonic-gatesub error
65*7c478bd9Sstevel@tonic-gate{
66*7c478bd9Sstevel@tonic-gate	my $exit = $_[0][0];
67*7c478bd9Sstevel@tonic-gate	my $prog = basename($0) . ': ';
68*7c478bd9Sstevel@tonic-gate	foreach my $err (@_) {
69*7c478bd9Sstevel@tonic-gate		my ($e, $fmt, @args) = @$err;
70*7c478bd9Sstevel@tonic-gate		printf(STDERR $prog . $fmt . "\n", @args);
71*7c478bd9Sstevel@tonic-gate	}
72*7c478bd9Sstevel@tonic-gate	exit($exit);
73*7c478bd9Sstevel@tonic-gate}
74*7c478bd9Sstevel@tonic-gate
75*7c478bd9Sstevel@tonic-gate#
76*7c478bd9Sstevel@tonic-gate# Merge an array of users/groups with an existing array.  The array to merge
77*7c478bd9Sstevel@tonic-gate# is the first argument, an array ref is the second argument.  The third
78*7c478bd9Sstevel@tonic-gate# argument is the mode which can be one of:
79*7c478bd9Sstevel@tonic-gate#     add	add all entries in the first arg to the second
80*7c478bd9Sstevel@tonic-gate#     remove	remove all entries in the first arg from the second
81*7c478bd9Sstevel@tonic-gate#     replace	replace the second arg by the first
82*7c478bd9Sstevel@tonic-gate# The resulting array is returned as a reference.
83*7c478bd9Sstevel@tonic-gate#
84*7c478bd9Sstevel@tonic-gatesub merge_lists
85*7c478bd9Sstevel@tonic-gate{
86*7c478bd9Sstevel@tonic-gate	my ($new, $old, $mode) = @_;
87*7c478bd9Sstevel@tonic-gate	my @err;
88*7c478bd9Sstevel@tonic-gate
89*7c478bd9Sstevel@tonic-gate	if ($mode eq 'add') {
90*7c478bd9Sstevel@tonic-gate		my @merged = @$old;
91*7c478bd9Sstevel@tonic-gate		my %look = map { $_ => 1 } @$old;
92*7c478bd9Sstevel@tonic-gate		my @leftover;
93*7c478bd9Sstevel@tonic-gate		foreach my $e (@$new) {
94*7c478bd9Sstevel@tonic-gate			if (! exists($look{$e})) {
95*7c478bd9Sstevel@tonic-gate				push(@merged, $e);
96*7c478bd9Sstevel@tonic-gate			} else {
97*7c478bd9Sstevel@tonic-gate				push(@leftover, $e);
98*7c478bd9Sstevel@tonic-gate			}
99*7c478bd9Sstevel@tonic-gate		}
100*7c478bd9Sstevel@tonic-gate		if (@leftover) {
101*7c478bd9Sstevel@tonic-gate			push(@err,
102*7c478bd9Sstevel@tonic-gate			    [6, gettext('Project already contains "%s"'),
103*7c478bd9Sstevel@tonic-gate			    join(',', @leftover)]);
104*7c478bd9Sstevel@tonic-gate			return (1, \@err);
105*7c478bd9Sstevel@tonic-gate		}
106*7c478bd9Sstevel@tonic-gate
107*7c478bd9Sstevel@tonic-gate		return(0, \@merged);
108*7c478bd9Sstevel@tonic-gate
109*7c478bd9Sstevel@tonic-gate	} elsif ($mode eq 'remove') {
110*7c478bd9Sstevel@tonic-gate
111*7c478bd9Sstevel@tonic-gate		my %seen;
112*7c478bd9Sstevel@tonic-gate		my @dups = grep($seen{$_}++ == 1, @$new);
113*7c478bd9Sstevel@tonic-gate		if (@dups) {
114*7c478bd9Sstevel@tonic-gate			push(@err, [6, gettext('Duplicate names "%s"'),
115*7c478bd9Sstevel@tonic-gate			    join(',', @dups)]);
116*7c478bd9Sstevel@tonic-gate			return (1, \@err);
117*7c478bd9Sstevel@tonic-gate		}
118*7c478bd9Sstevel@tonic-gate		my @merged;
119*7c478bd9Sstevel@tonic-gate		my %look = map { $_ => 0 } @$new;
120*7c478bd9Sstevel@tonic-gate		foreach my $e (@$old) {
121*7c478bd9Sstevel@tonic-gate			if (exists($look{$e})) {
122*7c478bd9Sstevel@tonic-gate				$look{$e}++;
123*7c478bd9Sstevel@tonic-gate			} else {
124*7c478bd9Sstevel@tonic-gate				push(@merged, $e);
125*7c478bd9Sstevel@tonic-gate			}
126*7c478bd9Sstevel@tonic-gate		}
127*7c478bd9Sstevel@tonic-gate		my @leftover = grep(! $look{$_}, keys(%look));
128*7c478bd9Sstevel@tonic-gate		if (@leftover) {
129*7c478bd9Sstevel@tonic-gate			push(@err, [6,
130*7c478bd9Sstevel@tonic-gate		            gettext('Project does not contain "%s"'),
131*7c478bd9Sstevel@tonic-gate			    join(',', @leftover)]);
132*7c478bd9Sstevel@tonic-gate			return (1, \@err);
133*7c478bd9Sstevel@tonic-gate		}
134*7c478bd9Sstevel@tonic-gate		return (0, \@merged);
135*7c478bd9Sstevel@tonic-gate
136*7c478bd9Sstevel@tonic-gate	} elsif ($mode eq 'replace' || $mode eq 'substitute') {
137*7c478bd9Sstevel@tonic-gate		return (0, $new);
138*7c478bd9Sstevel@tonic-gate	}
139*7c478bd9Sstevel@tonic-gate}
140*7c478bd9Sstevel@tonic-gate
141*7c478bd9Sstevel@tonic-gate#
142*7c478bd9Sstevel@tonic-gate# merge_values(ref to listA, ref to listB, mode
143*7c478bd9Sstevel@tonic-gate#
144*7c478bd9Sstevel@tonic-gate# Merges the values in listB with the values in listA.  Dups are not
145*7c478bd9Sstevel@tonic-gate# merged away, but instead are maintained.
146*7c478bd9Sstevel@tonic-gate#
147*7c478bd9Sstevel@tonic-gate# modes:
148*7c478bd9Sstevel@tonic-gate#	add   :	add values in listB to listA
149*7c478bd9Sstevel@tonic-gate#	remove:	removes first instance of each value in listB from listA
150*7c478bd9Sstevel@tonic-gate#
151*7c478bd9Sstevel@tonic-gatesub merge_values
152*7c478bd9Sstevel@tonic-gate{
153*7c478bd9Sstevel@tonic-gate
154*7c478bd9Sstevel@tonic-gate	my ($new, $old, $mode) = @_;
155*7c478bd9Sstevel@tonic-gate	my $undefined;
156*7c478bd9Sstevel@tonic-gate	my @merged;
157*7c478bd9Sstevel@tonic-gate	my $lastmerged;
158*7c478bd9Sstevel@tonic-gate	my ($oldval, $newval);
159*7c478bd9Sstevel@tonic-gate	my $found;
160*7c478bd9Sstevel@tonic-gate	my @err;
161*7c478bd9Sstevel@tonic-gate
162*7c478bd9Sstevel@tonic-gate	if (!defined($old) && !defined($new)) {
163*7c478bd9Sstevel@tonic-gate		return (0, $undefined);
164*7c478bd9Sstevel@tonic-gate	}
165*7c478bd9Sstevel@tonic-gate
166*7c478bd9Sstevel@tonic-gate	if ($mode eq 'add') {
167*7c478bd9Sstevel@tonic-gate
168*7c478bd9Sstevel@tonic-gate		if (defined($old)) {
169*7c478bd9Sstevel@tonic-gate			push(@merged, @$old);
170*7c478bd9Sstevel@tonic-gate		}
171*7c478bd9Sstevel@tonic-gate		if (defined($new)) {
172*7c478bd9Sstevel@tonic-gate			push(@merged, @$new);
173*7c478bd9Sstevel@tonic-gate		}
174*7c478bd9Sstevel@tonic-gate		return (0, \@merged);
175*7c478bd9Sstevel@tonic-gate
176*7c478bd9Sstevel@tonic-gate	} elsif ($mode eq 'remove') {
177*7c478bd9Sstevel@tonic-gate
178*7c478bd9Sstevel@tonic-gate		$lastmerged = $old;
179*7c478bd9Sstevel@tonic-gate		foreach $newval (@$new) {
180*7c478bd9Sstevel@tonic-gate			$found = 0;
181*7c478bd9Sstevel@tonic-gate			@merged = ();
182*7c478bd9Sstevel@tonic-gate			foreach $oldval (@$lastmerged) {
183*7c478bd9Sstevel@tonic-gate				if (!$found &&
184*7c478bd9Sstevel@tonic-gate				    projent_values_equal($newval, $oldval)) {
185*7c478bd9Sstevel@tonic-gate					$found = 1;
186*7c478bd9Sstevel@tonic-gate				} else {
187*7c478bd9Sstevel@tonic-gate					push(@merged, $oldval);
188*7c478bd9Sstevel@tonic-gate				}
189*7c478bd9Sstevel@tonic-gate
190*7c478bd9Sstevel@tonic-gate			}
191*7c478bd9Sstevel@tonic-gate			if (!$found) {
192*7c478bd9Sstevel@tonic-gate				push(@err, [6, gettext(
193*7c478bd9Sstevel@tonic-gate				    'Value "%s" not found'),
194*7c478bd9Sstevel@tonic-gate				    projent_values2string($newval)]);
195*7c478bd9Sstevel@tonic-gate			}
196*7c478bd9Sstevel@tonic-gate			@$lastmerged = @merged;
197*7c478bd9Sstevel@tonic-gate		}
198*7c478bd9Sstevel@tonic-gate
199*7c478bd9Sstevel@tonic-gate		if (@err) {
200*7c478bd9Sstevel@tonic-gate			return (1, \@err);
201*7c478bd9Sstevel@tonic-gate		} else {
202*7c478bd9Sstevel@tonic-gate			return (0, \@merged);
203*7c478bd9Sstevel@tonic-gate		}
204*7c478bd9Sstevel@tonic-gate	}
205*7c478bd9Sstevel@tonic-gate}
206*7c478bd9Sstevel@tonic-gate
207*7c478bd9Sstevel@tonic-gate#
208*7c478bd9Sstevel@tonic-gate# merge_attribs(listA ref, listB ref, mode)
209*7c478bd9Sstevel@tonic-gate#
210*7c478bd9Sstevel@tonic-gate# Merge listB of attribute/values hash refs with listA
211*7c478bd9Sstevel@tonic-gate# Each hash ref should have keys "name" and "values"
212*7c478bd9Sstevel@tonic-gate#
213*7c478bd9Sstevel@tonic-gate# modes:
214*7c478bd9Sstevel@tonic-gate#     add	For each attribute in listB, add its values to
215*7c478bd9Sstevel@tonic-gate#	        the matching attribute in listA.  If listA does not
216*7c478bd9Sstevel@tonic-gate#		contain this attribute, add it.
217*7c478bd9Sstevel@tonic-gate#
218*7c478bd9Sstevel@tonic-gate#     remove	For each attribute in listB, remove its values from
219*7c478bd9Sstevel@tonic-gate#	        the matching attribute in listA.  If all of an
220*7c478bd9Sstevel@tonic-gate#		attributes values are removed, the attribute is removed.
221*7c478bd9Sstevel@tonic-gate#		If the attribute in listB has no values, then the attribute
222*7c478bd9Sstevel@tonic-gate#		and all of it's values are removed from listA
223*7c478bd9Sstevel@tonic-gate#
224*7c478bd9Sstevel@tonic-gate#     substitute For each attribute in listB, replace the values of
225*7c478bd9Sstevel@tonic-gate#	        the matching attribute in listA with its values.  If
226*7c478bd9Sstevel@tonic-gate#		listA does not contain this attribute, add it.
227*7c478bd9Sstevel@tonic-gate#
228*7c478bd9Sstevel@tonic-gate#     replace	Return listB
229*7c478bd9Sstevel@tonic-gate#
230*7c478bd9Sstevel@tonic-gate# The resulting array is returned as a reference.
231*7c478bd9Sstevel@tonic-gate#
232*7c478bd9Sstevel@tonic-gatesub merge_attribs
233*7c478bd9Sstevel@tonic-gate{
234*7c478bd9Sstevel@tonic-gate	my ($new, $old, $mode) = @_;
235*7c478bd9Sstevel@tonic-gate	my @merged;
236*7c478bd9Sstevel@tonic-gate	my @err;
237*7c478bd9Sstevel@tonic-gate	my $ret;
238*7c478bd9Sstevel@tonic-gate	my $tmp;
239*7c478bd9Sstevel@tonic-gate	my $newattrib;
240*7c478bd9Sstevel@tonic-gate	my $oldattrib;
241*7c478bd9Sstevel@tonic-gate	my $values;
242*7c478bd9Sstevel@tonic-gate
243*7c478bd9Sstevel@tonic-gate	if ($mode eq 'add') {
244*7c478bd9Sstevel@tonic-gate
245*7c478bd9Sstevel@tonic-gate		my %oldhash;
246*7c478bd9Sstevel@tonic-gate		push(@merged, @$old);
247*7c478bd9Sstevel@tonic-gate		%oldhash = map { $_->{'name'} => $_ } @$old;
248*7c478bd9Sstevel@tonic-gate		foreach $newattrib (@$new) {
249*7c478bd9Sstevel@tonic-gate
250*7c478bd9Sstevel@tonic-gate			$oldattrib = $oldhash{$newattrib->{'name'}};
251*7c478bd9Sstevel@tonic-gate			if (defined($oldattrib)) {
252*7c478bd9Sstevel@tonic-gate				($ret, $tmp) = merge_values(
253*7c478bd9Sstevel@tonic-gate				    $newattrib->{'values'},
254*7c478bd9Sstevel@tonic-gate				    $oldattrib->{'values'},
255*7c478bd9Sstevel@tonic-gate				    $mode);
256*7c478bd9Sstevel@tonic-gate
257*7c478bd9Sstevel@tonic-gate				if ($ret != 0) {
258*7c478bd9Sstevel@tonic-gate					push(@err, @$tmp);
259*7c478bd9Sstevel@tonic-gate				} else {
260*7c478bd9Sstevel@tonic-gate					$oldattrib->{'values'} = $tmp;
261*7c478bd9Sstevel@tonic-gate				}
262*7c478bd9Sstevel@tonic-gate			} else {
263*7c478bd9Sstevel@tonic-gate				push(@merged, $newattrib);
264*7c478bd9Sstevel@tonic-gate			}
265*7c478bd9Sstevel@tonic-gate		}
266*7c478bd9Sstevel@tonic-gate		if (@err) {
267*7c478bd9Sstevel@tonic-gate			return (1, \@err);
268*7c478bd9Sstevel@tonic-gate		} else {
269*7c478bd9Sstevel@tonic-gate			return (0, \@merged);
270*7c478bd9Sstevel@tonic-gate		}
271*7c478bd9Sstevel@tonic-gate
272*7c478bd9Sstevel@tonic-gate	} elsif ($mode eq 'remove') {
273*7c478bd9Sstevel@tonic-gate
274*7c478bd9Sstevel@tonic-gate		my %seen;
275*7c478bd9Sstevel@tonic-gate		my @dups = grep($seen{$_}++ == 1, map { $_->{'name'} } @$new);
276*7c478bd9Sstevel@tonic-gate		if (@dups) {
277*7c478bd9Sstevel@tonic-gate			push(@err, [6, gettext(
278*7c478bd9Sstevel@tonic-gate			    'Duplicate Attributes "%s"'),
279*7c478bd9Sstevel@tonic-gate			     join(',', @dups)]);
280*7c478bd9Sstevel@tonic-gate			return (1, \@err);
281*7c478bd9Sstevel@tonic-gate		}
282*7c478bd9Sstevel@tonic-gate		my %toremove = map { $_->{'name'} => $_ } @$new;
283*7c478bd9Sstevel@tonic-gate
284*7c478bd9Sstevel@tonic-gate		foreach $oldattrib (@$old) {
285*7c478bd9Sstevel@tonic-gate			$newattrib = $toremove{$oldattrib->{'name'}};
286*7c478bd9Sstevel@tonic-gate			if (!defined($newattrib)) {
287*7c478bd9Sstevel@tonic-gate
288*7c478bd9Sstevel@tonic-gate				push(@merged, $oldattrib);
289*7c478bd9Sstevel@tonic-gate
290*7c478bd9Sstevel@tonic-gate			} else {
291*7c478bd9Sstevel@tonic-gate				if (defined($newattrib->{'values'})) {
292*7c478bd9Sstevel@tonic-gate					($ret, $tmp) = merge_values(
293*7c478bd9Sstevel@tonic-gate					    $newattrib->{'values'},
294*7c478bd9Sstevel@tonic-gate					    $oldattrib->{'values'},
295*7c478bd9Sstevel@tonic-gate					    $mode);
296*7c478bd9Sstevel@tonic-gate
297*7c478bd9Sstevel@tonic-gate					if ($ret != 0) {
298*7c478bd9Sstevel@tonic-gate						push(@err, @$tmp);
299*7c478bd9Sstevel@tonic-gate					} else {
300*7c478bd9Sstevel@tonic-gate						$oldattrib->{'values'} = $tmp;
301*7c478bd9Sstevel@tonic-gate					}
302*7c478bd9Sstevel@tonic-gate					if (defined($tmp) && @$tmp) {
303*7c478bd9Sstevel@tonic-gate						push(@merged, $oldattrib);
304*7c478bd9Sstevel@tonic-gate					}
305*7c478bd9Sstevel@tonic-gate				}
306*7c478bd9Sstevel@tonic-gate				delete $toremove{$oldattrib->{'name'}};
307*7c478bd9Sstevel@tonic-gate			}
308*7c478bd9Sstevel@tonic-gate		}
309*7c478bd9Sstevel@tonic-gate		foreach $tmp (keys(%toremove)) {
310*7c478bd9Sstevel@tonic-gate			push(@err, [6,
311*7c478bd9Sstevel@tonic-gate		            gettext('Project does not contain "%s"'),
312*7c478bd9Sstevel@tonic-gate			    $tmp]);
313*7c478bd9Sstevel@tonic-gate		}
314*7c478bd9Sstevel@tonic-gate
315*7c478bd9Sstevel@tonic-gate		if (@err) {
316*7c478bd9Sstevel@tonic-gate			return (1, \@err);
317*7c478bd9Sstevel@tonic-gate		} else {
318*7c478bd9Sstevel@tonic-gate			return (0, \@merged);
319*7c478bd9Sstevel@tonic-gate		}
320*7c478bd9Sstevel@tonic-gate
321*7c478bd9Sstevel@tonic-gate	} elsif ($mode eq 'substitute') {
322*7c478bd9Sstevel@tonic-gate
323*7c478bd9Sstevel@tonic-gate		my %oldhash;
324*7c478bd9Sstevel@tonic-gate		push(@merged, @$old);
325*7c478bd9Sstevel@tonic-gate		%oldhash = map { $_->{'name'} => $_ } @$old;
326*7c478bd9Sstevel@tonic-gate		foreach $newattrib (@$new) {
327*7c478bd9Sstevel@tonic-gate
328*7c478bd9Sstevel@tonic-gate			$oldattrib = $oldhash{$newattrib->{'name'}};
329*7c478bd9Sstevel@tonic-gate			if (defined($oldattrib)) {
330*7c478bd9Sstevel@tonic-gate
331*7c478bd9Sstevel@tonic-gate				$oldattrib->{'values'} =
332*7c478bd9Sstevel@tonic-gate				    $newattrib->{'values'};
333*7c478bd9Sstevel@tonic-gate
334*7c478bd9Sstevel@tonic-gate			} else {
335*7c478bd9Sstevel@tonic-gate				push(@merged, $newattrib);
336*7c478bd9Sstevel@tonic-gate			}
337*7c478bd9Sstevel@tonic-gate		}
338*7c478bd9Sstevel@tonic-gate		if (@err) {
339*7c478bd9Sstevel@tonic-gate			return (1, \@err);
340*7c478bd9Sstevel@tonic-gate		} else {
341*7c478bd9Sstevel@tonic-gate			return (0, \@merged);
342*7c478bd9Sstevel@tonic-gate		}
343*7c478bd9Sstevel@tonic-gate
344*7c478bd9Sstevel@tonic-gate	} elsif ($mode eq 'replace') {
345*7c478bd9Sstevel@tonic-gate		return (0, $new);
346*7c478bd9Sstevel@tonic-gate	}
347*7c478bd9Sstevel@tonic-gate}
348*7c478bd9Sstevel@tonic-gate
349*7c478bd9Sstevel@tonic-gate#
350*7c478bd9Sstevel@tonic-gate# Main routine of script.
351*7c478bd9Sstevel@tonic-gate#
352*7c478bd9Sstevel@tonic-gate# Set the message locale.
353*7c478bd9Sstevel@tonic-gate#
354*7c478bd9Sstevel@tonic-gatesetlocale(LC_ALL, '');
355*7c478bd9Sstevel@tonic-gatetextdomain(TEXT_DOMAIN);
356*7c478bd9Sstevel@tonic-gate
357*7c478bd9Sstevel@tonic-gate
358*7c478bd9Sstevel@tonic-gate# Process command options and do some initial command-line validity checking.
359*7c478bd9Sstevel@tonic-gatemy ($pname, $flags);
360*7c478bd9Sstevel@tonic-gate$flags = {};
361*7c478bd9Sstevel@tonic-gatemy $modify = 0;
362*7c478bd9Sstevel@tonic-gate
363*7c478bd9Sstevel@tonic-gatemy $projfile = &PROJF_PATH;
364*7c478bd9Sstevel@tonic-gatemy $opt_n;
365*7c478bd9Sstevel@tonic-gatemy $opt_c;
366*7c478bd9Sstevel@tonic-gatemy $opt_o;
367*7c478bd9Sstevel@tonic-gatemy $opt_p;
368*7c478bd9Sstevel@tonic-gatemy $opt_l;
369*7c478bd9Sstevel@tonic-gatemy $opt_a;
370*7c478bd9Sstevel@tonic-gatemy $opt_r;
371*7c478bd9Sstevel@tonic-gatemy $opt_s;
372*7c478bd9Sstevel@tonic-gatemy $opt_U;
373*7c478bd9Sstevel@tonic-gatemy $opt_G;
374*7c478bd9Sstevel@tonic-gatemy @opt_K;
375*7c478bd9Sstevel@tonic-gate
376*7c478bd9Sstevel@tonic-gateGetOptions("f=s" => \$projfile,
377*7c478bd9Sstevel@tonic-gate	   "n"   => \$opt_n,
378*7c478bd9Sstevel@tonic-gate	   "c=s" => \$opt_c,
379*7c478bd9Sstevel@tonic-gate	   "o"	 => \$opt_o,
380*7c478bd9Sstevel@tonic-gate	   "p=s" => \$opt_p,
381*7c478bd9Sstevel@tonic-gate	   "l=s" => \$opt_l,
382*7c478bd9Sstevel@tonic-gate	   "s"	 => \$opt_s,
383*7c478bd9Sstevel@tonic-gate	   "r"	 => \$opt_r,
384*7c478bd9Sstevel@tonic-gate	   "a"	 => \$opt_a,
385*7c478bd9Sstevel@tonic-gate	   "U=s" => \$opt_U,
386*7c478bd9Sstevel@tonic-gate	   "G=s" => \$opt_G,
387*7c478bd9Sstevel@tonic-gate	   "K=s" => \@opt_K) || usage();
388*7c478bd9Sstevel@tonic-gate
389*7c478bd9Sstevel@tonic-gateusage(gettext('Invalid command-line arguments')) if (@ARGV > 1);
390*7c478bd9Sstevel@tonic-gate
391*7c478bd9Sstevel@tonic-gateif ($opt_c || $opt_G || $opt_l || $opt_p || $opt_U || @opt_K) {
392*7c478bd9Sstevel@tonic-gate	$modify = 1;
393*7c478bd9Sstevel@tonic-gate	if (! defined($ARGV[0])) {
394*7c478bd9Sstevel@tonic-gate		usage(gettext('No project name specified'));
395*7c478bd9Sstevel@tonic-gate	}
396*7c478bd9Sstevel@tonic-gate}
397*7c478bd9Sstevel@tonic-gate
398*7c478bd9Sstevel@tonic-gateif (!$modify && defined($ARGV[0])) {
399*7c478bd9Sstevel@tonic-gate	usage(gettext('missing -c, -G, -l, -p, -U, or -K'));
400*7c478bd9Sstevel@tonic-gate}
401*7c478bd9Sstevel@tonic-gate
402*7c478bd9Sstevel@tonic-gateif ($modify && $projfile eq '-') {
403*7c478bd9Sstevel@tonic-gate	usage(gettext('Cannot modify standard input'));
404*7c478bd9Sstevel@tonic-gate}
405*7c478bd9Sstevel@tonic-gate
406*7c478bd9Sstevel@tonic-gate$pname = $ARGV[0];
407*7c478bd9Sstevel@tonic-gateusage(gettext('-o requires -p projid to be specified'))
408*7c478bd9Sstevel@tonic-gate    if (defined($opt_o) && ! defined($opt_p));
409*7c478bd9Sstevel@tonic-gateusage(gettext('-a, -r, and -s are mutually exclusive'))
410*7c478bd9Sstevel@tonic-gate    if ((defined($opt_a) && (defined($opt_r) || defined($opt_s))) ||
411*7c478bd9Sstevel@tonic-gate	(defined($opt_r) && (defined($opt_a) || defined($opt_s))) ||
412*7c478bd9Sstevel@tonic-gate	(defined($opt_s) && (defined($opt_a) || defined($opt_r))));
413*7c478bd9Sstevel@tonic-gate
414*7c478bd9Sstevel@tonic-gateusage(gettext('-a and -r require -U users or -G groups to be specified'))
415*7c478bd9Sstevel@tonic-gate    if ((defined($opt_a) || defined($opt_r) || defined($opt_s)) &&
416*7c478bd9Sstevel@tonic-gate    ! (defined($opt_U) || defined($opt_G) || (@opt_K)));
417*7c478bd9Sstevel@tonic-gate
418*7c478bd9Sstevel@tonic-gate
419*7c478bd9Sstevel@tonic-gateif (defined($opt_a)) {
420*7c478bd9Sstevel@tonic-gate	$flags->{mode} = 'add';
421*7c478bd9Sstevel@tonic-gate} elsif (defined($opt_r)) {
422*7c478bd9Sstevel@tonic-gate	$flags->{mode} = 'remove';
423*7c478bd9Sstevel@tonic-gate} elsif (defined($opt_s)) {
424*7c478bd9Sstevel@tonic-gate	$flags->{mode} = 'substitute';
425*7c478bd9Sstevel@tonic-gate} else {
426*7c478bd9Sstevel@tonic-gate	$flags->{mode} = 'replace';
427*7c478bd9Sstevel@tonic-gate}
428*7c478bd9Sstevel@tonic-gate
429*7c478bd9Sstevel@tonic-gate# Fabricate an unique temporary filename.
430*7c478bd9Sstevel@tonic-gatemy $tmpprojf = $projfile . ".tmp.$$";
431*7c478bd9Sstevel@tonic-gate
432*7c478bd9Sstevel@tonic-gatemy $pfh;
433*7c478bd9Sstevel@tonic-gate
434*7c478bd9Sstevel@tonic-gate#
435*7c478bd9Sstevel@tonic-gate# Read the project file.  sysopen() is used so we can control the file mode.
436*7c478bd9Sstevel@tonic-gate# Handle special case for standard input.
437*7c478bd9Sstevel@tonic-gateif ($projfile eq '-') {
438*7c478bd9Sstevel@tonic-gate	open($pfh, "<&=STDIN") or error( [10,
439*7c478bd9Sstevel@tonic-gate	    gettext('Cannot open standard input')]);
440*7c478bd9Sstevel@tonic-gate} elsif (! sysopen($pfh, $projfile, O_RDONLY)) {
441*7c478bd9Sstevel@tonic-gate	error([10, gettext('Cannot open %s: %s'), $projfile, $!]);
442*7c478bd9Sstevel@tonic-gate}
443*7c478bd9Sstevel@tonic-gatemy ($mode, $uid, $gid) = (stat($pfh))[2,4,5];
444*7c478bd9Sstevel@tonic-gate
445*7c478bd9Sstevel@tonic-gate
446*7c478bd9Sstevel@tonic-gateif ($opt_n) {
447*7c478bd9Sstevel@tonic-gate	$flags->{'validate'} = 'false';
448*7c478bd9Sstevel@tonic-gate} else {
449*7c478bd9Sstevel@tonic-gate	$flags->{'validate'} = 'true';
450*7c478bd9Sstevel@tonic-gate}
451*7c478bd9Sstevel@tonic-gate
452*7c478bd9Sstevel@tonic-gate$flags->{'res'} = 'true';
453*7c478bd9Sstevel@tonic-gate$flags->{'dup'} = 'true';
454*7c478bd9Sstevel@tonic-gate
455*7c478bd9Sstevel@tonic-gatemy ($ret, $pf) = projf_read($pfh, $flags);
456*7c478bd9Sstevel@tonic-gateif ($ret != 0) {
457*7c478bd9Sstevel@tonic-gate	error(@$pf);
458*7c478bd9Sstevel@tonic-gate}
459*7c478bd9Sstevel@tonic-gateclose($pfh);
460*7c478bd9Sstevel@tonic-gatemy $err;
461*7c478bd9Sstevel@tonic-gatemy $tmperr;
462*7c478bd9Sstevel@tonic-gatemy $value;
463*7c478bd9Sstevel@tonic-gate
464*7c478bd9Sstevel@tonic-gate# Find existing record.
465*7c478bd9Sstevel@tonic-gatemy ($proj, $idx);
466*7c478bd9Sstevel@tonic-gate$idx = 0;
467*7c478bd9Sstevel@tonic-gate
468*7c478bd9Sstevel@tonic-gateif (defined($pname)) {
469*7c478bd9Sstevel@tonic-gate	foreach my $r (@$pf) {
470*7c478bd9Sstevel@tonic-gate		if ($r->{'name'} eq $pname) {
471*7c478bd9Sstevel@tonic-gate			$proj = $r;
472*7c478bd9Sstevel@tonic-gate			last;
473*7c478bd9Sstevel@tonic-gate		}
474*7c478bd9Sstevel@tonic-gate		$idx++;
475*7c478bd9Sstevel@tonic-gate	}
476*7c478bd9Sstevel@tonic-gate	error([6, gettext('Project "%s" does not exist'), $pname])
477*7c478bd9Sstevel@tonic-gate	    if (! $proj);
478*7c478bd9Sstevel@tonic-gate}
479*7c478bd9Sstevel@tonic-gate#
480*7c478bd9Sstevel@tonic-gate# If there are no modification options, simply reading the file, which
481*7c478bd9Sstevel@tonic-gate# includes parsing and verifying, is sufficient.
482*7c478bd9Sstevel@tonic-gate#
483*7c478bd9Sstevel@tonic-gateif (!$modify) {
484*7c478bd9Sstevel@tonic-gate	exit(0);
485*7c478bd9Sstevel@tonic-gate}
486*7c478bd9Sstevel@tonic-gate
487*7c478bd9Sstevel@tonic-gateforeach my $r (@$pf) {
488*7c478bd9Sstevel@tonic-gate	if ($r->{'name'} eq $pname) {
489*7c478bd9Sstevel@tonic-gate		$proj = $r;
490*7c478bd9Sstevel@tonic-gate		last;
491*7c478bd9Sstevel@tonic-gate	}
492*7c478bd9Sstevel@tonic-gate	$idx++;
493*7c478bd9Sstevel@tonic-gate}
494*7c478bd9Sstevel@tonic-gate
495*7c478bd9Sstevel@tonic-gate# Update the record as appropriate.
496*7c478bd9Sstevel@tonic-gate$err = [];
497*7c478bd9Sstevel@tonic-gate
498*7c478bd9Sstevel@tonic-gate# Set new project name.
499*7c478bd9Sstevel@tonic-gateif (defined($opt_l)) {
500*7c478bd9Sstevel@tonic-gate
501*7c478bd9Sstevel@tonic-gate	($ret, $value) = projent_parse_name($opt_l);
502*7c478bd9Sstevel@tonic-gate	if ($ret != 0) {
503*7c478bd9Sstevel@tonic-gate		push(@$err, @$value);
504*7c478bd9Sstevel@tonic-gate	} else {
505*7c478bd9Sstevel@tonic-gate		$proj->{'name'} = $value;
506*7c478bd9Sstevel@tonic-gate		if (!defined($opt_n)) {
507*7c478bd9Sstevel@tonic-gate			($ret, $tmperr) =
508*7c478bd9Sstevel@tonic-gate			    projent_validate_unique_name($proj, $pf);
509*7c478bd9Sstevel@tonic-gate			if ($ret != 0) {
510*7c478bd9Sstevel@tonic-gate				push(@$err, @$tmperr);
511*7c478bd9Sstevel@tonic-gate			}
512*7c478bd9Sstevel@tonic-gate		}
513*7c478bd9Sstevel@tonic-gate	}
514*7c478bd9Sstevel@tonic-gate}
515*7c478bd9Sstevel@tonic-gate
516*7c478bd9Sstevel@tonic-gate# Set new project id.
517*7c478bd9Sstevel@tonic-gateif (defined($opt_p)) {
518*7c478bd9Sstevel@tonic-gate
519*7c478bd9Sstevel@tonic-gate	($ret, $value) = projent_parse_projid($opt_p);
520*7c478bd9Sstevel@tonic-gate	if ($ret != 0) {
521*7c478bd9Sstevel@tonic-gate		push(@$err, @$value);
522*7c478bd9Sstevel@tonic-gate	} else {
523*7c478bd9Sstevel@tonic-gate		$proj->{'projid'} = $value;
524*7c478bd9Sstevel@tonic-gate
525*7c478bd9Sstevel@tonic-gate		# Check for dupicate.
526*7c478bd9Sstevel@tonic-gate		if ((!defined($opt_n)) && (!defined($opt_o))) {
527*7c478bd9Sstevel@tonic-gate			($ret, $tmperr) =
528*7c478bd9Sstevel@tonic-gate			    projent_validate_unique_id($proj, $pf);
529*7c478bd9Sstevel@tonic-gate			if ($ret != 0) {
530*7c478bd9Sstevel@tonic-gate				push(@$err, @$tmperr);
531*7c478bd9Sstevel@tonic-gate			}
532*7c478bd9Sstevel@tonic-gate		}
533*7c478bd9Sstevel@tonic-gate	}
534*7c478bd9Sstevel@tonic-gate}
535*7c478bd9Sstevel@tonic-gate
536*7c478bd9Sstevel@tonic-gate# Set new comment.
537*7c478bd9Sstevel@tonic-gateif (defined($opt_c)) {
538*7c478bd9Sstevel@tonic-gate
539*7c478bd9Sstevel@tonic-gate	($ret, $value) = projent_parse_comment($opt_c);
540*7c478bd9Sstevel@tonic-gate	if ($ret != 0) {
541*7c478bd9Sstevel@tonic-gate		push(@$err, @$value);
542*7c478bd9Sstevel@tonic-gate	} else {
543*7c478bd9Sstevel@tonic-gate		$proj->{'comment'} = $value;
544*7c478bd9Sstevel@tonic-gate	}
545*7c478bd9Sstevel@tonic-gate}
546*7c478bd9Sstevel@tonic-gate
547*7c478bd9Sstevel@tonic-gate# Set new users.
548*7c478bd9Sstevel@tonic-gateif (defined($opt_U)) {
549*7c478bd9Sstevel@tonic-gate
550*7c478bd9Sstevel@tonic-gate	my @sortlist;
551*7c478bd9Sstevel@tonic-gate	my $list;
552*7c478bd9Sstevel@tonic-gate	($ret, $list) = projent_parse_users($opt_U, {'allowspaces' => 1});
553*7c478bd9Sstevel@tonic-gate	if ($ret != 0) {
554*7c478bd9Sstevel@tonic-gate		push(@$err, @$list);
555*7c478bd9Sstevel@tonic-gate	} else {
556*7c478bd9Sstevel@tonic-gate		($ret, $list) =
557*7c478bd9Sstevel@tonic-gate		    merge_lists($list, $proj->{'userlist'}, $flags->{mode});
558*7c478bd9Sstevel@tonic-gate		if ($ret != 0) {
559*7c478bd9Sstevel@tonic-gate			push(@$err, @$list);
560*7c478bd9Sstevel@tonic-gate		} else {
561*7c478bd9Sstevel@tonic-gate			@sortlist = sort(@$list);
562*7c478bd9Sstevel@tonic-gate			$proj->{'userlist'} = \@sortlist;
563*7c478bd9Sstevel@tonic-gate		}
564*7c478bd9Sstevel@tonic-gate	}
565*7c478bd9Sstevel@tonic-gate}
566*7c478bd9Sstevel@tonic-gate
567*7c478bd9Sstevel@tonic-gate# Set new groups.
568*7c478bd9Sstevel@tonic-gateif (defined($opt_G)) {
569*7c478bd9Sstevel@tonic-gate
570*7c478bd9Sstevel@tonic-gate	my @sortlist;
571*7c478bd9Sstevel@tonic-gate	my $list;
572*7c478bd9Sstevel@tonic-gate	($ret, $list) = projent_parse_groups($opt_G, {'allowspaces' => 1});
573*7c478bd9Sstevel@tonic-gate	if ($ret != 0) {
574*7c478bd9Sstevel@tonic-gate		push(@$err, @$list);
575*7c478bd9Sstevel@tonic-gate	} else {
576*7c478bd9Sstevel@tonic-gate		($ret, $list) =
577*7c478bd9Sstevel@tonic-gate		    merge_lists($list, $proj->{'grouplist'}, $flags->{mode});
578*7c478bd9Sstevel@tonic-gate		if ($ret != 0) {
579*7c478bd9Sstevel@tonic-gate			push(@$err, @$list);
580*7c478bd9Sstevel@tonic-gate		} else {
581*7c478bd9Sstevel@tonic-gate			@sortlist = sort(@$list);
582*7c478bd9Sstevel@tonic-gate			$proj->{'grouplist'} = \@sortlist;
583*7c478bd9Sstevel@tonic-gate		}
584*7c478bd9Sstevel@tonic-gate	}
585*7c478bd9Sstevel@tonic-gate}
586*7c478bd9Sstevel@tonic-gate
587*7c478bd9Sstevel@tonic-gate# Set new attributes.
588*7c478bd9Sstevel@tonic-gatemy $attrib;
589*7c478bd9Sstevel@tonic-gatemy @attriblist;
590*7c478bd9Sstevel@tonic-gate
591*7c478bd9Sstevel@tonic-gateforeach $attrib (@opt_K) {
592*7c478bd9Sstevel@tonic-gate
593*7c478bd9Sstevel@tonic-gate	my $list;
594*7c478bd9Sstevel@tonic-gate	($ret, $list) = projent_parse_attributes($attrib, {'allowunits' => 1});
595*7c478bd9Sstevel@tonic-gate	if ($ret != 0) {
596*7c478bd9Sstevel@tonic-gate		push(@$err, @$list);
597*7c478bd9Sstevel@tonic-gate	} else {
598*7c478bd9Sstevel@tonic-gate		push(@attriblist, @$list);
599*7c478bd9Sstevel@tonic-gate	}
600*7c478bd9Sstevel@tonic-gate}
601*7c478bd9Sstevel@tonic-gate
602*7c478bd9Sstevel@tonic-gateif (@attriblist) {
603*7c478bd9Sstevel@tonic-gate	my @sortlist;
604*7c478bd9Sstevel@tonic-gate	my $list;
605*7c478bd9Sstevel@tonic-gate
606*7c478bd9Sstevel@tonic-gate	($ret, $list) =
607*7c478bd9Sstevel@tonic-gate	    merge_attribs(\@attriblist, $proj->{'attributelist'},
608*7c478bd9Sstevel@tonic-gate	    $flags->{mode});
609*7c478bd9Sstevel@tonic-gate	if ($ret != 0) {
610*7c478bd9Sstevel@tonic-gate		push(@$err, @$list);
611*7c478bd9Sstevel@tonic-gate	} else {
612*7c478bd9Sstevel@tonic-gate		@sortlist =
613*7c478bd9Sstevel@tonic-gate		    sort { $a->{'name'} cmp $b->{'name'} } @$list;
614*7c478bd9Sstevel@tonic-gate		$proj->{'attributelist'} = \@sortlist;
615*7c478bd9Sstevel@tonic-gate	}
616*7c478bd9Sstevel@tonic-gate}
617*7c478bd9Sstevel@tonic-gate
618*7c478bd9Sstevel@tonic-gate# Validate all projent fields.
619*7c478bd9Sstevel@tonic-gateif (!defined($opt_n)) {
620*7c478bd9Sstevel@tonic-gate	($ret, $tmperr) = projent_validate($proj, $flags);
621*7c478bd9Sstevel@tonic-gate	if ($ret != 0) {
622*7c478bd9Sstevel@tonic-gate		push(@$err, @$tmperr);
623*7c478bd9Sstevel@tonic-gate	}
624*7c478bd9Sstevel@tonic-gate}
625*7c478bd9Sstevel@tonic-gateif (@$err) {
626*7c478bd9Sstevel@tonic-gate	error(@$err);
627*7c478bd9Sstevel@tonic-gate}
628*7c478bd9Sstevel@tonic-gate
629*7c478bd9Sstevel@tonic-gate# Write out the project file.
630*7c478bd9Sstevel@tonic-gateif ($modify) {
631*7c478bd9Sstevel@tonic-gate
632*7c478bd9Sstevel@tonic-gate	#
633*7c478bd9Sstevel@tonic-gate	# Mark projent to write based on new values instead of
634*7c478bd9Sstevel@tonic-gate	# original line.
635*7c478bd9Sstevel@tonic-gate	#
636*7c478bd9Sstevel@tonic-gate	$proj->{'modified'} = 'true';
637*7c478bd9Sstevel@tonic-gate	umask(0000);
638*7c478bd9Sstevel@tonic-gate	sysopen($pfh, $tmpprojf, O_WRONLY | O_CREAT | O_EXCL, $mode) ||
639*7c478bd9Sstevel@tonic-gate	    error([10, gettext('Cannot create %s: %s'), $tmpprojf, $!]);
640*7c478bd9Sstevel@tonic-gate	projf_write($pfh, $pf);
641*7c478bd9Sstevel@tonic-gate	close($pfh);
642*7c478bd9Sstevel@tonic-gate
643*7c478bd9Sstevel@tonic-gate	# Update file attributes.
644*7c478bd9Sstevel@tonic-gate	if (!chown($uid, $gid, $tmpprojf)) {
645*7c478bd9Sstevel@tonic-gate		unlink($tmpprojf);
646*7c478bd9Sstevel@tonic-gate		error([10, gettext('Cannot set ownership of %s: %s'),
647*7c478bd9Sstevel@tonic-gate		    $tmpprojf, $!]);
648*7c478bd9Sstevel@tonic-gate	}
649*7c478bd9Sstevel@tonic-gate	if (! rename($tmpprojf, $projfile)) {
650*7c478bd9Sstevel@tonic-gate		unlink($tmpprojf);
651*7c478bd9Sstevel@tonic-gate		error([10, gettext('cannot rename %s to %s: %s'),
652*7c478bd9Sstevel@tonic-gate	            $tmpprojf, $projfile, $!]);
653*7c478bd9Sstevel@tonic-gate	}
654*7c478bd9Sstevel@tonic-gate
655*7c478bd9Sstevel@tonic-gate}
656*7c478bd9Sstevel@tonic-gate
657*7c478bd9Sstevel@tonic-gateexit(0);
658*7c478bd9Sstevel@tonic-gate
659*7c478bd9Sstevel@tonic-gate
660*7c478bd9Sstevel@tonic-gate
661*7c478bd9Sstevel@tonic-gate
662