2 # Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved.
3 # Copyright (c) 2014 Racktop Systems.
7 # Project.pm provides the bootstrap for the Sun::Solaris::Project module, and
8 # also functions for reading, validating and writing out project(4) format
11 ################################################################################
20 use POSIX
qw(locale_h limits_h);
22 package Sun
::Solaris
::Project
;
27 XSLoader
::load
(__PACKAGE__
, $VERSION);
29 our (@EXPORT_OK, %EXPORT_TAGS);
30 my @constants = qw(MAXPROJID PROJNAME_MAX PROJF_PATH PROJECT_BUFSZ
31 SETPROJ_ERR_TASK SETPROJ_ERR_POOL);
32 my @syscalls = qw(getprojid);
33 my @libcalls = qw(setproject activeprojects getprojent setprojent endprojent
34 getprojbyname getprojbyid getdefaultproj fgetprojent inproj
36 my @private = qw(projf_read projf_write projf_validate projent_parse
37 projent_parse_name projent_validate_unique_name
38 projent_parse_projid projent_validate_unique_id
42 projent_parse_attributes
43 projent_validate projent_validate_projid
44 projent_values_equal projent_values2string);
46 @EXPORT_OK = (@constants, @syscalls, @libcalls, @private);
47 %EXPORT_TAGS = (CONSTANTS
=> \
@constants, SYSCALLS
=> \
@syscalls,
48 LIBCALLS
=> \
@libcalls, PRIVATE
=> \
@private, ALL
=> \
@EXPORT_OK);
50 use base
qw(Exporter);
51 use Sun
::Solaris
::Utils
qw(gettext);
54 # Set up default rules for validating rctls.
55 # These rules are not global-flag specific, but instead
56 # are the total set of allowable values on all rctls.
59 our $MaxNum = &RCTL_MAX_VALUE
;
66 foreach $name (split(' ', $Config{sig_name
})) {
71 'privs' => [ qw(basic privileged priv) ],
72 'actions' => [ qw(none deny sig) ],
73 'signals' => [ qw(ABRT XRES HUP STOP TERM KILL XFSZ XCPU),
85 $RctlRules{'__DEFAULT__'} = \
%rules;
88 # projf_combine_errors(errorA, errorlistB)
90 # Concatenates a single error with a list of errors. Each error in the new
91 # list will have a status matching the status of errorA.
95 # projf_combine_errors(
96 # [ 5, "Error on line %d, 10 ],
97 # [ [ 3, "Invalid Value %s", "foo" ],
98 # [ 6, "Duplicate Value %s", "bar" ]
101 # would return the list ref:
103 # [ [ 5, "Error on line %d: Invalid Value %s", 10, "foo" ],
104 # [ 5, "Error on line %d: Duplicate Value %s", 10, "bar" ]
107 # This function is used when a fuction wants to add more information to
108 # a list of errors returned by another function.
110 sub projf_combine_errors
113 my ($error1, $errorlist) = @_;
119 my ($err1, $fmt1, @args1);
120 my ($err2, $fmt2, @args2);
122 ($err1, $fmt1, @args1) = @
$error1;
123 foreach $error2 (@
$errorlist) {
125 ($err2, $fmt2, @args2) = @
$error2;
126 $newerror = [ $err1, $fmt1 . ', ' . $fmt2, @args1, @args2];
127 push(@newerrorlist, $newerror);
129 return (\
@newerrorlist);
133 # projf_read(filename, flags)
135 # Reads and parses a project(4) file, and returns a list of projent hashes.
138 # filename - file to read
139 # flags - hash ref of flags
141 # If flags contains key "validate", the project file entries will also be
142 # validated for run-time correctness If so, the flags ref is forwarded to
147 # Returns a ref to a list of projent hashes. See projent_parse() for a
148 # description of a projent hash.
153 my ($fh, $flags) = @_;
157 my ($projname, $projid, $comment, $users, $groups, $attributes);
161 my ($line, $origline, $next, @projf);
162 while (defined($line = <$fh>)) {
167 # Remove any line continuations and trailing newline.
172 if (length($line) > (&PROJECT_BUFSZ
- 2)) {
175 gettext
('Parse error on line %d, line too long'),
180 ($ret, $ref) = projent_parse
($line, {});
182 $ref = projf_combine_errors
(
183 [5, gettext
('Parse error on line %d'), $linenum],
192 # Cache original line to save original format if it is
195 $projent->{'line'} = $origline;
196 $projent->{'modified'} = 'false';
197 $projent->{'linenum'} = $linenum;
199 push(@projents, $projent);
202 if (defined($flags->{'validate'}) && ($flags->{'validate'} eq 'true')) {
203 ($ret, $ref) = projf_validate
(\
@projents, $flags);
213 return (0, \
@projents);
218 # projf_write(filehandle, projent list)
220 # Write a list of projent hashes to a file handle.
221 # projent's with key "modified" => false will be
222 # written using the "line" key. projent's with
223 # key "modified" => "true" will be written by
224 # constructing a new line based on their "name"
225 # "projid", "comment", "userlist", "grouplist"
226 # and "attributelist" keys.
230 my ($fh, $projents) = @_;
234 foreach $projent (@
$projents) {
236 if ($projent->{'modified'} eq 'false') {
237 $string = $projent->{'line'};
239 $string = projent_2string
($projent) . "\n";
246 # projent_parse(line)
248 # Functions for parsing the project file lines into projent hashes.
250 # Returns a number and a ref, one of:
252 # (0, ref to projent hash)
253 # (non-zero, ref to list of errors)
256 # allowspaces: allow spaces between user and group names.
257 # allowunits : allow units (K, M, etc), on rctl values.
259 # A projent hash contains the keys:
261 # "name" - string name of project
262 # "projid" - numeric id of project
263 # "comment" - comment string
264 # "users" - , seperated user list string
265 # "userlist" - list ref to list of user name strings
266 # "groups" - , seperated group list string
267 # "grouplist" - list ref to liset of group name strings
268 # "attributes" - ; seperated attribute list string
269 # "attributelist" - list ref to list of attribute refs
270 # (see projent_parse_attributes() for attribute ref)
275 my ($line, $flags) = @_;
279 my ($projname, $projid, $comment, $users, $groups, $attributes);
282 # Split fields of project line. split() is not used because
283 # we must enforce that there are 6 fields.
285 ($projname, $projid, $comment, $users, $groups, $attributes) =
287 /^([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*)$/;
289 # If there is not a complete match, nothing will be defined;
290 if (!defined($projname)) {
291 push(@errs, [5, gettext
(
292 'Incorrect number of fields. Should have 5 ":"\'s.')]);
294 # Get as many fields as we can.
295 ($projname, $projid, $comment, $users, $groups, $attributes) =
299 if (defined($projname)) {
300 $projent->{'name'} = $projname;
301 ($ret, $ref) = projent_parse_name
($projname);
306 if (defined($projid)) {
307 $projent->{'projid'} = $projid;
308 ($ret, $ref) = projent_parse_projid
($projid);
313 if (defined($comment)) {
314 $projent->{'comment'} = $comment;
315 ($ret, $ref) = projent_parse_comment
($comment);
320 if (defined($users)) {
321 $projent->{'users'} = $users;
322 ($ret, $ref) = projent_parse_users
($users, $flags);
326 $projent->{'userlist'} = $ref;
329 if (defined($groups)) {
330 $projent->{'groups'} = $groups;
331 ($ret, $ref) = projent_parse_groups
($groups, $flags);
335 $projent->{'grouplist'} = $ref;
338 if (defined($attributes)) {
339 $projent->{'attributes'} = $attributes;
340 ($ret, $ref) = projent_parse_attributes
($attributes, $flags);
344 $projent->{'attributelist'} = $ref;
352 return (0, $projent);
357 # Project name syntax checking.
359 sub projent_parse_name
364 if (!($projname =~ /^[[:alpha:]][[:alnum:]_.-]*$/)) {
365 push(@err, ([3, gettext
(
366 'Invalid project name "%s", contains invalid characters'),
370 if (length($projname) > &PROJNAME_MAX
) {
371 push(@err, ([3, gettext
(
372 'Invalid project name "%s", name too long'),
376 return (0, $projname);
380 # Projid syntax checking.
382 sub projent_parse_projid
387 # verify projid is a positive number, and less than UID_MAX
388 if (!($projid =~ /^\d+$/)) {
389 push(@err, [3, gettext
('Invalid projid "%s"'),
393 } elsif ($projid > POSIX
::INT_MAX
) {
394 push(@err, [3, gettext
('Invalid projid "%s": must be <= '.
405 # Project comment syntax checking.
407 sub projent_parse_comment
411 # no restrictions on comments
412 return (0, $comment);
416 # projent_parse_users(string, flags)
418 # Parses "," seperated list of users, and returns list ref to a list of
419 # user names. If flags contains key "allowspaces", then spaces are
420 # allowed between user names and ","'s.
422 sub projent_parse_users
424 my ($users, $flags) = @_;
430 if (exists($flags->{'allowspaces'})) {
431 $pattern = '\s*,\s*';
435 @userlist = split(/$pattern/, $users);
437 # Return empty list if there are no users.
439 return (0, \
@userlist);
442 # Verify each user name is the correct format for a valid user name.
443 foreach $user (@userlist) {
445 # Allow for wildcards.
446 if ($user eq '*' || $user eq '!*') {
450 # Allow for ! operator, usernames must begin with alpha-num,
451 # and contain alpha-num, '_', digits, '.', or '-'.
452 if (!($user =~ /^!?[[:alpha:]][[:alnum:]_.-]*$/)) {
453 push(@err, [3, gettext
('Invalid user name "%s"'),
461 return (0, \
@userlist);
466 # projent_parse_groups(string, flags)
468 # Parses "," seperated list of groups, and returns list ref to a list of
469 # groups names. If flags contains key "allowspaces", then spaces are
470 # allowed between group names and ","'s.
472 sub projent_parse_groups
474 my ($groups, $flags) = @_;
481 if (exists($flags->{'allowspaces'})) {
482 $pattern = '\s*,\s*';
486 @grouplist = split(/$pattern/, $groups);
488 # Return empty list if there are no groups.
490 return (0, \
@grouplist);
493 # Verify each group is the correct format for a valid group name.
494 foreach $group (@grouplist) {
496 # Allow for wildcards.
497 if ($group eq '*' || $group eq '!*') {
501 # Allow for ! operator, groupnames can contain only alpha
502 # characters and digits.
503 if (!($group =~ /^!?[[:alnum:]]+$/)) {
504 push(@err, [3, gettext
('Invalid group name "%s"'),
513 return (0, \
@grouplist);
518 # projent_tokenize_attribute_values(values)
520 # Values is the right hand side of a name=values attribute/values pair.
521 # This function splits the values string into a list of tokens. Tokens are
522 # valid string values and the characters ( ) ,
524 sub projent_tokenize_attribute_values
527 # This seperates the attribute string into higher level tokens
538 # Seperate tokens delimited by "(", ")", and ",".
539 @tokens = split(/([,()])/, $_[0], -1);
542 @newtokens = grep($_ ne '', @tokens);
544 foreach $token (@newtokens) {
545 if (!($token =~ /^[(),]$/ ||
546 $token =~ /^[[:alnum:]_.\/=+-]*$/)) {
547 push(@err, [3, gettext
(
548 'Invalid Character at or near "%s"'), $token]);
554 return (0, \
@newtokens);
559 # projent_parse_attribute_values(values)
561 # Values is the right hand side of a name=values attribute/values pair.
562 # This function parses the values string into a list of values. Each value
563 # can be either a scalar value, or a ref to another list of values.
564 # A ref to the list of values is returned.
566 sub projent_parse_attribute_values
569 # For some reason attribute values can be lists of values and
570 # sublists, which are scoped using ()'s. All values and sublists
571 # are delimited by ","'s. Empty values are lists are permitted.
573 # This function returns a reference to a list of values, each of
574 # which can be a scalar value, or a reference to a sublist. Sublists
575 # can contain both scalar values and references to furthur sublists.
588 push (@valuestack, []);
590 ($ret, $ref) = projent_tokenize_attribute_values
($values);
596 foreach $token (@
$tokens) {
598 push(@usedtokens, $token);
602 if ($prev eq ',' || $prev eq '(' ||
604 push(@
{$valuestack[$#valuestack]}, '');
611 if (!($prev eq '(' || $prev eq ',' ||
614 $line = join('', @usedtokens);
615 push(@err, [3, gettext
(
616 '"%s" <- "(" unexpected'),
624 push(@
{$valuestack[$#valuestack]}, $arrayref);
625 push(@valuestack, $arrayref);
632 if ($parendepth <= 0) {
634 $line = join('', @usedtokens);
635 push(@err, [3, gettext
(
636 '"%s" <- ")" unexpected'),
642 if ($prev eq ',' || $prev eq '(') {
643 push(@
{$valuestack[$#valuestack]}, '');
652 if (!($prev eq ',' || $prev eq '(' || $prev eq '')) {
653 $line = join('', @usedtokens);
654 push(@err, [3, gettext
(
655 '"%s" <- "%s" unexpected'),
661 push(@
{$valuestack[$#valuestack]}, $token);
666 if ($parendepth != 0) {
667 push(@err, [3, gettext
(
668 '"%s" <- ")" missing'),
673 if ($prev eq ',' || $prev eq '') {
674 push(@
{$valuestack[$#valuestack]}, '');
677 return (0, $valuestack[0]);
681 # projent_parse_attribute("name=values", $flags)
683 # $flags is a hash ref.
685 # 'allowunits' - allows numeric values to be scaled on certain attributes
687 # Returns a hash ref with keys:
689 # "name" - name of attribute
690 # "values" - ref to list of values.
691 # Each value can be a scalar value, or a ref to
692 # a sub-list of values.
694 sub projent_parse_attribute
696 my ($string, $flags) = @_;
698 my ($name, $stock, $values);
710 # pattern for matching stock symbols.
711 my $stockp = '[[:upper:]]{1,5}(?:.[[:upper:]]{1,5})?,';
712 # Match attribute with no value.
713 ($name, $stock) = $string =~
714 /^(($stockp)?[[:alpha:]][[:alnum:]_.-]*)$/;
716 $attribute->{'name'} = $name;
717 return (0, $attribute);
720 # Match attribute with value list.
721 ($name, $stock, $values) = $string =~
722 /^(($stockp)?[[:alpha:]][[:alnum:]_.-]*)=(.*)$/;
724 $attribute->{'name'} = $name;
726 if (!defined($values)) {
730 ($ret, $ref) = projent_parse_attribute_values
($values);
732 $ref = projf_combine_errors
(
734 gettext
('Invalid value on attribute "%s"'),
740 # Scale attributes than can be scaled.
741 if (exists($flags->{"allowunits"})) {
743 if ($name eq 'rcap.max-rss' &&
744 defined($ref->[0]) && !ref($ref->[0])) {
747 ($num, $modifier, $unit) =
748 projent_val2num
($ref->[0], $scale);
750 if (!defined($num)) {
752 if (defined($unit)) {
753 push(@err, [3, gettext
(
754 'rcap.max-rss has invalid '.
755 'unit "%s"'), $unit]);
757 push(@err, [3, gettext
(
758 'rcap.max-rss has invalid '.
759 'value "%s"'), $ref->[0]]);
761 } elsif ($num eq "OVERFLOW") {
762 push(@err, [3, gettext
( 'rcap.max-rss value '.
763 '"%s" exceeds maximum value "%s"'),
764 $ref->[0], $MaxNum]);
769 # Check hashed cache of rctl rules.
770 $rules = $RctlRules{$name};
771 if (!defined($rules)) {
773 # See if this is an resource control name, if so
776 ($rctlmax, $rctlflags) = rctl_get_info
($name);
777 if (defined($rctlmax)) {
778 $rules = proj_getrctlrules
(
779 $rctlmax, $rctlflags);
780 if (defined($rules)) {
781 $RctlRules{$name} = $rules;
789 # Scale values if this is an rctl.
790 if (defined ($rules) && ref($rules)) {
791 $flags->{'type'} = $rules->{'type'};
792 foreach $tuple (@
$ref) {
794 # Skip if tuple this is not a list.
798 # Skip if second element is not scalar.
799 if (!defined($tuple->[1]) ||
803 ($num, $modifier, $unit) =
804 projent_val2num
($tuple->[1],
807 if (!defined($num)) {
809 if (defined($unit)) {
810 push(@err, [3, gettext
(
816 push(@err, [3, gettext
(
822 } elsif ($num eq "OVERFLOW") {
823 push(@err, [3, gettext
(
824 'rctl %s value "%s" '.
825 'exceeds maximum value "%s"'),
826 $name, $tuple->[1], $MaxNum]);
833 $attribute->{'values'} = $ref;
837 return (0, $attribute);
841 # Attribute did not match name[=value,value...]
842 push(@err, [3, gettext
('Invalid attribute "%s"'), $string]);
848 # projent_parse_attributes("; seperated list of name=values pairs");
850 # Returns a list of attribute references, as returned by
851 # projent_parse_attribute().
853 sub projent_parse_attributes
855 my ($attributes, $flags) = @_;
857 my @attributestrings;
863 # Split up attributes by ";"'s.
864 @attributestrings = split(/;/, $attributes);
866 # If no attributes, return empty list.
867 if (!@attributestrings) {
868 return (0, \
@attributelist);
871 foreach $attributestring (@attributestrings) {
873 ($ret, $ref) = projent_parse_attribute
($attributestring,
878 push(@attributelist, $ref);
885 return (0, \
@attributelist);
891 # projent_values_equal(list A, list B)
893 # Given two references to lists of attribute values (as returned by
894 # projent_parse_attribute_values()), returns 1 if they are identical
895 # lists or 0 if they are not.
897 # XXX sub projent_values_equal;
898 sub projent_values_equal
906 if (ref($x) && ref($y)) {
908 if (scalar(@
$x) != scalar(@
$y)) {
911 foreach $itema (@
$x) {
913 $itemb = $y->[$index++];
915 if (!projent_values_equal
($itema, $itemb)) {
921 } elsif ((!ref($x) && (!ref($y)))) {
929 # Converts a list of values to a , seperated string, enclosing sublists
932 sub projent_values2string
939 if (!defined($values)) {
945 foreach $value (@
$values) {
949 '(' . projent_values2string
($value) . ')');
951 push(@valuelist, $value);
955 $string = join(',', @valuelist) ;
956 if (!defined($string)) {
963 # Converts a ref to an attribute hash with keys "name", and "values" to
964 # a string in the form "name=value,value...".
966 sub projent_attribute2string
968 my ($attribute) = @_;
971 $string = $attribute->{'name'};
973 if (ref($attribute->{'values'}) && @
{$attribute->{'values'}}) {
974 $string = $string . '=' .
975 projent_values2string
(($attribute->{'values'}));
981 # Converts a ref to a projent hash (as returned by projent_parse()) to
982 # a project(4) database entry line.
987 my @attributestrings;
990 foreach $attribute (@
{$projent->{'attributelist'}}) {
991 push(@attributestrings, projent_attribute2string
($attribute));
993 return (join(':', ($projent->{'name'},
994 $projent->{'projid'},
995 $projent->{'comment'},
996 join(',', @
{$projent->{'userlist'}}),
997 join(',', @
{$projent->{'grouplist'}}),
998 join(';', @attributestrings))));
1002 # projf_validate(ref to list of projents hashes, flags)
1004 # For each projent hash ref in the list, checks that users, groups, and pools
1005 # exists, and that known attributes are valid. Attributes matching rctl names
1006 # are verified to have valid values given that rctl's global flags and max
1011 # "res" - allow reserved project ids 0-99
1012 # "dup" - allow duplicate project ids
1016 my ($projents, $flags) = @_;
1026 # check for unique project names
1027 foreach $projent (@
$projents) {
1031 $seennames{$projent->{'name'}}++;
1032 $seenids{$projent->{'projid'}}++;
1034 if ($seennames{$projent->{'name'}} > 1) {
1035 push(@lineerr, [4, gettext
(
1036 'Duplicate project name "%s"'),
1037 $projent->{'name'}]);
1040 if (!defined($flags->{'dup'})) {
1041 if ($seenids{$projent->{'projid'}} > 1) {
1042 push(@lineerr, [4, gettext
(
1043 'Duplicate projid "%s"'),
1044 $projent->{'projid'}]);
1047 ($ret, $ref) = projent_validate
($projent, $flags);
1049 push(@lineerr, @
$ref);
1054 $ref = projf_combine_errors
([5, gettext
(
1055 'Validation error on line %d'),
1056 $projent->{'linenum'}], \
@lineerr);
1063 return (0, $projents);
1068 # projent_validate_unique_id(
1069 # ref to projent hash, ref to list of projent hashes)
1071 # Verifies that projid of the projent hash only exists once in the list of
1074 sub projent_validate_unique_id
1076 my ($projent, $projf, $idhash) = @_;
1079 my $projid = $projent->{'projid'};
1081 if (scalar(grep($_->{'projid'} eq $projid, @
$projf)) > 1) {
1083 push(@err, [4, gettext
('Duplicate projid "%s"'),
1087 return ($ret, \
@err);
1091 # projent_validate_unique_id(
1092 # ref to projent hash, ref to list of projent hashes)
1094 # Verifies that project name of the projent hash only exists once in the list
1095 # of projent hashes.
1097 # If the seconds argument is a hash ref, it is treated
1099 sub projent_validate_unique_name
1101 my ($projent, $projf, $namehash) = @_;
1104 my $pname = $projent->{'name'};
1106 if (scalar(grep($_->{'name'} eq $pname, @
$projf)) > 1) {
1109 [9, gettext
('Duplicate project name "%s"'), $pname]);
1112 return ($ret, \
@err);
1116 # projent_validate(ref to projents hash, flags)
1118 # Checks that users, groups, and pools exists, and that known attributes
1119 # are valid. Attributes matching rctl names are verified to have valid
1120 # values given that rctl's global flags and max value.
1124 # "allowspaces" - user and group list are allowed to contain whitespace
1125 # "res" - allow reserved project ids 0-99
1127 sub projent_validate
1129 my ($projent, $flags) = @_;
1135 projent_validate_name
($projent->{'name'}, $flags);
1140 projent_validate_projid
($projent->{'projid'}, $flags);
1145 projent_validate_comment
($projent->{'comment'}, $flags);
1150 projent_validate_users
($projent->{'userlist'}, $flags);
1155 projent_validate_groups
($projent->{'grouplist'}, $flags);
1159 ($ret, $ref) = projent_validate_attributes
(
1160 $projent->{'attributelist'}, $flags);
1165 my $string = projent_2string
($projent);
1166 if (length($string) > (&PROJECT_BUFSZ
- 2)) {
1167 push(@err, [3, gettext
('projent line too long')]);
1173 return (0, $projent);
1178 # projent_validate_name(name, flags)
1180 # does nothing, as any parse-able project name is valid
1182 sub projent_validate_name
1184 my ($name, $flags) = @_;
1192 # projent_validate_projid(projid, flags)
1194 # Validates that projid is within the valid range of numbers.
1196 # "res" - allow reserved projid's 0-99
1198 sub projent_validate_projid
1200 my ($projid, $flags) = @_;
1205 if (defined($flags->{'res'})) {
1211 if ($projid < $minprojid) {
1214 push(@err, [3, gettext
('Invalid projid "%s": '.
1220 return ($ret, \
@err);
1224 # projent_validate_comment(name, flags)
1226 # Does nothing, as any parse-able comment is valid.
1228 sub projent_validate_comment
1230 my ($comment, $flags) = @_;
1237 # projent_validate_users(ref to list of user names, flags)
1239 # Verifies that each username is either a valid glob, such
1240 # as * or !*, or is an existing user. flags is unused.
1241 # Also validates that there are no duplicates.
1243 sub projent_validate_users
1245 my ($users, $flags) = @_;
1251 foreach $user (@
$users) {
1253 if ($user eq '*' || $user eq '!*') {
1257 $username =~ s/^!//;
1259 if (!defined(getpwnam($username))) {
1262 gettext
('User "%s" does not exist'),
1268 my @dups = grep($seen{$_}++ == 1, @
$users);
1271 push(@err, [3, gettext
('Duplicate user names "%s"'),
1274 return ($ret, \
@err)
1278 # projent_validate_groups(ref to list of group names, flags)
1280 # Verifies that each groupname is either a valid glob, such
1281 # as * or !*, or is an existing group. flags is unused.
1282 # Also validates that there are no duplicates.
1284 sub projent_validate_groups
1286 my ($groups, $flags) = @_;
1292 foreach $group (@
$groups) {
1294 if ($group eq '*' || $group eq '!*') {
1298 $groupname = $group;
1299 $groupname =~ s/^!//;
1301 if (!defined(getgrnam($groupname))) {
1304 gettext
('Group "%s" does not exist'),
1310 my @dups = grep($seen{$_}++ == 1, @
$groups);
1313 push(@err, [3, gettext
('Duplicate group names "%s"'),
1317 return ($ret, \
@err)
1321 # projent_validate_attribute(attribute hash ref, flags)
1323 # Verifies that if the attribute's name is a known attribute or
1324 # resource control, that it contains a valid value.
1327 sub projent_validate_attribute
1329 my ($attribute, $flags) = @_;
1330 my $name = $attribute->{'name'};
1331 my $values = $attribute->{'values'};
1338 if (defined($values)) {
1339 $value = $values->[0];
1341 if ($name eq 'task.final') {
1343 if (defined($values)) {
1345 push(@errs, [3, gettext
(
1346 'task.final should not have value')]);
1349 # Need to rcap.max-rss needs to be a number
1350 } elsif ($name eq 'rcap.max-rss') {
1352 if (!defined($values)) {
1354 push(@errs, [3, gettext
(
1355 'rcap.max-rss missing value')]);
1356 } elsif (scalar(@
$values) != 1) {
1358 push(@errs, [3, gettext
(
1359 'rcap.max-rss should have single value')]);
1361 if (!defined($value) || ref($value)) {
1363 push(@errs, [3, gettext
(
1364 'rcap.max-rss has invalid value "%s"'),
1365 projent_values2string
($values)]);;
1366 } elsif ($value !~ /^\d+$/) {
1368 push(@errs, [3, gettext
(
1369 'rcap.max-rss is not an integer value: "%s"'),
1370 projent_values2string
($values)]);;
1371 } elsif ($value > $MaxNum) {
1373 push(@errs, [3, gettext
(
1374 'rcap.max-rss too large')]);
1377 } elsif ($name eq 'project.pool') {
1378 if (!defined($values)) {
1380 push(@errs, [3, gettext
(
1381 'project.pool missing value')]);
1382 } elsif (scalar(@
$values) != 1) {
1384 push(@errs, [3, gettext
(
1385 'project.pool should have single value')]);
1386 } elsif (!defined($value) || ref($value)) {
1388 push(@errs, [3, gettext
(
1389 'project.pool has invalid value "%s'),
1390 projent_values2string
($values)]);;
1391 } elsif (!($value =~ /^[[:alpha:]][[:alnum:]_.-]*$/)) {
1393 push(@errs, [3, gettext
(
1394 'project.pool: invalid pool name "%s"'),
1397 } elsif (pool_exists
($value) != 0) {
1399 push(@errs, [6, gettext
(
1400 'project.pool: pools not enabled or pool does '.
1410 # See if rctl rules exist for this attribute. If so, it
1411 # is an rctl and is checked for valid values.
1414 # check hashed cache of rctl rules.
1415 $rules = $RctlRules{$name};
1416 if (!defined($rules)) {
1419 # See if this is an resource control name, if so
1422 ($rctlmax, $rctlflags) = rctl_get_info
($name);
1423 if (defined($rctlmax)) {
1424 $rules = proj_getrctlrules
(
1425 $rctlmax, $rctlflags);
1426 if (defined($rules)) {
1427 $RctlRules{$name} = $rules;
1429 $RctlRules{$name} = "NOT AN RCTL";
1434 # If rules are defined, this is a resource control.
1435 if (defined($rules) && ref($rules)) {
1438 projent_validate_rctl
($attribute, $flags);
1445 return ($ret, \
@errs);
1449 # projent_validate_attributes(ref to attribute list, flags)
1451 # Validates all attributes in list of attribute references using
1452 # projent_validate_attribute. flags is unused.
1455 sub projent_validate_attributes
1457 my ($attributes, $flags) = @_;
1464 foreach $attribute (@
$attributes) {
1466 ($ret, $ref) = projent_validate_attribute
($attribute, $flags);
1474 my @dups = grep($seen{$_}++ == 1, map { $_->{'name'} } @
$attributes);
1477 push(@err, [3, gettext
('Duplicate attributes "%s"'),
1481 return ($result, \
@err);
1485 # projent_getrctlrules(max value, global flags)
1487 # given an rctls max value and global flags, returns a ref to a hash
1488 # of rctl rules that is used by projent_validate_rctl to validate an
1491 sub proj_getrctlrules
1493 my ($max, $flags) = @_;
1499 [ qw(ABRT XRES HUP STOP TERM KILL),
1507 $rctl->{'max'} = $max;
1509 if ($flags & &RCTL_GLOBAL_BYTES
) {
1510 $rctl->{'type'} = 'bytes';
1511 } elsif ($flags & &RCTL_GLOBAL_SECONDS
) {
1512 $rctl->{'type'} = 'seconds';
1513 } elsif ($flags & &RCTL_GLOBAL_COUNT
) {
1514 $rctl->{'type'} = 'count';
1516 $rctl->{'type'} = 'unknown';
1518 if ($flags & &RCTL_GLOBAL_NOBASIC
) {
1519 $rctl->{'privs'} = ['privileged', 'priv'];
1521 $rctl->{'privs'} = ['basic', 'privileged', 'priv'];
1524 if ($flags & &RCTL_GLOBAL_DENY_ALWAYS
) {
1525 $rctl->{'actions'} = ['deny'];
1527 } elsif ($flags & &RCTL_GLOBAL_DENY_NEVER
) {
1528 $rctl->{'actions'} = ['none'];
1530 $rctl->{'actions'} = ['none', 'deny'];
1533 if ($flags & &RCTL_GLOBAL_SIGNAL_NEVER
) {
1534 $rctl->{'signals'} = [];
1538 push(@
{$rctl->{'actions'}}, 'sig');
1540 if ($flags & &RCTL_GLOBAL_CPU_TIME
) {
1541 push(@
$signals, 'XCPU', '30');
1543 if ($flags & &RCTL_GLOBAL_FILE_SIZE
) {
1544 push(@
$signals, 'XFSZ', '31');
1546 $rctl->{'signals'} = $signals;
1552 # projent_val2num(scaled value, "seconds" | "count" | "bytes")
1554 # converts an integer or scaled value to an integer value.
1555 # returns (integer value, modifier character, unit character.
1557 # On failure, integer value is undefined. If the original
1558 # scaled value is a plain integer, modifier character and
1559 # unit character will be undefined.
1563 my ($val, $type) = @_;
1564 my %scaleM = ( k
=> 1000,
1568 p
=> 1000000000000000,
1569 e
=> 1000000000000000000);
1570 my %scaleB = ( k
=> 1024,
1574 p
=> 1125899906842624,
1575 e
=> 1152921504606846976);
1579 my ($num, $modifier, $unit);
1586 ($num, $modifier, $unit) = $val =~
1587 /^(\d+(?:\.\d+)?)(?i:([kmgtpe])?([bs])?)$/;
1590 if (!defined($num)) {
1591 return ($undefined, $undefined, $undefined);
1594 # Decimal number with no scaling modifier.
1595 if (!defined($modifier) && $num =~ /^\d+\.\d+/) {
1596 return ($undefined, $undefined, $undefined);
1599 if ($type eq 'bytes') {
1602 } elsif ($type eq 'seconds') {
1609 if (defined($unit)) {
1613 # So not succeed if unit is incorrect.
1614 if (!defined($exp_unit) && defined($unit)) {
1615 return ($undefined, $modifier, $unit);
1617 if (defined($unit) && $unit ne $exp_unit) {
1618 return ($undefined, $modifier, $unit);
1621 if (defined($modifier)) {
1623 $modifier = lc($modifier);
1624 $mul = $scale->{$modifier};
1628 # check for integer overflow.
1629 if ($num > $MaxNum) {
1630 return ("OVERFLOW", $modifier, $unit);
1633 # Trim numbers that are decimal equivalent to the maximum value
1634 # to the maximum integer value.
1636 if ($num == $MaxNum) {
1639 } elsif ($num < $MaxNum) {
1640 # convert any decimal numbers to an integer
1644 return ($num, $modifier, $unit);
1647 # projent_validate_rctl(ref to rctl attribute hash, flags)
1649 # verifies that the given rctl hash with keys "name" and
1650 # "values" contains valid values for the given name.
1653 sub projent_validate_rctl
1655 my ($rctl, $flags) = @_;
1669 my $sigstring; # Full signal string on right hand of signal=SIGXXX.
1670 my $signame; # Signal number or XXX part of SIGXXX.
1676 $name = $rctl->{'name'};
1677 $values = $rctl->{'values'};
1680 # Get the default rules for all rctls, and the specific rules for
1683 $allrules = $RctlRules{'__DEFAULT__'};
1684 $rules = $RctlRules{$name};
1686 if (!defined($rules) || !ref($rules)) {
1690 # Allow for no rctl values on rctl.
1691 if (!defined($values)) {
1695 # If values exist, make sure it is a list.
1696 if (!ref($values)) {
1698 push(@err, [3, gettext
(
1699 'rctl "%s" missing value'), $name]);
1703 foreach $value (@
$values) {
1705 # Each value should be a list.
1709 push(@err, [3, gettext
(
1710 'rctl "%s" value "%s" should be in ()\'s'),
1716 ($priv, $val, @actions) = @
$value;
1719 $valuestring = projent_values2string
([$value]);
1720 push(@err, [3, gettext
(
1721 'rctl "%s" value missing action "%s"'),
1722 $name, $valuestring]);
1725 if (!defined($priv)) {
1727 push(@err, [3, gettext
(
1728 'rctl "%s" value missing privilege "%s"'),
1729 $name, $valuestring]);
1731 } elsif (ref($priv)) {
1733 $valuestring = projent_values2string
([$priv]);
1734 push(@err, [3, gettext
(
1735 'rctl "%s" invalid privilege "%s"'),
1736 $name, $valuestring]);
1739 if (!(grep /^$priv$/, @
{$allrules->{'privs'}})) {
1742 push(@err, [3, gettext
(
1743 'rctl "%s" unknown privilege "%s"'),
1746 } elsif (!(grep /^$priv$/, @
{$rules->{'privs'}})) {
1749 push(@err, [3, gettext
(
1750 'rctl "%s" privilege not allowed '.
1751 '"%s"'), $name, $priv]);
1754 if (!defined($val)) {
1756 push(@err, [3, gettext
(
1757 'rctl "%s" missing value'), $name]);
1759 } elsif (ref($val)) {
1761 $valuestring = projent_values2string
([$val]);
1762 push(@err, [3, gettext
(
1763 'rctl "%s" invalid value "%s"'),
1764 $name, $valuestring]);
1767 if ($val !~ /^\d+$/) {
1769 push(@err, [3, gettext
(
1770 'rctl "%s" value "%s" is not '.
1771 'an integer'), $name, $val]);
1773 } elsif ($val > $rules->{'max'}) {
1775 push(@err, [3, gettext
(
1776 'rctl "%s" value "%s" exceeds '.
1777 'system limit'), $name, $val]);
1784 foreach $action (@actions) {
1789 projent_values2string
([$action]);
1790 push(@err, [3, gettext
(
1791 'rctl "%s" invalid action "%s"'),
1792 $name, $valuestring]);
1797 if ($action =~ /^sig(nal)?(=.*)?$/) {
1801 if (!(grep /^$action$/, @
{$allrules->{'actions'}})) {
1804 push(@err, [3, gettext
(
1805 'rctl "%s" unknown action "%s"'),
1809 } elsif (!(grep /^$action$/, @
{$rules->{'actions'}})) {
1812 push(@err, [3, gettext
(
1813 'rctl "%s" action not allowed "%s"'),
1818 if ($action eq 'none') {
1819 if ($nonecount >= 1) {
1822 push(@err, [3, gettext
(
1823 'rctl "%s" duplicate action '.
1829 if ($action eq 'deny') {
1830 if ($denycount >= 1) {
1833 push(@err, [3, gettext
(
1834 'rctl "%s" duplicate action '.
1841 # action must be signal
1842 if ($sigcount >= 1) {
1845 push(@err, [3, gettext
(
1846 'rctl "%s" duplicate action sig'),
1852 # Make sure signal is correct format, one of:
1860 ($sigstring) = $signal =~
1864 (?
:SIG
)?
[[:upper
:]]+(?
:[+-][123])?
1868 if (!defined($sigstring)) {
1870 push(@err, [3, gettext
(
1871 'rctl "%s" invalid signal "%s"'),
1876 $signame = $sigstring;
1877 $signame =~ s/SIG//;
1879 # Make sure specific signal is allowed.
1880 $siglist = $allrules->{'signals'};
1881 if (!(grep /^$signame$/, @
$siglist)) {
1883 push(@err, [3, gettext
(
1884 'rctl "%s" invalid signal "%s"'),
1888 $siglist = $rules->{'signals'};
1890 if (!(grep /^$signame$/, @
$siglist)) {
1892 push(@err, [3, gettext
(
1893 'rctl "%s" signal not allowed "%s"'),
1899 if ($nonecount && ($denycount || $sigcount)) {
1901 push(@err, [3, gettext
(
1902 'rctl "%s" action "none" specified with '.
1903 'other actions'), $name]);
1908 return ($ret, \
@err);
1910 return ($ret, \
@err);