1#!/usr/perl5/bin/perl -w
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License, Version 1.0 only
7# (the "License").  You may not use this file except in compliance
8# with the License.
9#
10# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11# or http://www.opensolaris.org/os/licensing.
12# See the License for the specific language governing permissions
13# and limitations under the License.
14#
15# When distributing Covered Code, include this CDDL HEADER in each
16# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17# If applicable, add the following below this CDDL HEADER, with the
18# fields enclosed by brackets "[]" replaced with your own identifying
19# information: Portions Copyright [yyyy] [name of copyright owner]
20#
21# CDDL HEADER END
22#
23#
24# Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
25# Use is subject to license terms.
26#
27#ident	"%Z%%M%	%I%	%E% SMI"
28#
29
30require 5.005;
31use strict;
32use locale;
33use Errno;
34use Fcntl;
35use File::Basename;
36use Getopt::Std;
37use Getopt::Long qw(:config no_ignore_case bundling);
38use POSIX qw(locale_h getuid getgid);
39use Sun::Solaris::Utils qw(textdomain gettext);
40use Sun::Solaris::Project qw(:ALL :PRIVATE);
41
42#
43# Print a usage message and exit.
44#
45sub usage
46{
47	my (@msg) = @_;
48	my $prog = basename($0);
49	my $space = ' ' x length($prog);
50	print(STDERR "$prog: @msg\n") if (@msg);
51	printf(STDERR gettext(
52	    "       %s [-n] [-f filename] [-p projid [-o]] [-c comment]\n".
53            "       %s [-U user[,user...]] [-G group[,group...]]\n".
54            "       %s [-K name[=value[,value...]]] project\n"),
55	       $prog, $space, $space);
56	exit(2);
57}
58
59#
60# Print a list of error messages and exit.
61#
62sub error
63{
64	my $exit = $_[0][0];
65	my $prog = basename($0) . ': ';
66	foreach my $err (@_) {
67		my ($e, $fmt, @args) = @$err;
68		printf(STDERR $prog . $fmt . "\n", @args);
69	}
70	exit($exit);
71}
72
73#
74# Main routine of script.
75#
76# Set the message locale.
77#
78setlocale(LC_ALL, '');
79textdomain(TEXT_DOMAIN);
80
81
82# Process command options and do some initial command-line validity checking.
83my ($pname, $flags);
84
85my $projfile = &PROJF_PATH;
86my $opt_n;
87my $opt_c;
88my $opt_o;
89my $opt_p;
90my $opt_U;
91my $opt_G;
92my @opt_K;
93
94GetOptions("f=s" => \$projfile,
95	   "n"   => \$opt_n,
96	   "c=s" => \$opt_c,
97	   "o"	 => \$opt_o,
98	   "p=s" => \$opt_p,
99	   "U=s" => \$opt_U,
100	   "G=s" => \$opt_G,
101	   "K=s" => \@opt_K) || usage();
102
103usage(gettext('Invalid command-line arguments')) if (@ARGV != 1);
104usage(gettext('No project name specified')) if (! defined($ARGV[0]));
105usage(gettext('-o requires -p projid to be specified'))
106    if (defined($opt_o) && ! defined($opt_p));
107
108$pname = $ARGV[0];
109my $maxpjid = 99;
110my $tmpprojf;
111
112
113# Fabricate an unique temporary filename.
114$tmpprojf = $projfile . ".tmp.$$";
115
116my $pfh;
117
118if (defined($opt_n)) {
119	$flags->{'validate'} = 'false';
120} else {
121	$flags->{'validate'} = 'true';
122}
123
124$flags->{'res'} = 'true';
125$flags->{'dup'} = 'true';
126
127my $pf;
128my ($mode, $uid, $gid);
129my $tmperr;
130my $ret;
131my $err;
132
133# Read the project file.  sysopen() is used so we can control the file mode.
134if (! sysopen($pfh, $projfile, O_RDONLY)) {
135	if ($! == Errno::ENOENT) {
136		$pf = [];
137		$mode = 0644;
138		$uid = getuid();
139		$gid = getgid();
140	} else {
141		error([10, gettext('Cannot open %s: %s'), $projfile, $!]);
142	}
143} else {
144	($mode, $uid, $gid) = (stat($pfh))[2,4,5];
145
146	($ret, $pf) = projf_read($pfh, $flags);
147	if ($ret != 0) {
148		error(@$pf);
149	}
150	close($pfh);
151	foreach (@$pf) {
152		$maxpjid = $_->{'projid'} if ($_->{'projid'} > $maxpjid);
153	}
154}
155
156
157my $proj = {};
158my ($value, $list);
159
160$proj->{'name'} = '';
161$proj->{'projid'} = $maxpjid + 1;;
162$proj->{'comment'} = '';
163$proj->{'userlist'} = [];
164$proj->{'grouplist'} = [];
165$proj->{'attributelist'} = [];
166$proj->{'modified'} = 'true';
167push(@$pf, $proj);
168
169# Update the record as appropriate.
170$err = [];
171
172($ret, $value) = projent_parse_name($pname);
173if ($ret != 0) {
174	push(@$err, @$value);
175} else {
176	$proj->{'name'} = $value;
177	if (!defined($opt_n)) {
178		($ret, $tmperr) =
179		    projent_validate_unique_name($proj, $pf);
180		if ($ret != 0) {
181			push(@$err, @$tmperr);
182		}
183	}
184}
185
186# Apply any changes due to options.
187if (defined($opt_p)) {
188
189	my ($ret, $value) = projent_parse_projid($opt_p);
190	if ($ret != 0) {
191		push(@$err, @$value);
192	} else {
193		$proj->{'projid'} = $value;
194		if (!defined($opt_n)) {
195			($ret, $tmperr) =
196			    projent_validate_projid($value, {});
197			if ($ret != 0) {
198				push(@$err, @$tmperr);
199			}
200		}
201		if ((!defined($opt_n)) && (!defined($opt_o))) {
202			($ret, $tmperr) =
203			    projent_validate_unique_id($proj, $pf);
204			if ($ret != 0) {
205				push(@$err, @$tmperr);
206			}
207		}
208	}
209}
210if (defined($opt_c)) {
211
212	my ($ret, $value) = projent_parse_comment($opt_c);
213	if ($ret != 0) {
214		push(@$err, @$value);
215	} else {
216		$proj->{'comment'} = $value;
217	}
218}
219if (defined($opt_U)) {
220
221	my @sortlist;
222	my ($ret, $list) = projent_parse_users($opt_U,
223	    { 'allowspaces' => 1 });
224	if ($ret != 0) {
225		push(@$err, @$list);
226	} else {
227		@sortlist = sort(@$list);
228		$proj->{'userlist'} = \@sortlist;
229	}
230}
231if (defined($opt_G)) {
232
233	my @sortlist;
234	my ($ret, $list) = projent_parse_groups($opt_G,
235	    { 'allowspaces' => 1 });
236	if ($ret != 0) {
237		push(@$err, @$list);
238	} else {
239		@sortlist = sort(@$list);
240		$proj->{'grouplist'} = \@sortlist;
241	}
242}
243
244my $attrib;
245my @attriblist;
246my @sortlist;
247
248# Support multiple instances of -K.
249foreach $attrib (@opt_K) {
250
251	my ($ret, $list) = projent_parse_attributes($attrib,
252	    {'allowunits' => 1});
253	if ($ret != 0) {
254		push(@$err, @$list);
255	} else {
256		push(@attriblist, @$list);
257	}
258}
259
260if (@attriblist) {
261	@sortlist = sort { $a->{'name'} cmp $b->{'name'} } @attriblist;
262	$proj->{'attributelist'} = \@sortlist;
263}
264
265# Validate project entry changes.
266if (!defined($opt_n)) {
267	($ret, $tmperr) = projent_validate($proj, $flags);
268	if ($ret != 0) {
269		push(@$err, @$tmperr);
270	}
271}
272if (@$err) {
273	error(@$err);
274}
275
276# Write out the project file.
277umask(0000);
278sysopen($pfh, $tmpprojf, O_WRONLY | O_CREAT | O_EXCL, $mode) ||
279    error([10, gettext('Cannot create %s: %s'), $tmpprojf, $!]);
280projf_write($pfh, $pf);
281close($pfh);
282if (!chown($uid, $gid, $tmpprojf)) {
283	unlink($tmpprojf);
284	error([10, gettext('Cannot set ownership of %s: %s'),
285	    $tmpprojf, $!]);
286}
287if (! rename($tmpprojf, $projfile)) {
288	unlink($tmpprojf);
289	error([10, gettext('cannot rename %s to %s: %s'),
290	    $tmpprojf, $projfile, $!]);
291}
292
293exit(0);
294