comm: add -Wall
[unleashed.git] / usr / src / cmd / projadd / projmod.pl
blob2d3f07e1a289e1a65f5c6773e8e36dd54c1b9dce
1 #!/usr/perl5/bin/perl -w
3 # CDDL HEADER START
5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
20 # CDDL HEADER END
23 # Copyright 2007 Sun Microsystems, Inc. All rights reserved.
24 # Use is subject to license terms.
26 #ident "%Z%%M% %I% %E% SMI"
29 require 5.005;
30 use strict;
31 use locale;
32 use Errno;
33 use Fcntl;
34 use File::Basename;
35 use Getopt::Std;
36 use Getopt::Long qw(:config no_ignore_case bundling);
37 use POSIX qw(locale_h);
38 use Sun::Solaris::Utils qw(textdomain gettext);
39 use Sun::Solaris::Project qw(:ALL :PRIVATE);
40 use Sun::Solaris::Task qw(:ALL);
43 # Print a usage message and exit.
45 sub usage
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 "Usage: %s [-n] [-f filename]\n"), $prog);
53 printf(STDERR gettext(
54 " %s [-n] [-A|-f filename] [-p projid [-o]] [-c comment]\n".
55 " %s [-a|-s|-r] [-U user[,user...]] [-G group[,group...]]\n".
56 " %s [-K name[=value[,value...]]] [-l new_projectname] ".
57 "project\n"), $prog, $space, $space, $space);
58 exit(2);
62 # Print a list of error messages and exit.
64 sub error
66 my $exit = $_[0][0];
67 my $prog = basename($0) . ': ';
68 foreach my $err (@_) {
69 my ($e, $fmt, @args) = @$err;
70 printf(STDERR $prog . $fmt . "\n", @args);
72 exit($exit);
76 # Merge an array of users/groups with an existing array. The array to merge
77 # is the first argument, an array ref is the second argument. The third
78 # argument is the mode which can be one of:
79 # add add all entries in the first arg to the second
80 # remove remove all entries in the first arg from the second
81 # replace replace the second arg by the first
82 # The resulting array is returned as a reference.
84 sub merge_lists
86 my ($new, $old, $mode) = @_;
87 my @err;
89 if ($mode eq 'add') {
90 my @merged = @$old;
91 my %look = map { $_ => 1 } @$old;
92 my @leftover;
93 foreach my $e (@$new) {
94 if (! exists($look{$e})) {
95 push(@merged, $e);
96 } else {
97 push(@leftover, $e);
100 if (@leftover) {
101 push(@err,
102 [6, gettext('Project already contains "%s"'),
103 join(',', @leftover)]);
104 return (1, \@err);
107 return(0, \@merged);
109 } elsif ($mode eq 'remove') {
111 my %seen;
112 my @dups = grep($seen{$_}++ == 1, @$new);
113 if (@dups) {
114 push(@err, [6, gettext('Duplicate names "%s"'),
115 join(',', @dups)]);
116 return (1, \@err);
118 my @merged;
119 my %look = map { $_ => 0 } @$new;
120 foreach my $e (@$old) {
121 if (exists($look{$e})) {
122 $look{$e}++;
123 } else {
124 push(@merged, $e);
127 my @leftover = grep(! $look{$_}, keys(%look));
128 if (@leftover) {
129 push(@err, [6,
130 gettext('Project does not contain "%s"'),
131 join(',', @leftover)]);
132 return (1, \@err);
134 return (0, \@merged);
136 } elsif ($mode eq 'replace' || $mode eq 'substitute') {
137 return (0, $new);
142 # merge_values(ref to listA, ref to listB, mode
144 # Merges the values in listB with the values in listA. Dups are not
145 # merged away, but instead are maintained.
147 # modes:
148 # add : add values in listB to listA
149 # remove: removes first instance of each value in listB from listA
151 sub merge_values
154 my ($new, $old, $mode) = @_;
155 my $undefined;
156 my @merged;
157 my $lastmerged;
158 my ($oldval, $newval);
159 my $found;
160 my @err;
162 if (!defined($old) && !defined($new)) {
163 return (0, $undefined);
166 if ($mode eq 'add') {
168 if (defined($old)) {
169 push(@merged, @$old);
171 if (defined($new)) {
172 push(@merged, @$new);
174 return (0, \@merged);
176 } elsif ($mode eq 'remove') {
178 $lastmerged = $old;
179 foreach $newval (@$new) {
180 $found = 0;
181 @merged = ();
182 foreach $oldval (@$lastmerged) {
183 if (!$found &&
184 projent_values_equal($newval, $oldval)) {
185 $found = 1;
186 } else {
187 push(@merged, $oldval);
191 if (!$found) {
192 push(@err, [6, gettext(
193 'Value "%s" not found'),
194 projent_values2string($newval)]);
196 @$lastmerged = @merged;
199 if (@err) {
200 return (1, \@err);
201 } else {
202 return (0, \@merged);
208 # merge_attribs(listA ref, listB ref, mode)
210 # Merge listB of attribute/values hash refs with listA
211 # Each hash ref should have keys "name" and "values"
213 # modes:
214 # add For each attribute in listB, add its values to
215 # the matching attribute in listA. If listA does not
216 # contain this attribute, add it.
218 # remove For each attribute in listB, remove its values from
219 # the matching attribute in listA. If all of an
220 # attributes values are removed, the attribute is removed.
221 # If the attribute in listB has no values, then the attribute
222 # and all of it's values are removed from listA
224 # substitute For each attribute in listB, replace the values of
225 # the matching attribute in listA with its values. If
226 # listA does not contain this attribute, add it.
228 # replace Return listB
230 # The resulting array is returned as a reference.
232 sub merge_attribs
234 my ($new, $old, $mode) = @_;
235 my @merged;
236 my @err;
237 my $ret;
238 my $tmp;
239 my $newattrib;
240 my $oldattrib;
241 my $values;
243 if ($mode eq 'add') {
245 my %oldhash;
246 push(@merged, @$old);
247 %oldhash = map { $_->{'name'} => $_ } @$old;
248 foreach $newattrib (@$new) {
250 $oldattrib = $oldhash{$newattrib->{'name'}};
251 if (defined($oldattrib)) {
252 ($ret, $tmp) = merge_values(
253 $newattrib->{'values'},
254 $oldattrib->{'values'},
255 $mode);
257 if ($ret != 0) {
258 push(@err, @$tmp);
259 } else {
260 $oldattrib->{'values'} = $tmp;
262 } else {
263 push(@merged, $newattrib);
266 if (@err) {
267 return (1, \@err);
268 } else {
269 return (0, \@merged);
272 } elsif ($mode eq 'remove') {
274 my %seen;
275 my @dups = grep($seen{$_}++ == 1, map { $_->{'name'} } @$new);
276 if (@dups) {
277 push(@err, [6, gettext(
278 'Duplicate Attributes "%s"'),
279 join(',', @dups)]);
280 return (1, \@err);
282 my %toremove = map { $_->{'name'} => $_ } @$new;
284 foreach $oldattrib (@$old) {
285 $newattrib = $toremove{$oldattrib->{'name'}};
286 if (!defined($newattrib)) {
288 push(@merged, $oldattrib);
290 } else {
291 if (defined($newattrib->{'values'})) {
292 ($ret, $tmp) = merge_values(
293 $newattrib->{'values'},
294 $oldattrib->{'values'},
295 $mode);
297 if ($ret != 0) {
298 push(@err, @$tmp);
299 } else {
300 $oldattrib->{'values'} = $tmp;
302 if (defined($tmp) && @$tmp) {
303 push(@merged, $oldattrib);
306 delete $toremove{$oldattrib->{'name'}};
309 foreach $tmp (keys(%toremove)) {
310 push(@err, [6,
311 gettext('Project does not contain "%s"'),
312 $tmp]);
315 if (@err) {
316 return (1, \@err);
317 } else {
318 return (0, \@merged);
321 } elsif ($mode eq 'substitute') {
323 my %oldhash;
324 push(@merged, @$old);
325 %oldhash = map { $_->{'name'} => $_ } @$old;
326 foreach $newattrib (@$new) {
328 $oldattrib = $oldhash{$newattrib->{'name'}};
329 if (defined($oldattrib)) {
331 $oldattrib->{'values'} =
332 $newattrib->{'values'};
334 } else {
335 push(@merged, $newattrib);
338 if (@err) {
339 return (1, \@err);
340 } else {
341 return (0, \@merged);
344 } elsif ($mode eq 'replace') {
345 return (0, $new);
350 # Main routine of script.
352 # Set the message locale.
354 setlocale(LC_ALL, '');
355 textdomain(TEXT_DOMAIN);
358 # Process command options and do some initial command-line validity checking.
359 my ($pname, $flags);
360 $flags = {};
361 my $modify = 0;
363 my $projfile;
364 my $opt_n;
365 my $opt_c;
366 my $opt_o;
367 my $opt_p;
368 my $opt_l;
369 my $opt_a;
370 my $opt_r;
371 my $opt_s;
372 my $opt_U;
373 my $opt_G;
374 my @opt_K;
375 my $opt_A;
377 GetOptions("f=s" => \$projfile,
378 "n" => \$opt_n,
379 "c=s" => \$opt_c,
380 "o" => \$opt_o,
381 "p=s" => \$opt_p,
382 "l=s" => \$opt_l,
383 "s" => \$opt_s,
384 "r" => \$opt_r,
385 "a" => \$opt_a,
386 "U=s" => \$opt_U,
387 "G=s" => \$opt_G,
388 "K=s" => \@opt_K,
389 "A" => \$opt_A) || usage();
391 usage(gettext('Invalid command-line arguments')) if (@ARGV > 1);
393 if ($opt_c || $opt_G || $opt_l || $opt_p || $opt_U || @opt_K || $opt_A) {
394 $modify = 1;
395 if (! defined($ARGV[0])) {
396 usage(gettext('No project name specified'));
400 if (!$modify && defined($ARGV[0])) {
401 usage(gettext('missing -c, -G, -l, -p, -U, or -K'));
404 if (defined($opt_A) && defined($projfile)) {
405 usage(gettext('-A and -f are mutually exclusive'));
408 if (! defined($projfile)) {
409 $projfile = &PROJF_PATH;
412 if ($modify && $projfile eq '-') {
413 usage(gettext('Cannot modify standard input'));
416 $pname = $ARGV[0];
417 usage(gettext('-o requires -p projid to be specified'))
418 if (defined($opt_o) && ! defined($opt_p));
419 usage(gettext('-a, -r, and -s are mutually exclusive'))
420 if ((defined($opt_a) && (defined($opt_r) || defined($opt_s))) ||
421 (defined($opt_r) && (defined($opt_a) || defined($opt_s))) ||
422 (defined($opt_s) && (defined($opt_a) || defined($opt_r))));
424 usage(gettext('-a and -r require -U users or -G groups to be specified'))
425 if ((defined($opt_a) || defined($opt_r) || defined($opt_s)) &&
426 ! (defined($opt_U) || defined($opt_G) || (@opt_K)));
429 if (defined($opt_a)) {
430 $flags->{mode} = 'add';
431 } elsif (defined($opt_r)) {
432 $flags->{mode} = 'remove';
433 } elsif (defined($opt_s)) {
434 $flags->{mode} = 'substitute';
435 } else {
436 $flags->{mode} = 'replace';
439 # Fabricate an unique temporary filename.
440 my $tmpprojf = $projfile . ".tmp.$$";
442 my $pfh;
445 # Read the project file. sysopen() is used so we can control the file mode.
446 # Handle special case for standard input.
447 if ($projfile eq '-') {
448 open($pfh, "<&=STDIN") or error( [10,
449 gettext('Cannot open standard input')]);
450 } elsif (! sysopen($pfh, $projfile, O_RDONLY)) {
451 error([10, gettext('Cannot open %s: %s'), $projfile, $!]);
453 my ($mode, $uid, $gid) = (stat($pfh))[2,4,5];
456 if ($opt_n) {
457 $flags->{'validate'} = 'false';
458 } else {
459 $flags->{'validate'} = 'true';
462 $flags->{'res'} = 'true';
463 $flags->{'dup'} = 'true';
465 my ($ret, $pf) = projf_read($pfh, $flags);
466 if ($ret != 0) {
467 error(@$pf);
469 close($pfh);
470 my $err;
471 my $tmperr;
472 my $value;
474 # Find existing record.
475 my ($proj, $idx);
476 $idx = 0;
478 if (defined($pname)) {
479 foreach my $r (@$pf) {
480 if ($r->{'name'} eq $pname) {
481 $proj = $r;
482 last;
484 $idx++;
486 error([6, gettext('Project "%s" does not exist'), $pname])
487 if (! $proj);
490 # If there are no modification options, simply reading the file, which
491 # includes parsing and verifying, is sufficient.
493 if (!$modify) {
494 exit(0);
497 foreach my $r (@$pf) {
498 if ($r->{'name'} eq $pname) {
499 $proj = $r;
500 last;
502 $idx++;
505 # Update the record as appropriate.
506 $err = [];
508 # Set new project name.
509 if (defined($opt_l)) {
511 ($ret, $value) = projent_parse_name($opt_l);
512 if ($ret != 0) {
513 push(@$err, @$value);
514 } else {
515 $proj->{'name'} = $value;
516 if (!defined($opt_n)) {
517 ($ret, $tmperr) =
518 projent_validate_unique_name($proj, $pf);
519 if ($ret != 0) {
520 push(@$err, @$tmperr);
526 # Set new project id.
527 if (defined($opt_p)) {
529 ($ret, $value) = projent_parse_projid($opt_p);
530 if ($ret != 0) {
531 push(@$err, @$value);
532 } else {
533 $proj->{'projid'} = $value;
535 # Check for dupicate.
536 if ((!defined($opt_n)) && (!defined($opt_o))) {
537 ($ret, $tmperr) =
538 projent_validate_unique_id($proj, $pf);
539 if ($ret != 0) {
540 push(@$err, @$tmperr);
546 # Set new comment.
547 if (defined($opt_c)) {
549 ($ret, $value) = projent_parse_comment($opt_c);
550 if ($ret != 0) {
551 push(@$err, @$value);
552 } else {
553 $proj->{'comment'} = $value;
557 # Set new users.
558 if (defined($opt_U)) {
560 my @sortlist;
561 my $list;
562 ($ret, $list) = projent_parse_users($opt_U, {'allowspaces' => 1});
563 if ($ret != 0) {
564 push(@$err, @$list);
565 } else {
566 ($ret, $list) =
567 merge_lists($list, $proj->{'userlist'}, $flags->{mode});
568 if ($ret != 0) {
569 push(@$err, @$list);
570 } else {
571 @sortlist = sort(@$list);
572 $proj->{'userlist'} = \@sortlist;
577 # Set new groups.
578 if (defined($opt_G)) {
580 my @sortlist;
581 my $list;
582 ($ret, $list) = projent_parse_groups($opt_G, {'allowspaces' => 1});
583 if ($ret != 0) {
584 push(@$err, @$list);
585 } else {
586 ($ret, $list) =
587 merge_lists($list, $proj->{'grouplist'}, $flags->{mode});
588 if ($ret != 0) {
589 push(@$err, @$list);
590 } else {
591 @sortlist = sort(@$list);
592 $proj->{'grouplist'} = \@sortlist;
597 # Set new attributes.
598 my $attrib;
599 my @attriblist;
601 foreach $attrib (@opt_K) {
603 my $list;
604 ($ret, $list) = projent_parse_attributes($attrib, {'allowunits' => 1});
605 if ($ret != 0) {
606 push(@$err, @$list);
607 } else {
608 push(@attriblist, @$list);
612 if (@attriblist) {
613 my @sortlist;
614 my $list;
616 ($ret, $list) =
617 merge_attribs(\@attriblist, $proj->{'attributelist'},
618 $flags->{mode});
619 if ($ret != 0) {
620 push(@$err, @$list);
621 } else {
622 @sortlist =
623 sort { $a->{'name'} cmp $b->{'name'} } @$list;
624 $proj->{'attributelist'} = \@sortlist;
628 # Validate all projent fields.
629 if (!defined($opt_n)) {
630 ($ret, $tmperr) = projent_validate($proj, $flags);
631 if ($ret != 0) {
632 push(@$err, @$tmperr);
635 if (@$err) {
636 error(@$err);
639 # Write out the project file.
640 if ($modify) {
643 # Mark projent to write based on new values instead of
644 # original line.
646 $proj->{'modified'} = 'true';
647 umask(0000);
648 sysopen($pfh, $tmpprojf, O_WRONLY | O_CREAT | O_EXCL, $mode) ||
649 error([10, gettext('Cannot create %s: %s'), $tmpprojf, $!]);
650 projf_write($pfh, $pf);
651 close($pfh);
653 # Update file attributes.
654 if (!chown($uid, $gid, $tmpprojf)) {
655 unlink($tmpprojf);
656 error([10, gettext('Cannot set ownership of %s: %s'),
657 $tmpprojf, $!]);
659 if (! rename($tmpprojf, $projfile)) {
660 unlink($tmpprojf);
661 error([10, gettext('cannot rename %s to %s: %s'),
662 $tmpprojf, $projfile, $!]);
667 if (defined($opt_A)) {
668 my $error;
670 if (($error = setproject($pname, "root", TASK_FINAL|TASK_PROJ_PURGE)) != 0) {
672 if ($error == SETPROJ_ERR_TASK) {
673 if ($!{EAGAIN}) {
674 error([5, gettext("resource control limit has ".
675 "been reached\n")]);
676 } elsif ($!{ESRCH}) {
677 error([5, gettext("user \"%s\" is not a member ".
678 "of project \"%s\"\n"), "root", $pname]);
679 } elsif ($!{EACCES}) {
680 error([5, gettext("the invoking task is final\n"
681 )]);
682 } else {
683 error([5, gettext("could not join project \"%s".
684 "\"\n"), $pname]);
687 } elsif ($error == SETPROJ_ERR_POOL) {
688 if ($!{EACCES}) {
689 error([5, gettext("no resource pool accepting ".
690 "default bindings exists for project \"%s".
691 "\"\n"), $pname]);
692 } elsif ($!{ESRCH}) {
693 error([5, gettext("specified resource pool ".
694 "does not exist for project \"%s\"\n"),
695 $pname]);
696 } else {
697 error([5, gettext("could not bind to default ".
698 "resource pool for project \"%s\"\n"),
699 $pname]);
702 } else {
704 # $error represents the position - within the semi-colon
705 # delimited $attribute - that generated the error
707 if ($error <= 0) {
708 error([5, gettext("setproject failed for ".
709 "project \"%s\"\n"), $pname]);
710 } else {
711 my ($name, $projid, $comment, $users_ref,
712 $groups_ref, $attr) = getprojbyname($pname);
713 my $attribute = ($attr =~
714 /(\S+?)=\S+?(?:;|\z)/g)[$error - 1];
716 if (!$attribute) {
717 error([5, gettext("warning, resource ".
718 "control assignment failed for ".
719 "project \"%s\" attribute %d\n"),
720 $pname, $error]);
721 } else {
722 error([5, gettext("warning, %s ".
723 "resource control assignment ".
724 "failed for project \"%s\"\n"),
725 $attribute, $pname]);
732 exit(0);