1 # 2 # Copyright (c) 1999, 2008, Oracle and/or its affiliates. All rights reserved. 3 # Copyright (c) 2014 Racktop Systems. 4 # 5 6 # 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 9 # files. 10 # 11 ################################################################################ 12 require 5.0010; 13 14 use strict; 15 use warnings; 16 use locale; 17 use Errno; 18 use Fcntl; 19 use File::Basename; 20 use POSIX qw(locale_h limits_h); 21 22 package Sun::Solaris::Project; 23 24 our $VERSION = '1.9'; 25 26 use XSLoader; 27 XSLoader::load(__PACKAGE__, $VERSION); 28 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 35 getprojidbyname); 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 39 projent_parse_comment 40 projent_parse_users 41 projent_parse_groups 42 projent_parse_attributes 43 projent_validate projent_validate_projid 44 projent_values_equal projent_values2string); 45 46 @EXPORT_OK = (@constants, @syscalls, @libcalls, @private); 47 %EXPORT_TAGS = (CONSTANTS => \@constants, SYSCALLS => \@syscalls, 48 LIBCALLS => \@libcalls, PRIVATE => \@private, ALL => \@EXPORT_OK); 49 50 use base qw(Exporter); 51 use Sun::Solaris::Utils qw(gettext); 52 53 # 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. 57 # 58 use Config; 59 our $MaxNum = &RCTL_MAX_VALUE; 60 our %RctlRules; 61 62 my %rules; 63 our %SigNo; 64 my $j; 65 my $name; 66 foreach $name (split(' ', $Config{sig_name})) { 67 $SigNo{$name} = $j; 68 $j++; 69 } 70 %rules = ( 71 'privs' => [ qw(basic privileged priv) ], 72 'actions' => [ qw(none deny sig) ], 73 'signals' => [ qw(ABRT XRES HUP STOP TERM KILL XFSZ XCPU), 74 $SigNo{'ABRT'}, 75 $SigNo{'XRES'}, 76 $SigNo{'HUP'}, 77 $SigNo{'STOP'}, 78 $SigNo{'TERM'}, 79 $SigNo{'KILL'}, 80 $SigNo{'XFSZ'}, 81 $SigNo{'XCPU'} ], 82 'max' => $MaxNum 83 ); 84 85 $RctlRules{'__DEFAULT__'} = \%rules; 86 87 # 88 # projf_combine_errors(errorA, errorlistB) 89 # 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. 92 # 93 # Example: 94 # 95 # projf_combine_errors( 96 # [ 5, "Error on line %d, 10 ], 97 # [ [ 3, "Invalid Value %s", "foo" ], 98 # [ 6, "Duplicate Value %s", "bar" ] 99 # ]); 100 # 101 # would return the list ref: 102 # 103 # [ [ 5, "Error on line %d: Invalid Value %s", 10, "foo" ], 104 # [ 5, "Error on line %d: Duplicate Value %s", 10, "bar" ] 105 # ] 106 # 107 # This function is used when a fuction wants to add more information to 108 # a list of errors returned by another function. 109 # 110 sub projf_combine_errors 111 { 112 113 my ($error1, $errorlist) = @_; 114 my $error2; 115 116 my $newerror; 117 my @newerrorlist; 118 119 my ($err1, $fmt1, @args1); 120 my ($err2, $fmt2, @args2); 121 122 ($err1, $fmt1, @args1) = @$error1; 123 foreach $error2 (@$errorlist) { 124 125 ($err2, $fmt2, @args2) = @$error2; 126 $newerror = [ $err1, $fmt1 . ', ' . $fmt2, @args1, @args2]; 127 push(@newerrorlist, $newerror); 128 } 129 return (\@newerrorlist); 130 } 131 132 # 133 # projf_read(filename, flags) 134 # 135 # Reads and parses a project(4) file, and returns a list of projent hashes. 136 # 137 # Inputs: 138 # filename - file to read 139 # flags - hash ref of flags 140 # 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 143 # projf_validate(). 144 # 145 # Return Value: 146 # 147 # Returns a ref to a list of projent hashes. See projent_parse() for a 148 # description of a projent hash. 149 # 150 sub projf_read 151 { 152 153 my ($fh, $flags) = @_; 154 my @projents; 155 my $projent; 156 my $linenum = 0; 157 my ($projname, $projid, $comment, $users, $groups, $attributes); 158 my ($ret, $ref); 159 my @errs; 160 161 my ($line, $origline, $next, @projf); 162 while (defined($line = <$fh>)) { 163 164 $linenum++; 165 $origline = $line; 166 167 # Remove any line continuations and trailing newline. 168 $line =~ s/\\\n//g; 169 chomp($line); 170 171 172 if (length($line) > (&PROJECT_BUFSZ - 2)) { 173 push(@errs, 174 [5, 175 gettext('Parse error on line %d, line too long'), 176 $linenum]); 177 178 } 179 180 ($ret, $ref) = projent_parse($line, {}); 181 if ($ret != 0) { 182 $ref = projf_combine_errors( 183 [5, gettext('Parse error on line %d'), $linenum], 184 $ref); 185 push(@errs, @$ref); 186 next; 187 } 188 189 $projent = $ref; 190 191 # 192 # Cache original line to save original format if it is 193 # not changed. 194 # 195 $projent->{'line'} = $origline; 196 $projent->{'modified'} = 'false'; 197 $projent->{'linenum'} = $linenum; 198 199 push(@projents, $projent); 200 } 201 202 if (defined($flags->{'validate'}) && ($flags->{'validate'} eq 'true')) { 203 ($ret, $ref) = projf_validate(\@projents, $flags); 204 if ($ret != 0) { 205 push(@errs, @$ref); 206 } 207 } 208 209 if (@errs) { 210 return (1, \@errs); 211 212 } else { 213 return (0, \@projents); 214 } 215 } 216 217 # 218 # projf_write(filehandle, projent list) 219 # 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. 227 # 228 sub projf_write 229 { 230 my ($fh, $projents) = @_; 231 my $projent; 232 my $string; 233 234 foreach $projent (@$projents) { 235 236 if ($projent->{'modified'} eq 'false') { 237 $string = $projent->{'line'}; 238 } else { 239 $string = projent_2string($projent) . "\n"; 240 } 241 print $fh "$string"; 242 } 243 } 244 245 # 246 # projent_parse(line) 247 # 248 # Functions for parsing the project file lines into projent hashes. 249 # 250 # Returns a number and a ref, one of: 251 # 252 # (0, ref to projent hash) 253 # (non-zero, ref to list of errors) 254 # 255 # Flag can be: 256 # allowspaces: allow spaces between user and group names. 257 # allowunits : allow units (K, M, etc), on rctl values. 258 # 259 # A projent hash contains the keys: 260 # 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) 271 # 272 sub projent_parse 273 { 274 275 my ($line, $flags) = @_; 276 my $projent = {}; 277 my ($ret, $ref); 278 my @errs; 279 my ($projname, $projid, $comment, $users, $groups, $attributes); 280 281 # 282 # Split fields of project line. split() is not used because 283 # we must enforce that there are 6 fields. 284 # 285 ($projname, $projid, $comment, $users, $groups, $attributes) = 286 $line =~ 287 /^([^:]*):([^:]*):([^:]*):([^:]*):([^:]*):([^:]*)$/; 288 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.')]); 293 294 # Get as many fields as we can. 295 ($projname, $projid, $comment, $users, $groups, $attributes) = 296 split(/:/, $line); 297 } 298 299 if (defined($projname)) { 300 $projent->{'name'} = $projname; 301 ($ret, $ref) = projent_parse_name($projname); 302 if ($ret != 0) { 303 push(@errs, @$ref); 304 } 305 } 306 if (defined($projid)) { 307 $projent->{'projid'} = $projid; 308 ($ret, $ref) = projent_parse_projid($projid); 309 if ($ret != 0) { 310 push(@errs, @$ref); 311 } 312 } 313 if (defined($comment)) { 314 $projent->{'comment'} = $comment; 315 ($ret, $ref) = projent_parse_comment($comment); 316 if ($ret != 0) { 317 push(@errs, @$ref); 318 } 319 } 320 if (defined($users)) { 321 $projent->{'users'} = $users; 322 ($ret, $ref) = projent_parse_users($users, $flags); 323 if ($ret != 0) { 324 push(@errs, @$ref); 325 } else { 326 $projent->{'userlist'} = $ref; 327 } 328 } 329 if (defined($groups)) { 330 $projent->{'groups'} = $groups; 331 ($ret, $ref) = projent_parse_groups($groups, $flags); 332 if ($ret != 0) { 333 push(@errs, @$ref); 334 } else { 335 $projent->{'grouplist'} = $ref; 336 } 337 } 338 if (defined($attributes)) { 339 $projent->{'attributes'} = $attributes; 340 ($ret, $ref) = projent_parse_attributes($attributes, $flags); 341 if ($ret != 0) { 342 push(@errs, @$ref); 343 } else { 344 $projent->{'attributelist'} = $ref; 345 } 346 } 347 348 if (@errs) { 349 return (1, \@errs); 350 351 } else { 352 return (0, $projent); 353 } 354 } 355 356 # 357 # Project name syntax checking. 358 # 359 sub projent_parse_name 360 { 361 my @err; 362 my ($projname) = @_; 363 364 if (!($projname =~ /^[[:alpha:]][[:alnum:]_.-]*$/)) { 365 push(@err, ([3, gettext( 366 'Invalid project name "%s", contains invalid characters'), 367 $projname])); 368 return (1, \@err); 369 } 370 if (length($projname) > &PROJNAME_MAX) { 371 push(@err, ([3, gettext( 372 'Invalid project name "%s", name too long'), 373 $projname])); 374 return (1, \@err); 375 } 376 return (0, $projname); 377 } 378 379 # 380 # Projid syntax checking. 381 # 382 sub projent_parse_projid 383 { 384 my @err; 385 my ($projid) = @_; 386 387 # verify projid is a positive number, and less than UID_MAX 388 if (!($projid =~ /^\d+$/)) { 389 push(@err, [3, gettext('Invalid projid "%s"'), 390 $projid]); 391 return (1, \@err); 392 393 } elsif ($projid > POSIX::INT_MAX) { 394 push(@err, [3, gettext('Invalid projid "%s": must be <= '. 395 POSIX::INT_MAX), 396 $projid]); 397 return (1, \@err); 398 399 } else { 400 return (0, $projid); 401 } 402 } 403 404 # 405 # Project comment syntax checking. 406 # 407 sub projent_parse_comment 408 { 409 my ($comment) = @_; 410 411 # no restrictions on comments 412 return (0, $comment); 413 } 414 415 # 416 # projent_parse_users(string, flags) 417 # 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. 421 # 422 sub projent_parse_users 423 { 424 my ($users, $flags) = @_; 425 my @err; 426 my $user; 427 my $pattern; 428 my @userlist; 429 430 if (exists($flags->{'allowspaces'})) { 431 $pattern = '\s*,\s*'; 432 } else { 433 $pattern = ','; 434 } 435 @userlist = split(/$pattern/, $users); 436 437 # Return empty list if there are no users. 438 if (!(@userlist)) { 439 return (0, \@userlist); 440 } 441 442 # Verify each user name is the correct format for a valid user name. 443 foreach $user (@userlist) { 444 445 # Allow for wildcards. 446 if ($user eq '*' || $user eq '!*') { 447 next; 448 } 449 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"'), 454 $user]); 455 next; 456 } 457 } 458 if (@err) { 459 return (1,\ @err); 460 } else { 461 return (0, \@userlist); 462 } 463 } 464 465 # 466 # projent_parse_groups(string, flags) 467 # 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. 471 # 472 sub projent_parse_groups 473 { 474 my ($groups, $flags) = @_; 475 my @err; 476 my $group; 477 my $pattern; 478 479 my @grouplist; 480 481 if (exists($flags->{'allowspaces'})) { 482 $pattern = '\s*,\s*'; 483 } else { 484 $pattern = ','; 485 } 486 @grouplist = split(/$pattern/, $groups); 487 488 # Return empty list if there are no groups. 489 if (!(@grouplist)) { 490 return (0, \@grouplist); 491 } 492 493 # Verify each group is the correct format for a valid group name. 494 foreach $group (@grouplist) { 495 496 # Allow for wildcards. 497 if ($group eq '*' || $group eq '!*') { 498 next; 499 } 500 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"'), 505 $group]); 506 next; 507 } 508 } 509 510 if (@err) { 511 return (1,\ @err); 512 } else { 513 return (0, \@grouplist); 514 } 515 } 516 517 # 518 # projent_tokenize_attribute_values(values) 519 # 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 ( ) , 523 # 524 sub projent_tokenize_attribute_values 525 { 526 # 527 # This seperates the attribute string into higher level tokens 528 # for parsing. 529 # 530 my $prev; 531 my $cur; 532 my $next; 533 my $token; 534 my @tokens; 535 my @newtokens; 536 my @err; 537 538 # Seperate tokens delimited by "(", ")", and ",". 539 @tokens = split(/([,()])/, $_[0], -1); 540 541 # Get rid of blanks 542 @newtokens = grep($_ ne '', @tokens); 543 544 foreach $token (@newtokens) { 545 if (!($token =~ /^[(),]$/ || 546 $token =~ /^[[:alnum:]_.\/=+-]*$/)) { 547 push(@err, [3, gettext( 548 'Invalid Character at or near "%s"'), $token]); 549 } 550 } 551 if (@err) { 552 return (1, \@err); 553 } else { 554 return (0, \@newtokens); 555 } 556 } 557 558 # 559 # projent_parse_attribute_values(values) 560 # 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. 565 # 566 sub projent_parse_attribute_values 567 { 568 # 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. 572 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. 576 # 577 my ($values) = @_; 578 my $tokens; 579 my @usedtokens; 580 my $token; 581 my $prev = ''; 582 my $parendepth = 0; 583 my @valuestack; 584 my @err; 585 my ($ret, $ref); 586 my $line; 587 588 push (@valuestack, []); 589 590 ($ret, $ref) = projent_tokenize_attribute_values($values); 591 if ($ret != 0) { 592 return ($ret, $ref); 593 } 594 $tokens = $ref; 595 596 foreach $token (@$tokens) { 597 598 push(@usedtokens, $token); 599 600 if ($token eq ',') { 601 602 if ($prev eq ',' || $prev eq '(' || 603 $prev eq '') { 604 push(@{$valuestack[$#valuestack]}, ''); 605 } 606 $prev = ','; 607 next; 608 } 609 if ($token eq '(') { 610 611 if (!($prev eq '(' || $prev eq ',' || 612 $prev eq '')) { 613 614 $line = join('', @usedtokens); 615 push(@err, [3, gettext( 616 '"%s" <- "(" unexpected'), 617 $line]); 618 619 return (1, \@err); 620 } 621 622 $parendepth++; 623 my $arrayref = []; 624 push(@{$valuestack[$#valuestack]}, $arrayref); 625 push(@valuestack, $arrayref); 626 627 $prev = '('; 628 next; 629 } 630 if ($token eq ')') { 631 632 if ($parendepth <= 0) { 633 634 $line = join('', @usedtokens); 635 push(@err, [3, gettext( 636 '"%s" <- ")" unexpected'), 637 $line]); 638 639 return (1, \@err); 640 } 641 642 if ($prev eq ',' || $prev eq '(') { 643 push(@{$valuestack[$#valuestack]}, ''); 644 } 645 $parendepth--; 646 pop @valuestack; 647 648 $prev = ')'; 649 next; 650 } 651 652 if (!($prev eq ',' || $prev eq '(' || $prev eq '')) { 653 $line = join('', @usedtokens); 654 push(@err, [3, gettext( 655 '"%s" <- "%s" unexpected'), 656 $line, $token]); 657 658 return (1, \@err); 659 } 660 661 push(@{$valuestack[$#valuestack]}, $token); 662 $prev = $token; 663 next; 664 } 665 666 if ($parendepth != 0) { 667 push(@err, [3, gettext( 668 '"%s" <- ")" missing'), 669 $values]); 670 return (1, \@err); 671 } 672 673 if ($prev eq ',' || $prev eq '') { 674 push(@{$valuestack[$#valuestack]}, ''); 675 } 676 677 return (0, $valuestack[0]); 678 } 679 680 # 681 # projent_parse_attribute("name=values", $flags) 682 # 683 # $flags is a hash ref. 684 # Valid flags keys: 685 # 'allowunits' - allows numeric values to be scaled on certain attributes 686 # 687 # Returns a hash ref with keys: 688 # 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. 693 # 694 sub projent_parse_attribute 695 { 696 my ($string, $flags) = @_; 697 my $attribute = {}; 698 my ($name, $stock, $values); 699 my ($ret, $ref); 700 my @err; 701 my $scale; 702 my $num; 703 my $modifier; 704 my $unit; 705 my $tuple; 706 my $rules; 707 my $rctlmax; 708 my $rctlflags; 709 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:]_.-]*)$/; 715 if ($name) { 716 $attribute->{'name'} = $name; 717 return (0, $attribute); 718 } 719 720 # Match attribute with value list. 721 ($name, $stock, $values) = $string =~ 722 /^(($stockp)?[[:alpha:]][[:alnum:]_.-]*)=(.*)$/; 723 if ($name) { 724 $attribute->{'name'} = $name; 725 726 if (!defined($values)) { 727 $values = ''; 728 } 729 730 ($ret, $ref) = projent_parse_attribute_values($values); 731 if ($ret != 0) { 732 $ref = projf_combine_errors( 733 [3, 734 gettext('Invalid value on attribute "%s"'), 735 $name], $ref); 736 push(@err, @$ref); 737 return ($ret, \@err) 738 } 739 740 # Scale attributes than can be scaled. 741 if (exists($flags->{"allowunits"})) { 742 743 if ($name eq 'rcap.max-rss' && 744 defined($ref->[0]) && !ref($ref->[0])) { 745 $scale = 'bytes'; 746 747 ($num, $modifier, $unit) = 748 projent_val2num($ref->[0], $scale); 749 750 if (!defined($num)) { 751 752 if (defined($unit)) { 753 push(@err, [3, gettext( 754 'rcap.max-rss has invalid '. 755 'unit "%s"'), $unit]); 756 } else { 757 push(@err, [3, gettext( 758 'rcap.max-rss has invalid '. 759 'value "%s"'), $ref->[0]]); 760 } 761 } elsif ($num eq "OVERFLOW") { 762 push(@err, [3, gettext( 'rcap.max-rss value '. 763 '"%s" exceeds maximum value "%s"'), 764 $ref->[0], $MaxNum]); 765 } else { 766 $ref->[0] = $num; 767 } 768 } 769 # Check hashed cache of rctl rules. 770 $rules = $RctlRules{$name}; 771 if (!defined($rules)) { 772 # 773 # See if this is an resource control name, if so 774 # cache rules. 775 # 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; 782 } else { 783 $RctlRules{$name} = 784 "NOT AN RCTL"; 785 } 786 } 787 } 788 789 # Scale values if this is an rctl. 790 if (defined ($rules) && ref($rules)) { 791 $flags->{'type'} = $rules->{'type'}; 792 foreach $tuple (@$ref) { 793 794 # Skip if tuple this is not a list. 795 if (!ref($tuple)) { 796 next; 797 } 798 # Skip if second element is not scalar. 799 if (!defined($tuple->[1]) || 800 ref($tuple->[1])) { 801 next; 802 } 803 ($num, $modifier, $unit) = 804 projent_val2num($tuple->[1], 805 $flags->{'type'}); 806 807 if (!defined($num)) { 808 809 if (defined($unit)) { 810 push(@err, [3, gettext( 811 'rctl %s has '. 812 'invalid unit '. 813 '"%s"'),$name, 814 $unit]); 815 } else { 816 push(@err, [3, gettext( 817 'rctl %s has '. 818 'invalid value '. 819 '"%s"'), $name, 820 $tuple->[1]]); 821 } 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]); 827 } else { 828 $tuple->[1] = $num; 829 } 830 } 831 } 832 } 833 $attribute->{'values'} = $ref; 834 if (@err) { 835 return (1, \@err); 836 } else { 837 return (0, $attribute); 838 } 839 840 } else { 841 # Attribute did not match name[=value,value...] 842 push(@err, [3, gettext('Invalid attribute "%s"'), $string]); 843 return (1, \@err); 844 } 845 } 846 847 # 848 # projent_parse_attributes("; seperated list of name=values pairs"); 849 # 850 # Returns a list of attribute references, as returned by 851 # projent_parse_attribute(). 852 # 853 sub projent_parse_attributes 854 { 855 my ($attributes, $flags) = @_; 856 my @attributelist; 857 my @attributestrings; 858 my $attributestring; 859 my $attribute; 860 my ($ret, $ref); 861 my @errs; 862 863 # Split up attributes by ";"'s. 864 @attributestrings = split(/;/, $attributes); 865 866 # If no attributes, return empty list. 867 if (!@attributestrings) { 868 return (0, \@attributelist); 869 } 870 871 foreach $attributestring (@attributestrings) { 872 873 ($ret, $ref) = projent_parse_attribute($attributestring, 874 $flags); 875 if ($ret != 0) { 876 push(@errs, @$ref); 877 } else { 878 push(@attributelist, $ref); 879 } 880 } 881 882 if (@errs) { 883 return (1, \@errs); 884 } else { 885 return (0, \@attributelist); 886 } 887 888 } 889 890 # 891 # projent_values_equal(list A, list B) 892 # 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. 896 # 897 # XXX sub projent_values_equal; 898 sub projent_values_equal 899 { 900 my ($x, $y) = @_; 901 902 my $itema; 903 my $itemb; 904 my $index = 0; 905 906 if (ref($x) && ref($y)) { 907 908 if (scalar(@$x) != scalar(@$y)) { 909 return (0); 910 } else { 911 foreach $itema (@$x) { 912 913 $itemb = $y->[$index++]; 914 915 if (!projent_values_equal($itema, $itemb)) { 916 return (0); 917 } 918 } 919 return (1); 920 } 921 } elsif ((!ref($x) && (!ref($y)))) { 922 return ($x eq $y); 923 } else { 924 return (0); 925 } 926 } 927 928 # 929 # Converts a list of values to a , seperated string, enclosing sublists 930 # in ()'s. 931 # 932 sub projent_values2string 933 { 934 my ($values) = @_; 935 my $string; 936 my $value; 937 my @valuelist; 938 939 if (!defined($values)) { 940 return (''); 941 } 942 if (!ref($values)) { 943 return ($values); 944 } 945 foreach $value (@$values) { 946 947 if (ref($value)) { 948 push(@valuelist, 949 '(' . projent_values2string($value) . ')'); 950 } else { 951 push(@valuelist, $value); 952 } 953 } 954 955 $string = join(',', @valuelist) ; 956 if (!defined($string)) { 957 $string = ''; 958 } 959 return ($string); 960 } 961 962 # 963 # Converts a ref to an attribute hash with keys "name", and "values" to 964 # a string in the form "name=value,value...". 965 # 966 sub projent_attribute2string 967 { 968 my ($attribute) = @_; 969 my $string; 970 971 $string = $attribute->{'name'}; 972 973 if (ref($attribute->{'values'}) && @{$attribute->{'values'}}) { 974 $string = $string . '=' . 975 projent_values2string(($attribute->{'values'})); 976 } 977 return ($string); 978 } 979 980 # 981 # Converts a ref to a projent hash (as returned by projent_parse()) to 982 # a project(4) database entry line. 983 # 984 sub projent_2string 985 { 986 my ($projent) = @_; 987 my @attributestrings; 988 my $attribute; 989 990 foreach $attribute (@{$projent->{'attributelist'}}) { 991 push(@attributestrings, projent_attribute2string($attribute)); 992 } 993 return (join(':', ($projent->{'name'}, 994 $projent->{'projid'}, 995 $projent->{'comment'}, 996 join(',', @{$projent->{'userlist'}}), 997 join(',', @{$projent->{'grouplist'}}), 998 join(';', @attributestrings)))); 999 } 1000 1001 # 1002 # projf_validate(ref to list of projents hashes, flags) 1003 # 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 1007 # value. 1008 # 1009 # Valid flag keys: 1010 # 1011 # "res" - allow reserved project ids 0-99 1012 # "dup" - allow duplicate project ids 1013 # 1014 sub projf_validate 1015 { 1016 my ($projents, $flags) = @_; 1017 my $projent; 1018 my $ret; 1019 my $ref; 1020 my @err; 1021 my %idhash; 1022 my %namehash; 1023 my %seenids; 1024 my %seennames; 1025 1026 # check for unique project names 1027 foreach $projent (@$projents) { 1028 1029 my @lineerr; 1030 1031 $seennames{$projent->{'name'}}++; 1032 $seenids{$projent->{'projid'}}++; 1033 1034 if ($seennames{$projent->{'name'}} > 1) { 1035 push(@lineerr, [4, gettext( 1036 'Duplicate project name "%s"'), 1037 $projent->{'name'}]); 1038 } 1039 1040 if (!defined($flags->{'dup'})) { 1041 if ($seenids{$projent->{'projid'}} > 1) { 1042 push(@lineerr, [4, gettext( 1043 'Duplicate projid "%s"'), 1044 $projent->{'projid'}]); 1045 } 1046 } 1047 ($ret, $ref) = projent_validate($projent, $flags); 1048 if ($ret != 0) { 1049 push(@lineerr, @$ref); 1050 } 1051 1052 if (@lineerr) { 1053 1054 $ref = projf_combine_errors([5, gettext( 1055 'Validation error on line %d'), 1056 $projent->{'linenum'}], \@lineerr); 1057 push(@err, @$ref); 1058 } 1059 } 1060 if (@err) { 1061 return (1, \@err); 1062 } else { 1063 return (0, $projents); 1064 } 1065 } 1066 1067 # 1068 # projent_validate_unique_id( 1069 # ref to projent hash, ref to list of projent hashes) 1070 # 1071 # Verifies that projid of the projent hash only exists once in the list of 1072 # projent hashes. 1073 # 1074 sub projent_validate_unique_id 1075 { 1076 my ($projent, $projf, $idhash) = @_; 1077 my @err; 1078 my $ret = 0; 1079 my $projid = $projent->{'projid'}; 1080 1081 if (scalar(grep($_->{'projid'} eq $projid, @$projf)) > 1) { 1082 $ret = 1; 1083 push(@err, [4, gettext('Duplicate projid "%s"'), 1084 $projid]); 1085 } 1086 1087 return ($ret, \@err); 1088 } 1089 1090 # 1091 # projent_validate_unique_id( 1092 # ref to projent hash, ref to list of projent hashes) 1093 # 1094 # Verifies that project name of the projent hash only exists once in the list 1095 # of projent hashes. 1096 # 1097 # If the seconds argument is a hash ref, it is treated 1098 # 1099 sub projent_validate_unique_name 1100 { 1101 my ($projent, $projf, $namehash) = @_; 1102 my $ret = 0; 1103 my @err; 1104 my $pname = $projent->{'name'}; 1105 1106 if (scalar(grep($_->{'name'} eq $pname, @$projf)) > 1) { 1107 $ret = 1; 1108 push(@err, 1109 [9, gettext('Duplicate project name "%s"'), $pname]); 1110 } 1111 1112 return ($ret, \@err); 1113 } 1114 1115 # 1116 # projent_validate(ref to projents hash, flags) 1117 # 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. 1121 # 1122 # Valid flag keys: 1123 # 1124 # "allowspaces" - user and group list are allowed to contain whitespace 1125 # "res" - allow reserved project ids 0-99 1126 # 1127 sub projent_validate 1128 { 1129 my ($projent, $flags) = @_; 1130 my $ret = 0; 1131 my $ref; 1132 my @err; 1133 1134 ($ret, $ref) = 1135 projent_validate_name($projent->{'name'}, $flags); 1136 if ($ret != 0) { 1137 push(@err, @$ref); 1138 } 1139 ($ret, $ref) = 1140 projent_validate_projid($projent->{'projid'}, $flags); 1141 if ($ret != 0) { 1142 push(@err, @$ref); 1143 } 1144 ($ret, $ref) = 1145 projent_validate_comment($projent->{'comment'}, $flags); 1146 if ($ret != 0) { 1147 push(@err, @$ref); 1148 } 1149 ($ret, $ref) = 1150 projent_validate_users($projent->{'userlist'}, $flags); 1151 if ($ret != 0) { 1152 push(@err, @$ref); 1153 } 1154 ($ret, $ref) = 1155 projent_validate_groups($projent->{'grouplist'}, $flags); 1156 if ($ret != 0) { 1157 push(@err, @$ref); 1158 } 1159 ($ret, $ref) = projent_validate_attributes( 1160 $projent->{'attributelist'}, $flags); 1161 if ($ret != 0) { 1162 push(@err, @$ref); 1163 } 1164 1165 my $string = projent_2string($projent); 1166 if (length($string) > (&PROJECT_BUFSZ - 2)) { 1167 push(@err, [3, gettext('projent line too long')]); 1168 } 1169 1170 if (@err) { 1171 return (1, \@err); 1172 } else { 1173 return (0, $projent); 1174 } 1175 } 1176 1177 # 1178 # projent_validate_name(name, flags) 1179 # 1180 # does nothing, as any parse-able project name is valid 1181 # 1182 sub projent_validate_name 1183 { 1184 my ($name, $flags) = @_; 1185 my @err; 1186 1187 return (0, \@err); 1188 1189 } 1190 1191 # 1192 # projent_validate_projid(projid, flags) 1193 # 1194 # Validates that projid is within the valid range of numbers. 1195 # Valid flag keys: 1196 # "res" - allow reserved projid's 0-99 1197 # 1198 sub projent_validate_projid 1199 { 1200 my ($projid, $flags) = @_; 1201 my @err; 1202 my $ret = 0; 1203 my $minprojid; 1204 1205 if (defined($flags->{'res'})) { 1206 $minprojid = 0; 1207 } else { 1208 $minprojid = 100; 1209 } 1210 1211 if ($projid < $minprojid) { 1212 1213 $ret = 1; 1214 push(@err, [3, gettext('Invalid projid "%s": '. 1215 'must be >= 100'), 1216 $projid]); 1217 1218 } 1219 1220 return ($ret, \@err); 1221 } 1222 1223 # 1224 # projent_validate_comment(name, flags) 1225 # 1226 # Does nothing, as any parse-able comment is valid. 1227 # 1228 sub projent_validate_comment 1229 { 1230 my ($comment, $flags) = @_; 1231 my @err; 1232 1233 return (0, \@err); 1234 } 1235 1236 # 1237 # projent_validate_users(ref to list of user names, flags) 1238 # 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. 1242 # 1243 sub projent_validate_users 1244 { 1245 my ($users, $flags) = @_; 1246 my @err; 1247 my $ret = 0; 1248 my $user; 1249 my $username; 1250 1251 foreach $user (@$users) { 1252 1253 if ($user eq '*' || $user eq '!*') { 1254 next; 1255 } 1256 $username = $user; 1257 $username =~ s/^!//; 1258 1259 if (!defined(getpwnam($username))) { 1260 $ret = 1; 1261 push(@err, [6, 1262 gettext('User "%s" does not exist'), 1263 $username]); 1264 } 1265 } 1266 1267 my %seen; 1268 my @dups = grep($seen{$_}++ == 1, @$users); 1269 if (@dups) { 1270 $ret = 1; 1271 push(@err, [3, gettext('Duplicate user names "%s"'), 1272 join(',', @dups)]); 1273 } 1274 return ($ret, \@err) 1275 } 1276 1277 # 1278 # projent_validate_groups(ref to list of group names, flags) 1279 # 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. 1283 # 1284 sub projent_validate_groups 1285 { 1286 my ($groups, $flags) = @_; 1287 my @err; 1288 my $ret = 0; 1289 my $group; 1290 my $groupname; 1291 1292 foreach $group (@$groups) { 1293 1294 if ($group eq '*' || $group eq '!*') { 1295 next; 1296 } 1297 1298 $groupname = $group; 1299 $groupname =~ s/^!//; 1300 1301 if (!defined(getgrnam($groupname))) { 1302 $ret = 1; 1303 push(@err, [6, 1304 gettext('Group "%s" does not exist'), 1305 $groupname]); 1306 } 1307 } 1308 1309 my %seen; 1310 my @dups = grep($seen{$_}++ == 1, @$groups); 1311 if (@dups) { 1312 $ret = 1; 1313 push(@err, [3, gettext('Duplicate group names "%s"'), 1314 join(',', @dups)]); 1315 } 1316 1317 return ($ret, \@err) 1318 } 1319 1320 # 1321 # projent_validate_attribute(attribute hash ref, flags) 1322 # 1323 # Verifies that if the attribute's name is a known attribute or 1324 # resource control, that it contains a valid value. 1325 # flags is unused. 1326 # 1327 sub projent_validate_attribute 1328 { 1329 my ($attribute, $flags) = @_; 1330 my $name = $attribute->{'name'}; 1331 my $values = $attribute->{'values'}; 1332 my $value; 1333 my @errs; 1334 my $ret = 0; 1335 my $result; 1336 my $ref; 1337 1338 if (defined($values)) { 1339 $value = $values->[0]; 1340 } 1341 if ($name eq 'task.final') { 1342 1343 if (defined($values)) { 1344 $ret = 1; 1345 push(@errs, [3, gettext( 1346 'task.final should not have value')]); 1347 } 1348 1349 # Need to rcap.max-rss needs to be a number 1350 } elsif ($name eq 'rcap.max-rss') { 1351 1352 if (!defined($values)) { 1353 $ret = 1; 1354 push(@errs, [3, gettext( 1355 'rcap.max-rss missing value')]); 1356 } elsif (scalar(@$values) != 1) { 1357 $ret = 1; 1358 push(@errs, [3, gettext( 1359 'rcap.max-rss should have single value')]); 1360 } 1361 if (!defined($value) || ref($value)) { 1362 $ret = 1; 1363 push(@errs, [3, gettext( 1364 'rcap.max-rss has invalid value "%s"'), 1365 projent_values2string($values)]);; 1366 } elsif ($value !~ /^\d+$/) { 1367 $ret = 1; 1368 push(@errs, [3, gettext( 1369 'rcap.max-rss is not an integer value: "%s"'), 1370 projent_values2string($values)]);; 1371 } elsif ($value > $MaxNum) { 1372 $ret = 1; 1373 push(@errs, [3, gettext( 1374 'rcap.max-rss too large')]); 1375 } 1376 1377 } elsif ($name eq 'project.pool') { 1378 if (!defined($values)) { 1379 $ret = 1; 1380 push(@errs, [3, gettext( 1381 'project.pool missing value')]); 1382 } elsif (scalar(@$values) != 1) { 1383 $ret = 1; 1384 push(@errs, [3, gettext( 1385 'project.pool should have single value')]); 1386 } elsif (!defined($value) || ref($value)) { 1387 $ret = 1; 1388 push(@errs, [3, gettext( 1389 'project.pool has invalid value "%s'), 1390 projent_values2string($values)]);; 1391 } elsif (!($value =~ /^[[:alpha:]][[:alnum:]_.-]*$/)) { 1392 $ret = 1; 1393 push(@errs, [3, gettext( 1394 'project.pool: invalid pool name "%s"'), 1395 $value]); 1396 # Pool must exist. 1397 } elsif (pool_exists($value) != 0) { 1398 $ret = 1; 1399 push(@errs, [6, gettext( 1400 'project.pool: pools not enabled or pool does '. 1401 'not exist: "%s"'), 1402 $value]); 1403 } 1404 } else { 1405 my $rctlmax; 1406 my $rctlflags; 1407 my $rules; 1408 1409 # 1410 # See if rctl rules exist for this attribute. If so, it 1411 # is an rctl and is checked for valid values. 1412 # 1413 1414 # check hashed cache of rctl rules. 1415 $rules = $RctlRules{$name}; 1416 if (!defined($rules)) { 1417 1418 # 1419 # See if this is an resource control name, if so 1420 # cache rules. 1421 # 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; 1428 } else { 1429 $RctlRules{$name} = "NOT AN RCTL"; 1430 } 1431 } 1432 } 1433 1434 # If rules are defined, this is a resource control. 1435 if (defined($rules) && ref($rules)) { 1436 1437 ($result, $ref) = 1438 projent_validate_rctl($attribute, $flags); 1439 if ($result != 0) { 1440 $ret = 1; 1441 push(@errs, @$ref); 1442 } 1443 } 1444 } 1445 return ($ret, \@errs); 1446 } 1447 1448 # 1449 # projent_validate_attributes(ref to attribute list, flags) 1450 # 1451 # Validates all attributes in list of attribute references using 1452 # projent_validate_attribute. flags is unused. 1453 # flags is unused. 1454 # 1455 sub projent_validate_attributes 1456 { 1457 my ($attributes, $flags) = @_; 1458 my @err; 1459 my $ret = 0; 1460 my $result = 0; 1461 my $ref; 1462 my $attribute; 1463 1464 foreach $attribute (@$attributes) { 1465 1466 ($ret, $ref) = projent_validate_attribute($attribute, $flags); 1467 if ($ret != 0) { 1468 $result = $ret; 1469 push(@err, @$ref); 1470 } 1471 } 1472 1473 my %seen; 1474 my @dups = grep($seen{$_}++ == 1, map { $_->{'name'} } @$attributes); 1475 if (@dups) { 1476 $result = 1; 1477 push(@err, [3, gettext('Duplicate attributes "%s"'), 1478 join(',', @dups)]); 1479 } 1480 1481 return ($result, \@err); 1482 } 1483 1484 # 1485 # projent_getrctlrules(max value, global flags) 1486 # 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 1489 # rctl's values. 1490 # 1491 sub proj_getrctlrules 1492 { 1493 my ($max, $flags) = @_; 1494 my $signals; 1495 my $rctl; 1496 1497 $rctl = {}; 1498 $signals = 1499 [ qw(ABRT XRES HUP STOP TERM KILL), 1500 $SigNo{'ABRT'}, 1501 $SigNo{'XRES'}, 1502 $SigNo{'HUP'}, 1503 $SigNo{'STOP'}, 1504 $SigNo{'TERM'}, 1505 $SigNo{'KILL'} ]; 1506 1507 $rctl->{'max'} = $max; 1508 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'; 1515 } else { 1516 $rctl->{'type'} = 'unknown'; 1517 } 1518 if ($flags & &RCTL_GLOBAL_NOBASIC) { 1519 $rctl->{'privs'} = ['privileged', 'priv']; 1520 } else { 1521 $rctl->{'privs'} = ['basic', 'privileged', 'priv']; 1522 } 1523 1524 if ($flags & &RCTL_GLOBAL_DENY_ALWAYS) { 1525 $rctl->{'actions'} = ['deny']; 1526 1527 } elsif ($flags & &RCTL_GLOBAL_DENY_NEVER) { 1528 $rctl->{'actions'} = ['none']; 1529 } else { 1530 $rctl->{'actions'} = ['none', 'deny']; 1531 } 1532 1533 if ($flags & &RCTL_GLOBAL_SIGNAL_NEVER) { 1534 $rctl->{'signals'} = []; 1535 1536 } else { 1537 1538 push(@{$rctl->{'actions'}}, 'sig'); 1539 1540 if ($flags & &RCTL_GLOBAL_CPU_TIME) { 1541 push(@$signals, 'XCPU', '30'); 1542 } 1543 if ($flags & &RCTL_GLOBAL_FILE_SIZE) { 1544 push(@$signals, 'XFSZ', '31'); 1545 } 1546 $rctl->{'signals'} = $signals; 1547 } 1548 return ($rctl); 1549 } 1550 1551 # 1552 # projent_val2num(scaled value, "seconds" | "count" | "bytes") 1553 # 1554 # converts an integer or scaled value to an integer value. 1555 # returns (integer value, modifier character, unit character. 1556 # 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. 1560 # 1561 sub projent_val2num 1562 { 1563 my ($val, $type) = @_; 1564 my %scaleM = ( k => 1000, 1565 m => 1000000, 1566 g => 1000000000, 1567 t => 1000000000000, 1568 p => 1000000000000000, 1569 e => 1000000000000000000); 1570 my %scaleB = ( k => 1024, 1571 m => 1048576, 1572 g => 1073741824, 1573 t => 1099511627776, 1574 p => 1125899906842624, 1575 e => 1152921504606846976); 1576 1577 my $scale; 1578 my $base; 1579 my ($num, $modifier, $unit); 1580 my $mul; 1581 my $string; 1582 my $i; 1583 my $undefined; 1584 my $exp_unit; 1585 1586 ($num, $modifier, $unit) = $val =~ 1587 /^(\d+(?:\.\d+)?)(?i:([kmgtpe])?([bs])?)$/; 1588 1589 # No numeric match. 1590 if (!defined($num)) { 1591 return ($undefined, $undefined, $undefined); 1592 } 1593 1594 # Decimal number with no scaling modifier. 1595 if (!defined($modifier) && $num =~ /^\d+\.\d+/) { 1596 return ($undefined, $undefined, $undefined); 1597 } 1598 1599 if ($type eq 'bytes') { 1600 $exp_unit = 'b'; 1601 $scale = \%scaleB; 1602 } elsif ($type eq 'seconds') { 1603 $exp_unit = 's'; 1604 $scale = \%scaleM; 1605 } else { 1606 $scale = \%scaleM; 1607 } 1608 1609 if (defined($unit)) { 1610 $unit = lc($unit); 1611 } 1612 1613 # So not succeed if unit is incorrect. 1614 if (!defined($exp_unit) && defined($unit)) { 1615 return ($undefined, $modifier, $unit); 1616 } 1617 if (defined($unit) && $unit ne $exp_unit) { 1618 return ($undefined, $modifier, $unit); 1619 } 1620 1621 if (defined($modifier)) { 1622 1623 $modifier = lc($modifier); 1624 $mul = $scale->{$modifier}; 1625 $num = $num * $mul; 1626 } 1627 1628 # check for integer overflow. 1629 if ($num > $MaxNum) { 1630 return ("OVERFLOW", $modifier, $unit); 1631 } 1632 # 1633 # Trim numbers that are decimal equivalent to the maximum value 1634 # to the maximum integer value. 1635 # 1636 if ($num == $MaxNum) { 1637 $num = $MaxNum;; 1638 1639 } elsif ($num < $MaxNum) { 1640 # convert any decimal numbers to an integer 1641 $num = int($num); 1642 } 1643 1644 return ($num, $modifier, $unit); 1645 } 1646 # 1647 # projent_validate_rctl(ref to rctl attribute hash, flags) 1648 # 1649 # verifies that the given rctl hash with keys "name" and 1650 # "values" contains valid values for the given name. 1651 # flags is unused. 1652 # 1653 sub projent_validate_rctl 1654 { 1655 my ($rctl, $flags) = @_; 1656 my $allrules; 1657 my $rules; 1658 my $name; 1659 my $values; 1660 my $value; 1661 my $valuestring; 1662 my $ret = 0; 1663 my @err; 1664 my $priv; 1665 my $val; 1666 my @actions; 1667 my $action; 1668 my $signal; 1669 my $sigstring; # Full signal string on right hand of signal=SIGXXX. 1670 my $signame; # Signal number or XXX part of SIGXXX. 1671 my $siglist; 1672 my $nonecount; 1673 my $denycount; 1674 my $sigcount; 1675 1676 $name = $rctl->{'name'}; 1677 $values = $rctl->{'values'}; 1678 1679 # 1680 # Get the default rules for all rctls, and the specific rules for 1681 # this rctl. 1682 # 1683 $allrules = $RctlRules{'__DEFAULT__'}; 1684 $rules = $RctlRules{$name}; 1685 1686 if (!defined($rules) || !ref($rules)) { 1687 $rules = $allrules; 1688 } 1689 1690 # Allow for no rctl values on rctl. 1691 if (!defined($values)) { 1692 return (0, \@err); 1693 } 1694 1695 # If values exist, make sure it is a list. 1696 if (!ref($values)) { 1697 1698 push(@err, [3, gettext( 1699 'rctl "%s" missing value'), $name]); 1700 return (1, \@err); 1701 } 1702 1703 foreach $value (@$values) { 1704 1705 # Each value should be a list. 1706 1707 if (!ref($value)) { 1708 $ret = 1; 1709 push(@err, [3, gettext( 1710 'rctl "%s" value "%s" should be in ()\'s'), 1711 $name, $value]); 1712 1713 next; 1714 } 1715 1716 ($priv, $val, @actions) = @$value; 1717 if (!@actions) { 1718 $ret = 1; 1719 $valuestring = projent_values2string([$value]); 1720 push(@err, [3, gettext( 1721 'rctl "%s" value missing action "%s"'), 1722 $name, $valuestring]); 1723 } 1724 1725 if (!defined($priv)) { 1726 $ret = 1; 1727 push(@err, [3, gettext( 1728 'rctl "%s" value missing privilege "%s"'), 1729 $name, $valuestring]); 1730 1731 } elsif (ref($priv)) { 1732 $ret = 1; 1733 $valuestring = projent_values2string([$priv]); 1734 push(@err, [3, gettext( 1735 'rctl "%s" invalid privilege "%s"'), 1736 $name, $valuestring]); 1737 1738 } else { 1739 if (!(grep /^$priv$/, @{$allrules->{'privs'}})) { 1740 1741 $ret = 1; 1742 push(@err, [3, gettext( 1743 'rctl "%s" unknown privilege "%s"'), 1744 $name, $priv]); 1745 1746 } elsif (!(grep /^$priv$/, @{$rules->{'privs'}})) { 1747 1748 $ret = 1; 1749 push(@err, [3, gettext( 1750 'rctl "%s" privilege not allowed '. 1751 '"%s"'), $name, $priv]); 1752 } 1753 } 1754 if (!defined($val)) { 1755 $ret = 1; 1756 push(@err, [3, gettext( 1757 'rctl "%s" missing value'), $name]); 1758 1759 } elsif (ref($val)) { 1760 $ret = 1; 1761 $valuestring = projent_values2string([$val]); 1762 push(@err, [3, gettext( 1763 'rctl "%s" invalid value "%s"'), 1764 $name, $valuestring]); 1765 1766 } else { 1767 if ($val !~ /^\d+$/) { 1768 $ret = 1; 1769 push(@err, [3, gettext( 1770 'rctl "%s" value "%s" is not '. 1771 'an integer'), $name, $val]); 1772 1773 } elsif ($val > $rules->{'max'}) { 1774 $ret = 1; 1775 push(@err, [3, gettext( 1776 'rctl "%s" value "%s" exceeds '. 1777 'system limit'), $name, $val]); 1778 } 1779 } 1780 $nonecount = 0; 1781 $denycount = 0; 1782 $sigcount = 0; 1783 1784 foreach $action (@actions) { 1785 1786 if (ref($action)) { 1787 $ret = 1; 1788 $valuestring = 1789 projent_values2string([$action]); 1790 push(@err, [3, gettext( 1791 'rctl "%s" invalid action "%s"'), 1792 $name, $valuestring]); 1793 1794 next; 1795 } 1796 1797 if ($action =~ /^sig(nal)?(=.*)?$/) { 1798 $signal = $action; 1799 $action = 'sig'; 1800 } 1801 if (!(grep /^$action$/, @{$allrules->{'actions'}})) { 1802 1803 $ret = 1; 1804 push(@err, [3, gettext( 1805 'rctl "%s" unknown action "%s"'), 1806 $name, $action]); 1807 next; 1808 1809 } elsif (!(grep /^$action$/, @{$rules->{'actions'}})) { 1810 1811 $ret = 1; 1812 push(@err, [3, gettext( 1813 'rctl "%s" action not allowed "%s"'), 1814 $name, $action]); 1815 next; 1816 } 1817 1818 if ($action eq 'none') { 1819 if ($nonecount >= 1) { 1820 1821 $ret = 1; 1822 push(@err, [3, gettext( 1823 'rctl "%s" duplicate action '. 1824 'none'), $name]); 1825 } 1826 $nonecount++; 1827 next; 1828 } 1829 if ($action eq 'deny') { 1830 if ($denycount >= 1) { 1831 1832 $ret = 1; 1833 push(@err, [3, gettext( 1834 'rctl "%s" duplicate action '. 1835 'deny'), $name]); 1836 } 1837 $denycount++; 1838 next; 1839 } 1840 1841 # action must be signal 1842 if ($sigcount >= 1) { 1843 1844 $ret = 1; 1845 push(@err, [3, gettext( 1846 'rctl "%s" duplicate action sig'), 1847 $name]); 1848 } 1849 $sigcount++; 1850 1851 # 1852 # Make sure signal is correct format, one of: 1853 # sig=## 1854 # signal=## 1855 # sig=SIGXXX 1856 # signal=SIGXXX 1857 # sig=XXX 1858 # signal=SIGXXX 1859 # 1860 ($sigstring) = $signal =~ 1861 /^ 1862 (?:signal|sig)= 1863 (\d+| 1864 (?:SIG)?[[:upper:]]+(?:[+-][123])? 1865 ) 1866 $/x; 1867 1868 if (!defined($sigstring)) { 1869 $ret = 1; 1870 push(@err, [3, gettext( 1871 'rctl "%s" invalid signal "%s"'), 1872 $name, $signal]); 1873 next; 1874 } 1875 1876 $signame = $sigstring; 1877 $signame =~ s/SIG//; 1878 1879 # Make sure specific signal is allowed. 1880 $siglist = $allrules->{'signals'}; 1881 if (!(grep /^$signame$/, @$siglist)) { 1882 $ret = 1; 1883 push(@err, [3, gettext( 1884 'rctl "%s" invalid signal "%s"'), 1885 $name, $signal]); 1886 next; 1887 } 1888 $siglist = $rules->{'signals'}; 1889 1890 if (!(grep /^$signame$/, @$siglist)) { 1891 $ret = 1; 1892 push(@err, [3, gettext( 1893 'rctl "%s" signal not allowed "%s"'), 1894 $name, $signal]); 1895 next; 1896 } 1897 } 1898 1899 if ($nonecount && ($denycount || $sigcount)) { 1900 $ret = 1; 1901 push(@err, [3, gettext( 1902 'rctl "%s" action "none" specified with '. 1903 'other actions'), $name]); 1904 } 1905 } 1906 1907 if (@err) { 1908 return ($ret, \@err); 1909 } else { 1910 return ($ret, \@err); 1911 } 1912 } 1913 1914 1;