2 # Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved.
6 # Project.pm provides the bootstrap for the Sun::Solaris::Project module, and
7 # also functions for reading, validating and writing out project(4) format
10 ################################################################################
19 use POSIX
qw(locale_h limits_h);
21 package Sun
::Solaris
::Project
;
26 XSLoader
::load
(__PACKAGE__
, $VERSION);
28 our (@EXPORT_OK, %EXPORT_TAGS);
29 my @constants = qw(MAXPROJID PROJNAME_MAX PROJF_PATH PROJECT_BUFSZ
30 SETPROJ_ERR_TASK SETPROJ_ERR_POOL);
31 my @syscalls = qw(getprojid);
32 my @libcalls = qw(setproject activeprojects getprojent setprojent endprojent
33 getprojbyname getprojbyid getdefaultproj fgetprojent inproj
35 my @private = qw(projf_read projf_write projf_validate projent_parse
36 projent_parse_name projent_validate_unique_name
37 projent_parse_projid projent_validate_unique_id
41 projent_parse_attributes
42 projent_validate projent_validate_projid
43 projent_values_equal projent_values2string);
45 @EXPORT_OK = (@constants, @syscalls, @libcalls, @private);
46 %EXPORT_TAGS = (CONSTANTS
=> \
@constants, SYSCALLS
=> \
@syscalls,
47 LIBCALLS
=> \
@libcalls, PRIVATE
=> \
@private, ALL
=> \
@EXPORT_OK);
49 use base
qw(Exporter);
50 use Sun
::Solaris
::Utils
qw(gettext);
53 # Set up default rules for validating rctls.
54 # These rules are not global-flag specific, but instead
55 # are the total set of allowable values on all rctls.
58 our $MaxNum = &RCTL_MAX_VALUE
;
65 foreach $name (split(' ', $Config{sig_name
})) {
70 'privs' => [ qw(basic privileged priv) ],
71 'actions' => [ qw(none deny sig) ],
72 'signals' => [ qw(ABRT XRES HUP STOP TERM KILL XFSZ XCPU),
84 $RctlRules{'__DEFAULT__'} = \
%rules;
87 # projf_combine_errors(errorA, errorlistB)
89 # Concatenates a single error with a list of errors. Each error in the new
90 # list will have a status matching the status of errorA.
94 # projf_combine_errors(
95 # [ 5, "Error on line %d, 10 ],
96 # [ [ 3, "Invalid Value %s", "foo" ],
97 # [ 6, "Duplicate Value %s", "bar" ]
100 # would return the list ref:
102 # [ [ 5, "Error on line %d: Invalid Value %s", 10, "foo" ],
103 # [ 5, "Error on line %d: Duplicate Value %s", 10, "bar" ]
106 # This function is used when a fuction wants to add more information to
107 # a list of errors returned by another function.
109 sub projf_combine_errors
112 my ($error1, $errorlist) = @_;
118 my ($err1, $fmt1, @args1);
119 my ($err2, $fmt2, @args2);
121 ($err1, $fmt1, @args1) = @
$error1;
122 foreach $error2 (@
$errorlist) {
124 ($err2, $fmt2, @args2) = @
$error2;
125 $newerror = [ $err1, $fmt1 . ', ' . $fmt2, @args1, @args2];
126 push(@newerrorlist, $newerror);
128 return (\
@newerrorlist);
132 # projf_read(filename, flags)
134 # Reads and parses a project(4) file, and returns a list of projent hashes.
137 # filename - file to read
138 # flags - hash ref of flags
140 # If flags contains key "validate", the project file entries will also be
141 # validated for run-time correctness If so, the flags ref is forwarded to
146 # Returns a ref to a list of projent hashes. See projent_parse() for a
147 # description of a projent hash.
152 my ($fh, $flags) = @_;
156 my ($projname, $projid, $comment, $users, $groups, $attributes);
160 my ($line, $origline, $next, @projf);
161 while (defined($line = <$fh>)) {
166 # Remove any line continuations and trailing newline.
171 if (length($line) > (&PROJECT_BUFSZ
- 2)) {
174 gettext
('Parse error on line %d, line too long'),
179 ($ret, $ref) = projent_parse
($line, {});
181 $ref = projf_combine_errors
(
182 [5, gettext
('Parse error on line %d'), $linenum],
191 # Cache original line to save original format if it is
194 $projent->{'line'} = $origline;
195 $projent->{'modified'} = 'false';
196 $projent->{'linenum'} = $linenum;
198 push(@projents, $projent);
201 if (defined($flags->{'validate'}) && ($flags->{'validate'} eq 'true')) {
202 ($ret, $ref) = projf_validate
(\
@projents, $flags);
212 return (0, \
@projents);
217 # projf_write(filehandle, projent list)
219 # Write a list of projent hashes to a file handle.
220 # projent's with key "modified" => false will be
221 # written using the "line" key. projent's with
222 # key "modified" => "true" will be written by
223 # constructing a new line based on their "name"
224 # "projid", "comment", "userlist", "grouplist"
225 # and "attributelist" keys.
229 my ($fh, $projents) = @_;
233 foreach $projent (@
$projents) {
235 if ($projent->{'modified'} eq 'false') {
236 $string = $projent->{'line'};
238 $string = projent_2string
($projent) . "\n";
245 # projent_parse(line)
247 # Functions for parsing the project file lines into projent hashes.
249 # Returns a number and a ref, one of:
251 # (0, ref to projent hash)
252 # (non-zero, ref to list of errors)
255 # allowspaces: allow spaces between user and group names.
256 # allowunits : allow units (K, M, etc), on rctl values.
258 # A projent hash contains the keys:
260 # "name" - string name of project
261 # "projid" - numeric id of project
262 # "comment" - comment string
263 # "users" - , seperated user list string
264 # "userlist" - list ref to list of user name strings
265 # "groups" - , seperated group list string
266 # "grouplist" - list ref to liset of group name strings
267 # "attributes" - ; seperated attribute list string
268 # "attributelist" - list ref to list of attribute refs
269 # (see projent_parse_attributes() for attribute ref)
274 my ($line, $flags) = @_;
278 my ($projname, $projid, $comment, $users, $groups, $attributes);
281 # Split fields of project line. split() is not used because
282 # we must enforce that there are 6 fields.
284 ($projname, $projid, $comment, $users, $groups, $attributes) =
286 /^([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*)$/;
288 # If there is not a complete match, nothing will be defined;
289 if (!defined($projname)) {
290 push(@errs, [5, gettext
(
291 'Incorrect number of fields. Should have 5 ":"\'s.')]);
293 # Get as many fields as we can.
294 ($projname, $projid, $comment, $users, $groups, $attributes) =
298 if (defined($projname)) {
299 $projent->{'name'} = $projname;
300 ($ret, $ref) = projent_parse_name
($projname);
305 if (defined($projid)) {
306 $projent->{'projid'} = $projid;
307 ($ret, $ref) = projent_parse_projid
($projid);
312 if (defined($comment)) {
313 $projent->{'comment'} = $comment;
314 ($ret, $ref) = projent_parse_comment
($comment);
319 if (defined($users)) {
320 $projent->{'users'} = $users;
321 ($ret, $ref) = projent_parse_users
($users, $flags);
325 $projent->{'userlist'} = $ref;
328 if (defined($groups)) {
329 $projent->{'groups'} = $groups;
330 ($ret, $ref) = projent_parse_groups
($groups, $flags);
334 $projent->{'grouplist'} = $ref;
337 if (defined($attributes)) {
338 $projent->{'attributes'} = $attributes;
339 ($ret, $ref) = projent_parse_attributes
($attributes, $flags);
343 $projent->{'attributelist'} = $ref;
351 return (0, $projent);
356 # Project name syntax checking.
358 sub projent_parse_name
363 if (!($projname =~ /^[[:alpha:]][[:alnum:]_.-]*$/)) {
364 push(@err, ([3, gettext
(
365 'Invalid project name "%s", contains invalid characters'),
369 if (length($projname) > &PROJNAME_MAX
) {
370 push(@err, ([3, gettext
(
371 'Invalid project name "%s", name too long'),
375 return (0, $projname);
379 # Projid syntax checking.
381 sub projent_parse_projid
386 # verify projid is a positive number, and less than UID_MAX
387 if (!($projid =~ /^\d+$/)) {
388 push(@err, [3, gettext
('Invalid projid "%s"'),
392 } elsif ($projid > POSIX
::INT_MAX
) {
393 push(@err, [3, gettext
('Invalid projid "%s": must be <= '.
404 # Project comment syntax checking.
406 sub projent_parse_comment
410 # no restrictions on comments
411 return (0, $comment);
415 # projent_parse_users(string, flags)
417 # Parses "," seperated list of users, and returns list ref to a list of
418 # user names. If flags contains key "allowspaces", then spaces are
419 # allowed between user names and ","'s.
421 sub projent_parse_users
423 my ($users, $flags) = @_;
429 if (exists($flags->{'allowspaces'})) {
430 $pattern = '\s*,\s*';
434 @userlist = split(/$pattern/, $users);
436 # Return empty list if there are no users.
438 return (0, \
@userlist);
441 # Verify each user name is the correct format for a valid user name.
442 foreach $user (@userlist) {
444 # Allow for wildcards.
445 if ($user eq '*' || $user eq '!*') {
449 # Allow for ! operator, usernames must begin with alpha-num,
450 # and contain alpha-num, '_', digits, '.', or '-'.
451 if (!($user =~ /^!?[[:alpha:]][[:alnum:]_.-]*$/)) {
452 push(@err, [3, gettext
('Invalid user name "%s"'),
460 return (0, \
@userlist);
465 # projent_parse_groups(string, flags)
467 # Parses "," seperated list of groups, and returns list ref to a list of
468 # groups names. If flags contains key "allowspaces", then spaces are
469 # allowed between group names and ","'s.
471 sub projent_parse_groups
473 my ($groups, $flags) = @_;
480 if (exists($flags->{'allowspaces'})) {
481 $pattern = '\s*,\s*';
485 @grouplist = split(/$pattern/, $groups);
487 # Return empty list if there are no groups.
489 return (0, \
@grouplist);
492 # Verify each group is the correct format for a valid group name.
493 foreach $group (@grouplist) {
495 # Allow for wildcards.
496 if ($group eq '*' || $group eq '!*') {
500 # Allow for ! operator, groupnames can contain only alpha
501 # characters and digits.
502 if (!($group =~ /^!?[[:alnum:]]+$/)) {
503 push(@err, [3, gettext
('Invalid group name "%s"'),
512 return (0, \
@grouplist);
517 # projent_tokenize_attribute_values(values)
519 # Values is the right hand side of a name=values attribute/values pair.
520 # This function splits the values string into a list of tokens. Tokens are
521 # valid string values and the characters ( ) ,
523 sub projent_tokenize_attribute_values
526 # This seperates the attribute string into higher level tokens
537 # Seperate tokens delimited by "(", ")", and ",".
538 @tokens = split(/([,()])/, $_[0], -1);
541 @newtokens = grep($_ ne '', @tokens);
543 foreach $token (@newtokens) {
544 if (!($token =~ /^[(),]$/ ||
545 $token =~ /^[[:alnum:]_.\/=+-]*$/)) {
546 push(@err, [3, gettext
(
547 'Invalid Character at or near "%s"'), $token]);
553 return (0, \
@newtokens);
558 # projent_parse_attribute_values(values)
560 # Values is the right hand side of a name=values attribute/values pair.
561 # This function parses the values string into a list of values. Each value
562 # can be either a scalar value, or a ref to another list of values.
563 # A ref to the list of values is returned.
565 sub projent_parse_attribute_values
568 # For some reason attribute values can be lists of values and
569 # sublists, which are scoped using ()'s. All values and sublists
570 # are delimited by ","'s. Empty values are lists are permitted.
572 # This function returns a reference to a list of values, each of
573 # which can be a scalar value, or a reference to a sublist. Sublists
574 # can contain both scalar values and references to furthur sublists.
587 push (@valuestack, []);
589 ($ret, $ref) = projent_tokenize_attribute_values
($values);
595 foreach $token (@
$tokens) {
597 push(@usedtokens, $token);
601 if ($prev eq ',' || $prev eq '(' ||
603 push(@
{$valuestack[$#valuestack]}, '');
610 if (!($prev eq '(' || $prev eq ',' ||
613 $line = join('', @usedtokens);
614 push(@err, [3, gettext
(
615 '"%s" <- "(" unexpected'),
623 push(@
{$valuestack[$#valuestack]}, $arrayref);
624 push(@valuestack, $arrayref);
631 if ($parendepth <= 0) {
633 $line = join('', @usedtokens);
634 push(@err, [3, gettext
(
635 '"%s" <- ")" unexpected'),
641 if ($prev eq ',' || $prev eq '(') {
642 push(@
{$valuestack[$#valuestack]}, '');
651 if (!($prev eq ',' || $prev eq '(' || $prev eq '')) {
652 $line = join('', @usedtokens);
653 push(@err, [3, gettext
(
654 '"%s" <- "%s" unexpected'),
660 push(@
{$valuestack[$#valuestack]}, $token);
665 if ($parendepth != 0) {
666 push(@err, [3, gettext
(
667 '"%s" <- ")" missing'),
672 if ($prev eq ',' || $prev eq '') {
673 push(@
{$valuestack[$#valuestack]}, '');
676 return (0, $valuestack[0]);
680 # projent_parse_attribute("name=values", $flags)
682 # $flags is a hash ref.
684 # 'allowunits' - allows numeric values to be scaled on certain attributes
686 # Returns a hash ref with keys:
688 # "name" - name of attribute
689 # "values" - ref to list of values.
690 # Each value can be a scalar value, or a ref to
691 # a sub-list of values.
693 sub projent_parse_attribute
695 my ($string, $flags) = @_;
697 my ($name, $stock, $values);
709 # pattern for matching stock symbols.
710 my $stockp = '[[:upper:]]{1,5}(?:.[[:upper:]]{1,5})?,';
711 # Match attribute with no value.
712 ($name, $stock) = $string =~
713 /^(($stockp)?[[:alpha:]][[:alnum:]_.-]*)$/;
715 $attribute->{'name'} = $name;
716 return (0, $attribute);
719 # Match attribute with value list.
720 ($name, $stock, $values) = $string =~
721 /^(($stockp)?[[:alpha:]][[:alnum:]_.-]*)=(.*)$/;
723 $attribute->{'name'} = $name;
725 if (!defined($values)) {
729 ($ret, $ref) = projent_parse_attribute_values
($values);
731 $ref = projf_combine_errors
(
733 gettext
('Invalid value on attribute "%s"'),
739 # Scale attributes than can be scaled.
740 if (exists($flags->{"allowunits"})) {
742 if ($name eq 'rcap.max-rss' &&
743 defined($ref->[0]) && !ref($ref->[0])) {
746 ($num, $modifier, $unit) =
747 projent_val2num
($ref->[0], $scale);
749 if (!defined($num)) {
751 if (defined($unit)) {
752 push(@err, [3, gettext
(
753 'rcap.max-rss has invalid '.
754 'unit "%s"'), $unit]);
756 push(@err, [3, gettext
(
757 'rcap.max-rss has invalid '.
758 'value "%s"'), $ref->[0]]);
760 } elsif ($num eq "OVERFLOW") {
761 push(@err, [3, gettext
( 'rcap.max-rss value '.
762 '"%s" exceeds maximum value "%s"'),
763 $ref->[0], $MaxNum]);
768 # Check hashed cache of rctl rules.
769 $rules = $RctlRules{$name};
770 if (!defined($rules)) {
772 # See if this is an resource control name, if so
775 ($rctlmax, $rctlflags) = rctl_get_info
($name);
776 if (defined($rctlmax)) {
777 $rules = proj_getrctlrules
(
778 $rctlmax, $rctlflags);
779 if (defined($rules)) {
780 $RctlRules{$name} = $rules;
788 # Scale values if this is an rctl.
789 if (defined ($rules) && ref($rules)) {
790 $flags->{'type'} = $rules->{'type'};
791 foreach $tuple (@
$ref) {
793 # Skip if tuple this is not a list.
797 # Skip if second element is not scalar.
798 if (!defined($tuple->[1]) ||
802 ($num, $modifier, $unit) =
803 projent_val2num
($tuple->[1],
806 if (!defined($num)) {
808 if (defined($unit)) {
809 push(@err, [3, gettext
(
815 push(@err, [3, gettext
(
821 } elsif ($num eq "OVERFLOW") {
822 push(@err, [3, gettext
(
823 'rctl %s value "%s" '.
824 'exceeds maximum value "%s"'),
825 $name, $tuple->[1], $MaxNum]);
832 $attribute->{'values'} = $ref;
836 return (0, $attribute);
840 # Attribute did not match name[=value,value...]
841 push(@err, [3, gettext
('Invalid attribute "%s"'), $string]);
847 # projent_parse_attributes("; seperated list of name=values pairs");
849 # Returns a list of attribute references, as returned by
850 # projent_parse_attribute().
852 sub projent_parse_attributes
854 my ($attributes, $flags) = @_;
856 my @attributestrings;
862 # Split up attributes by ";"'s.
863 @attributestrings = split(/;/, $attributes);
865 # If no attributes, return empty list.
866 if (!@attributestrings) {
867 return (0, \
@attributelist);
870 foreach $attributestring (@attributestrings) {
872 ($ret, $ref) = projent_parse_attribute
($attributestring,
877 push(@attributelist, $ref);
884 return (0, \
@attributelist);
890 # projent_values_equal(list A, list B)
892 # Given two references to lists of attribute values (as returned by
893 # projent_parse_attribute_values()), returns 1 if they are identical
894 # lists or 0 if they are not.
896 # XXX sub projent_values_equal;
897 sub projent_values_equal
905 if (ref($x) && ref($y)) {
907 if (scalar(@
$x) != scalar(@
$y)) {
910 foreach $itema (@
$x) {
912 $itemb = $y->[$index++];
914 if (!projent_values_equal
($itema, $itemb)) {
920 } elsif ((!ref($x) && (!ref($y)))) {
928 # Converts a list of values to a , seperated string, enclosing sublists
931 sub projent_values2string
938 if (!defined($values)) {
944 foreach $value (@
$values) {
948 '(' . projent_values2string
($value) . ')');
950 push(@valuelist, $value);
954 $string = join(',', @valuelist) ;
955 if (!defined($string)) {
962 # Converts a ref to an attribute hash with keys "name", and "values" to
963 # a string in the form "name=value,value...".
965 sub projent_attribute2string
967 my ($attribute) = @_;
970 $string = $attribute->{'name'};
972 if (ref($attribute->{'values'}) && @
{$attribute->{'values'}}) {
973 $string = $string . '=' .
974 projent_values2string
(($attribute->{'values'}));
980 # Converts a ref to a projent hash (as returned by projent_parse()) to
981 # a project(4) database entry line.
986 my @attributestrings;
989 foreach $attribute (@
{$projent->{'attributelist'}}) {
990 push(@attributestrings, projent_attribute2string
($attribute));
992 return (join(':', ($projent->{'name'},
993 $projent->{'projid'},
994 $projent->{'comment'},
995 join(',', @
{$projent->{'userlist'}}),
996 join(',', @
{$projent->{'grouplist'}}),
997 join(';', @attributestrings))));
1001 # projf_validate(ref to list of projents hashes, flags)
1003 # For each projent hash ref in the list, checks that users, groups, and pools
1004 # exists, and that known attributes are valid. Attributes matching rctl names
1005 # are verified to have valid values given that rctl's global flags and max
1010 # "res" - allow reserved project ids 0-99
1011 # "dup" - allow duplicate project ids
1015 my ($projents, $flags) = @_;
1025 # check for unique project names
1026 foreach $projent (@
$projents) {
1030 $seennames{$projent->{'name'}}++;
1031 $seenids{$projent->{'projid'}}++;
1033 if ($seennames{$projent->{'name'}} > 1) {
1034 push(@lineerr, [4, gettext
(
1035 'Duplicate project name "%s"'),
1036 $projent->{'name'}]);
1039 if (!defined($flags->{'dup'})) {
1040 if ($seenids{$projent->{'projid'}} > 1) {
1041 push(@lineerr, [4, gettext
(
1042 'Duplicate projid "%s"'),
1043 $projent->{'projid'}]);
1046 ($ret, $ref) = projent_validate
($projent, $flags);
1048 push(@lineerr, @
$ref);
1053 $ref = projf_combine_errors
([5, gettext
(
1054 'Validation error on line %d'),
1055 $projent->{'linenum'}], \
@lineerr);
1062 return (0, $projents);
1067 # projent_validate_unique_id(
1068 # ref to projent hash, ref to list of projent hashes)
1070 # Verifies that projid of the projent hash only exists once in the list of
1073 sub projent_validate_unique_id
1075 my ($projent, $projf, $idhash) = @_;
1078 my $projid = $projent->{'projid'};
1080 if (scalar(grep($_->{'projid'} eq $projid, @
$projf)) > 1) {
1082 push(@err, [4, gettext
('Duplicate projid "%s"'),
1086 return ($ret, \
@err);
1090 # projent_validate_unique_id(
1091 # ref to projent hash, ref to list of projent hashes)
1093 # Verifies that project name of the projent hash only exists once in the list
1094 # of projent hashes.
1096 # If the seconds argument is a hash ref, it is treated
1098 sub projent_validate_unique_name
1100 my ($projent, $projf, $namehash) = @_;
1103 my $pname = $projent->{'name'};
1105 if (scalar(grep($_->{'name'} eq $pname, @
$projf)) > 1) {
1108 [9, gettext
('Duplicate project name "%s"'), $pname]);
1111 return ($ret, \
@err);
1115 # projent_validate(ref to projents hash, flags)
1117 # Checks that users, groups, and pools exists, and that known attributes
1118 # are valid. Attributes matching rctl names are verified to have valid
1119 # values given that rctl's global flags and max value.
1123 # "allowspaces" - user and group list are allowed to contain whitespace
1124 # "res" - allow reserved project ids 0-99
1126 sub projent_validate
1128 my ($projent, $flags) = @_;
1134 projent_validate_name
($projent->{'name'}, $flags);
1139 projent_validate_projid
($projent->{'projid'}, $flags);
1144 projent_validate_comment
($projent->{'comment'}, $flags);
1149 projent_validate_users
($projent->{'userlist'}, $flags);
1154 projent_validate_groups
($projent->{'grouplist'}, $flags);
1158 ($ret, $ref) = projent_validate_attributes
(
1159 $projent->{'attributelist'}, $flags);
1164 my $string = projent_2string
($projent);
1165 if (length($string) > (&PROJECT_BUFSZ
- 2)) {
1166 push(@err, [3, gettext
('projent line too long')]);
1172 return (0, $projent);
1177 # projent_validate_name(name, flags)
1179 # does nothing, as any parse-able project name is valid
1181 sub projent_validate_name
1183 my ($name, $flags) = @_;
1191 # projent_validate_projid(projid, flags)
1193 # Validates that projid is within the valid range of numbers.
1195 # "res" - allow reserved projid's 0-99
1197 sub projent_validate_projid
1199 my ($projid, $flags) = @_;
1204 if (defined($flags->{'res'})) {
1210 if ($projid < $minprojid) {
1213 push(@err, [3, gettext
('Invalid projid "%s": '.
1219 return ($ret, \
@err);
1223 # projent_validate_comment(name, flags)
1225 # Does nothing, as any parse-able comment is valid.
1227 sub projent_validate_comment
1229 my ($comment, $flags) = @_;
1236 # projent_validate_users(ref to list of user names, flags)
1238 # Verifies that each username is either a valid glob, such
1239 # as * or !*, or is an existing user. flags is unused.
1240 # Also validates that there are no duplicates.
1242 sub projent_validate_users
1244 my ($users, $flags) = @_;
1250 foreach $user (@
$users) {
1252 if ($user eq '*' || $user eq '!*') {
1256 $username =~ s/^!//;
1258 if (!defined(getpwnam($username))) {
1261 gettext
('User "%s" does not exist'),
1267 my @dups = grep($seen{$_}++ == 1, @
$users);
1270 push(@err, [3, gettext
('Duplicate user names "%s"'),
1273 return ($ret, \
@err)
1277 # projent_validate_groups(ref to list of group names, flags)
1279 # Verifies that each groupname is either a valid glob, such
1280 # as * or !*, or is an existing group. flags is unused.
1281 # Also validates that there are no duplicates.
1283 sub projent_validate_groups
1285 my ($groups, $flags) = @_;
1291 foreach $group (@
$groups) {
1293 if ($group eq '*' || $group eq '!*') {
1297 $groupname = $group;
1298 $groupname =~ s/^!//;
1300 if (!defined(getgrnam($groupname))) {
1303 gettext
('Group "%s" does not exist'),
1309 my @dups = grep($seen{$_}++ == 1, @
$groups);
1312 push(@err, [3, gettext
('Duplicate group names "%s"'),
1316 return ($ret, \
@err)
1320 # projent_validate_attribute(attribute hash ref, flags)
1322 # Verifies that if the attribute's name is a known attribute or
1323 # resource control, that it contains a valid value.
1326 sub projent_validate_attribute
1328 my ($attribute, $flags) = @_;
1329 my $name = $attribute->{'name'};
1330 my $values = $attribute->{'values'};
1337 if (defined($values)) {
1338 $value = $values->[0];
1340 if ($name eq 'task.final') {
1342 if (defined($values)) {
1344 push(@errs, [3, gettext
(
1345 'task.final should not have value')]);
1348 # Need to rcap.max-rss needs to be a number
1349 } elsif ($name eq 'rcap.max-rss') {
1351 if (!defined($values)) {
1353 push(@errs, [3, gettext
(
1354 'rcap.max-rss missing value')]);
1355 } elsif (scalar(@
$values) != 1) {
1357 push(@errs, [3, gettext
(
1358 'rcap.max-rss should have single value')]);
1360 if (!defined($value) || ref($value)) {
1362 push(@errs, [3, gettext
(
1363 'rcap.max-rss has invalid value "%s"'),
1364 projent_values2string
($values)]);;
1365 } elsif ($value !~ /^\d+$/) {
1367 push(@errs, [3, gettext
(
1368 'rcap.max-rss is not an integer value: "%s"'),
1369 projent_values2string
($values)]);;
1370 } elsif ($value > $MaxNum) {
1372 push(@errs, [3, gettext
(
1373 'rcap.max-rss too large')]);
1376 } elsif ($name eq 'project.pool') {
1377 if (!defined($values)) {
1379 push(@errs, [3, gettext
(
1380 'project.pool missing value')]);
1381 } elsif (scalar(@
$values) != 1) {
1383 push(@errs, [3, gettext
(
1384 'project.pool should have single value')]);
1385 } elsif (!defined($value) || ref($value)) {
1387 push(@errs, [3, gettext
(
1388 'project.pool has invalid value "%s'),
1389 projent_values2string
($values)]);;
1390 } elsif (!($value =~ /^[[:alpha:]][[:alnum:]_.-]*$/)) {
1392 push(@errs, [3, gettext
(
1393 'project.pool: invalid pool name "%s"'),
1396 } elsif (pool_exists
($value) != 0) {
1398 push(@errs, [6, gettext
(
1399 'project.pool: pools not enabled or pool does '.
1409 # See if rctl rules exist for this attribute. If so, it
1410 # is an rctl and is checked for valid values.
1413 # check hashed cache of rctl rules.
1414 $rules = $RctlRules{$name};
1415 if (!defined($rules)) {
1418 # See if this is an resource control name, if so
1421 ($rctlmax, $rctlflags) = rctl_get_info
($name);
1422 if (defined($rctlmax)) {
1423 $rules = proj_getrctlrules
(
1424 $rctlmax, $rctlflags);
1425 if (defined($rules)) {
1426 $RctlRules{$name} = $rules;
1428 $RctlRules{$name} = "NOT AN RCTL";
1433 # If rules are defined, this is a resource control.
1434 if (defined($rules) && ref($rules)) {
1437 projent_validate_rctl
($attribute, $flags);
1444 return ($ret, \
@errs);
1448 # projent_validate_attributes(ref to attribute list, flags)
1450 # Validates all attributes in list of attribute references using
1451 # projent_validate_attribute. flags is unused.
1454 sub projent_validate_attributes
1456 my ($attributes, $flags) = @_;
1463 foreach $attribute (@
$attributes) {
1465 ($ret, $ref) = projent_validate_attribute
($attribute, $flags);
1473 my @dups = grep($seen{$_}++ == 1, map { $_->{'name'} } @
$attributes);
1476 push(@err, [3, gettext
('Duplicate attributes "%s"'),
1480 return ($result, \
@err);
1484 # projent_getrctlrules(max value, global flags)
1486 # given an rctls max value and global flags, returns a ref to a hash
1487 # of rctl rules that is used by projent_validate_rctl to validate an
1490 sub proj_getrctlrules
1492 my ($max, $flags) = @_;
1498 [ qw(ABRT XRES HUP STOP TERM KILL),
1506 $rctl->{'max'} = $max;
1508 if ($flags & &RCTL_GLOBAL_BYTES
) {
1509 $rctl->{'type'} = 'bytes';
1510 } elsif ($flags & &RCTL_GLOBAL_SECONDS
) {
1511 $rctl->{'type'} = 'seconds';
1512 } elsif ($flags & &RCTL_GLOBAL_COUNT
) {
1513 $rctl->{'type'} = 'count';
1515 $rctl->{'type'} = 'unknown';
1517 if ($flags & &RCTL_GLOBAL_NOBASIC
) {
1518 $rctl->{'privs'} = ['privileged', 'priv'];
1520 $rctl->{'privs'} = ['basic', 'privileged', 'priv'];
1523 if ($flags & &RCTL_GLOBAL_DENY_ALWAYS
) {
1524 $rctl->{'actions'} = ['deny'];
1526 } elsif ($flags & &RCTL_GLOBAL_DENY_NEVER
) {
1527 $rctl->{'actions'} = ['none'];
1529 $rctl->{'actions'} = ['none', 'deny'];
1532 if ($flags & &RCTL_GLOBAL_SIGNAL_NEVER
) {
1533 $rctl->{'signals'} = [];
1537 push(@
{$rctl->{'actions'}}, 'sig');
1539 if ($flags & &RCTL_GLOBAL_CPU_TIME
) {
1540 push(@
$signals, 'XCPU', '30');
1542 if ($flags & &RCTL_GLOBAL_FILE_SIZE
) {
1543 push(@
$signals, 'XFSZ', '31');
1545 $rctl->{'signals'} = $signals;
1551 # projent_val2num(scaled value, "seconds" | "count" | "bytes")
1553 # converts an integer or scaled value to an integer value.
1554 # returns (integer value, modifier character, unit character.
1556 # On failure, integer value is undefined. If the original
1557 # scaled value is a plain integer, modifier character and
1558 # unit character will be undefined.
1562 my ($val, $type) = @_;
1563 my %scaleM = ( k
=> 1000,
1567 p
=> 1000000000000000,
1568 e
=> 1000000000000000000);
1569 my %scaleB = ( k
=> 1024,
1573 p
=> 1125899906842624,
1574 e
=> 1152921504606846976);
1578 my ($num, $modifier, $unit);
1585 ($num, $modifier, $unit) = $val =~
1586 /^(\d+(?:\.\d+)?)(?i:([kmgtpe])?([bs])?)$/;
1589 if (!defined($num)) {
1590 return ($undefined, $undefined, $undefined);
1593 # Decimal number with no scaling modifier.
1594 if (!defined($modifier) && $num =~ /^\d+\.\d+/) {
1595 return ($undefined, $undefined, $undefined);
1598 if ($type eq 'bytes') {
1601 } elsif ($type eq 'seconds') {
1608 if (defined($unit)) {
1612 # So not succeed if unit is incorrect.
1613 if (!defined($exp_unit) && defined($unit)) {
1614 return ($undefined, $modifier, $unit);
1616 if (defined($unit) && $unit ne $exp_unit) {
1617 return ($undefined, $modifier, $unit);
1620 if (defined($modifier)) {
1622 $modifier = lc($modifier);
1623 $mul = $scale->{$modifier};
1627 # check for integer overflow.
1628 if ($num > $MaxNum) {
1629 return ("OVERFLOW", $modifier, $unit);
1632 # Trim numbers that are decimal equivalent to the maximum value
1633 # to the maximum integer value.
1635 if ($num == $MaxNum) {
1638 } elsif ($num < $MaxNum) {
1639 # convert any decimal numbers to an integer
1643 return ($num, $modifier, $unit);
1646 # projent_validate_rctl(ref to rctl attribute hash, flags)
1648 # verifies that the given rctl hash with keys "name" and
1649 # "values" contains valid values for the given name.
1652 sub projent_validate_rctl
1654 my ($rctl, $flags) = @_;
1668 my $sigstring; # Full signal string on right hand of signal=SIGXXX.
1669 my $signame; # Signal number or XXX part of SIGXXX.
1675 $name = $rctl->{'name'};
1676 $values = $rctl->{'values'};
1679 # Get the default rules for all rctls, and the specific rules for
1682 $allrules = $RctlRules{'__DEFAULT__'};
1683 $rules = $RctlRules{$name};
1685 if (!defined($rules) || !ref($rules)) {
1689 # Allow for no rctl values on rctl.
1690 if (!defined($values)) {
1694 # If values exist, make sure it is a list.
1695 if (!ref($values)) {
1697 push(@err, [3, gettext
(
1698 'rctl "%s" missing value'), $name]);
1702 foreach $value (@
$values) {
1704 # Each value should be a list.
1708 push(@err, [3, gettext
(
1709 'rctl "%s" value "%s" should be in ()\'s'),
1715 ($priv, $val, @actions) = @
$value;
1718 $valuestring = projent_values2string
([$value]);
1719 push(@err, [3, gettext
(
1720 'rctl "%s" value missing action "%s"'),
1721 $name, $valuestring]);
1724 if (!defined($priv)) {
1726 push(@err, [3, gettext
(
1727 'rctl "%s" value missing privilege "%s"'),
1728 $name, $valuestring]);
1730 } elsif (ref($priv)) {
1732 $valuestring = projent_values2string
([$priv]);
1733 push(@err, [3, gettext
(
1734 'rctl "%s" invalid privilege "%s"'),
1735 $name, $valuestring]);
1738 if (!(grep /^$priv$/, @
{$allrules->{'privs'}})) {
1741 push(@err, [3, gettext
(
1742 'rctl "%s" unknown privilege "%s"'),
1745 } elsif (!(grep /^$priv$/, @
{$rules->{'privs'}})) {
1748 push(@err, [3, gettext
(
1749 'rctl "%s" privilege not allowed '.
1750 '"%s"'), $name, $priv]);
1753 if (!defined($val)) {
1755 push(@err, [3, gettext
(
1756 'rctl "%s" missing value'), $name]);
1758 } elsif (ref($val)) {
1760 $valuestring = projent_values2string
([$val]);
1761 push(@err, [3, gettext
(
1762 'rctl "%s" invalid value "%s"'),
1763 $name, $valuestring]);
1766 if ($val !~ /^\d+$/) {
1768 push(@err, [3, gettext
(
1769 'rctl "%s" value "%s" is not '.
1770 'an integer'), $name, $val]);
1772 } elsif ($val > $rules->{'max'}) {
1774 push(@err, [3, gettext
(
1775 'rctl "%s" value "%s" exceeds '.
1776 'system limit'), $name, $val]);
1783 foreach $action (@actions) {
1788 projent_values2string
([$action]);
1789 push(@err, [3, gettext
(
1790 'rctl "%s" invalid action "%s"'),
1791 $name, $valuestring]);
1796 if ($action =~ /^sig(nal)?(=.*)?$/) {
1800 if (!(grep /^$action$/, @
{$allrules->{'actions'}})) {
1803 push(@err, [3, gettext
(
1804 'rctl "%s" unknown action "%s"'),
1808 } elsif (!(grep /^$action$/, @
{$rules->{'actions'}})) {
1811 push(@err, [3, gettext
(
1812 'rctl "%s" action not allowed "%s"'),
1817 if ($action eq 'none') {
1818 if ($nonecount >= 1) {
1821 push(@err, [3, gettext
(
1822 'rctl "%s" duplicate action '.
1828 if ($action eq 'deny') {
1829 if ($denycount >= 1) {
1832 push(@err, [3, gettext
(
1833 'rctl "%s" duplicate action '.
1840 # action must be signal
1841 if ($sigcount >= 1) {
1844 push(@err, [3, gettext
(
1845 'rctl "%s" duplicate action sig'),
1851 # Make sure signal is correct format, one of:
1859 ($sigstring) = $signal =~
1863 (?
:SIG
)?
[[:upper
:]]+(?
:[+-][123])?
1867 if (!defined($sigstring)) {
1869 push(@err, [3, gettext
(
1870 'rctl "%s" invalid signal "%s"'),
1875 $signame = $sigstring;
1876 $signame =~ s/SIG//;
1878 # Make sure specific signal is allowed.
1879 $siglist = $allrules->{'signals'};
1880 if (!(grep /^$signame$/, @
$siglist)) {
1882 push(@err, [3, gettext
(
1883 'rctl "%s" invalid signal "%s"'),
1887 $siglist = $rules->{'signals'};
1889 if (!(grep /^$signame$/, @
$siglist)) {
1891 push(@err, [3, gettext
(
1892 'rctl "%s" signal not allowed "%s"'),
1898 if ($nonecount && ($denycount || $sigcount)) {
1900 push(@err, [3, gettext
(
1901 'rctl "%s" action "none" specified with '.
1902 'other actions'), $name]);
1907 return ($ret, \
@err);
1909 return ($ret, \
@err);