1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved.
  23  * Copyright 2011 Nexenta Systems, Inc. All rights reserved.
  24  */
  25 
  26 /*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T                     */
  27 /*        All Rights Reserved                                           */
  28 /*                                                                      */
  29 
  30 /*
  31  * University Copyright- Copyright (c) 1982, 1986, 1988
  32  * The Regents of the University of California
  33  * All Rights Reserved
  34  *
  35  * University Acknowledgment- Portions of this document are derived from
  36  * software developed by the University of California, Berkeley, and its
  37  * contributors.
  38  */
  39 
  40 /*
  41  * chmod option mode files
  42  * where
  43  *      mode is [ugoa][+-=][rwxXlstugo] or an octal number
  44  *      mode is [<+|->A[# <number] ]<aclspec>
  45  *      mode is S<attrspec>
  46  *      option is -R, -f, and -@
  47  */
  48 
  49 /*
  50  *  Note that many convolutions are necessary
  51  *  due to the re-use of bits between locking
  52  *  and setgid
  53  */
  54 
  55 #include <unistd.h>
  56 #include <stdlib.h>
  57 #include <stdio.h>
  58 #include <sys/types.h>
  59 #include <sys/stat.h>
  60 #include <fcntl.h>
  61 #include <dirent.h>
  62 #include <locale.h>
  63 #include <string.h>       /* strerror() */
  64 #include <stdarg.h>
  65 #include <limits.h>
  66 #include <ctype.h>
  67 #include <errno.h>
  68 #include <sys/acl.h>
  69 #include <aclutils.h>
  70 #include <libnvpair.h>
  71 #include <libcmdutils.h>
  72 #include <libgen.h>
  73 #include <attr.h>
  74 
  75 static int      rflag;
  76 static int      fflag;
  77 
  78 extern int      optind;
  79 extern int      errno;
  80 
  81 static int      mac;            /* Alternate to argc (for parseargs) */
  82 static char     **mav;          /* Alternate to argv (for parseargs) */
  83 
  84 static char     *ms;            /* Points to the mode argument */
  85 
  86 #define ACL_ADD                 1
  87 #define ACL_DELETE              2
  88 #define ACL_SLOT_DELETE         3
  89 #define ACL_REPLACE             4
  90 #define ACL_STRIP               5
  91 
  92 #define LEFTBRACE       '{'
  93 #define RIGHTBRACE      '}'
  94 #define A_SEP           ','
  95 #define A_SEP_TOK       ","
  96 
  97 #define A_COMPACT_TYPE  'c'
  98 #define A_VERBOSE_TYPE  'v'
  99 #define A_ALLATTRS_TYPE 'a'
 100 
 101 #define A_SET_OP        '+'
 102 #define A_INVERSE_OP    '-'
 103 #define A_REPLACE_OP    '='
 104 #define A_UNDEF_OP      '\0'
 105 
 106 #define A_SET_TEXT      "set"
 107 #define A_INVERSE_TEXT  "clear"
 108 
 109 #define A_SET_VAL       B_TRUE
 110 #define A_CLEAR_VAL     B_FALSE
 111 
 112 #define ATTR_OPTS       0
 113 #define ATTR_NAMES      1
 114 
 115 #define sec_acls        secptr.acls
 116 #define sec_attrs       secptr.attrs
 117 
 118 typedef struct acl_args {
 119         acl_t   *acl_aclp;
 120         int     acl_slot;
 121         int     acl_action;
 122 } acl_args_t;
 123 
 124 typedef enum {
 125         SEC_ACL,
 126         SEC_ATTR
 127 } chmod_sec_t;
 128 
 129 typedef struct {
 130         chmod_sec_t             sec_type;
 131         union {
 132                 acl_args_t      *acls;
 133                 nvlist_t        *attrs;
 134         } secptr;
 135 } sec_args_t;
 136 
 137 typedef struct attr_name {
 138         char                    *name;
 139         struct attr_name        *next;
 140 } attr_name_t;
 141 
 142 
 143 extern mode_t newmode_common(char *ms, mode_t new_mode, mode_t umsk,
 144     char *file, char *path, o_mode_t *group_clear_bits,
 145     o_mode_t *group_set_bits);
 146 
 147 static int chmodr(char *dir, char *path, mode_t mode, mode_t umsk,
 148     sec_args_t *secp, attr_name_t *attrname);
 149 static int doacl(char *file, struct stat *st, acl_args_t *aclp);
 150 static int dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp,
 151     attr_name_t *attrnames);
 152 static void handle_acl(char *name, o_mode_t group_clear_bits,
 153     o_mode_t group_set_bits);
 154 void errmsg(int severity, int code, char *format, ...);
 155 static void free_attr_names(attr_name_t *attrnames);
 156 static void parseargs(int ac, char *av[]);
 157 static int parse_acl_args(char *arg, sec_args_t **sec_args);
 158 static int parse_attr_args(char *arg, sec_args_t **sec_args);
 159 static void print_attrs(int flag);
 160 static int set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist);
 161 static void usage(void);
 162 
 163 int
 164 main(int argc, char *argv[])
 165 {
 166         int             i, c;
 167         int             status = 0;
 168         mode_t          umsk;
 169         sec_args_t      *sec_args = NULL;
 170         attr_name_t     *attrnames = NULL;
 171         attr_name_t     *attrend = NULL;
 172         attr_name_t     *tattr;
 173 
 174         (void) setlocale(LC_ALL, "");
 175 #if !defined(TEXT_DOMAIN)       /* Should be defined by cc -D */
 176 #define TEXT_DOMAIN "SYS_TEST"  /* Use this only if it weren't */
 177 #endif
 178         (void) textdomain(TEXT_DOMAIN);
 179 
 180         parseargs(argc, argv);
 181 
 182         while ((c = getopt(mac, mav, "Rf@:")) != EOF) {
 183                 switch (c) {
 184                 case 'R':
 185                         rflag++;
 186                         break;
 187                 case 'f':
 188                         fflag++;
 189                         break;
 190                 case '@':
 191                         if (((tattr = malloc(sizeof (attr_name_t))) == NULL) ||
 192                             ((tattr->name = strdup(optarg)) == NULL)) {
 193                                 perror("chmod");
 194                                 exit(2);
 195                         }
 196                         if (attrnames == NULL) {
 197                                 attrnames = tattr;
 198                                 attrnames->next = NULL;
 199                         } else {
 200                                 attrend->next = tattr;
 201                         }
 202                         attrend = tattr;
 203                         break;
 204                 case '?':
 205                         usage();
 206                         exit(2);
 207                 }
 208         }
 209 
 210         /*
 211          * Check for sufficient arguments
 212          * or a usage error.
 213          */
 214 
 215         mac -= optind;
 216         mav += optind;
 217         if ((mac >= 2) && (mav[0][0] == 'A')) {
 218                 if (attrnames != NULL) {
 219                         free_attr_names(attrnames);
 220                         attrnames = NULL;
 221                 }
 222                 if (parse_acl_args(*mav, &sec_args)) {
 223                         usage();
 224                         exit(2);
 225                 }
 226         } else if ((mac >= 2) && (mav[0][0] == 'S')) {
 227                 if (parse_attr_args(*mav, &sec_args)) {
 228                         usage();
 229                         exit(2);
 230 
 231                 /* A no-op attribute operation was specified. */
 232                 } else if (sec_args->sec_attrs == NULL) {
 233                         exit(0);
 234                 }
 235         } else {
 236                 if (mac < 2) {
 237                         usage();
 238                         exit(2);
 239                 }
 240                 if (attrnames != NULL) {
 241                         free_attr_names(attrnames);
 242                         attrnames = NULL;
 243                 }
 244         }
 245 
 246         ms = mav[0];
 247 
 248         umsk = umask(0);
 249         (void) umask(umsk);
 250 
 251         for (i = 1; i < mac; i++) {
 252                 status += dochmod(mav[i], mav[i], umsk, sec_args, attrnames);
 253         }
 254 
 255         return (fflag ? 0 : status);
 256 }
 257 
 258 static void
 259 free_attr_names(attr_name_t *attrnames)
 260 {
 261         attr_name_t     *attrnamesptr = attrnames;
 262         attr_name_t     *tptr;
 263 
 264         while (attrnamesptr != NULL) {
 265                 tptr = attrnamesptr->next;
 266                 if (attrnamesptr->name != NULL) {
 267                         free(attrnamesptr->name);
 268                 }
 269                 attrnamesptr = tptr;
 270         }
 271 }
 272 
 273 static int
 274 dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp,
 275     attr_name_t *attrnames)
 276 {
 277         static struct stat st;
 278         int linkflg = 0;
 279         o_mode_t        group_clear_bits, group_set_bits;
 280 
 281         if (lstat(name, &st) < 0) {
 282                 errmsg(2, 0, gettext("can't access %s\n"), path);
 283                 return (1);
 284         }
 285 
 286         if ((st.st_mode & S_IFMT) == S_IFLNK) {
 287                 linkflg = 1;
 288                 if (stat(name, &st) < 0) {
 289                         errmsg(2, 0, gettext("can't access %s\n"), path);
 290                         return (1);
 291                 }
 292         }
 293 
 294         /* Do not recurse if directory is object of symbolic link */
 295         if (rflag && ((st.st_mode & S_IFMT) == S_IFDIR) && !linkflg) {
 296                 return (chmodr(name, path, st.st_mode, umsk, secp, attrnames));
 297         }
 298 
 299         if (secp != NULL) {
 300                 if (secp->sec_type == SEC_ACL) {
 301                         return (doacl(name, &st, secp->sec_acls));
 302                 } else if (secp->sec_type == SEC_ATTR) {
 303                         return (set_attrs(name, attrnames, secp->sec_attrs));
 304                 } else {
 305                         return (1);
 306                 }
 307         } else {
 308                 if (chmod(name, newmode_common(ms, st.st_mode, umsk, name, path,
 309                     &group_clear_bits, &group_set_bits)) == -1) {
 310                         errmsg(2, 0, gettext("can't change %s\n"), path);
 311                         return (1);
 312                 }
 313         }
 314 
 315         /*
 316          * If the group permissions of the file are being modified,
 317          * make sure that the file's ACL (if it has one) is
 318          * modified also, since chmod is supposed to apply group
 319          * permissions changes to both the acl mask and the
 320          * general group permissions.
 321          */
 322         if (group_clear_bits || group_set_bits)
 323                 handle_acl(name, group_clear_bits, group_set_bits);
 324 
 325         return (0);
 326 }
 327 
 328 static int
 329 chmodr(char *dir, char *path,  mode_t mode, mode_t umsk, sec_args_t *secp,
 330     attr_name_t *attrnames)
 331 {
 332 
 333         DIR *dirp;
 334         struct dirent *dp;
 335         char savedir[PATH_MAX];                 /* dir name to restore */
 336         char currdir[PATH_MAX+1];               /* current dir name + '/' */
 337         char parentdir[PATH_MAX+1];             /* parent dir name  + '/' */
 338         int ecode;
 339         struct stat st;
 340         o_mode_t        group_clear_bits, group_set_bits;
 341 
 342         if (getcwd(savedir, PATH_MAX) == 0)
 343                 errmsg(2, 255, gettext("chmod: could not getcwd %s\n"),
 344                     savedir);
 345 
 346         /*
 347          * Change what we are given before doing it's contents
 348          */
 349         if (secp != NULL) {
 350                 if (lstat(dir, &st) < 0) {
 351                         errmsg(2, 0, gettext("can't access %s\n"), path);
 352                         return (1);
 353                 }
 354                 if (secp->sec_type == SEC_ACL) {
 355                         (void) doacl(dir, &st, secp->sec_acls);
 356                 } else if (secp->sec_type == SEC_ATTR) {
 357                         (void) set_attrs(dir, attrnames, secp->sec_attrs);
 358                 } else {
 359                         return (1);
 360                 }
 361         } else if (chmod(dir, newmode_common(ms, mode, umsk, dir, path,
 362             &group_clear_bits, &group_set_bits)) < 0) {
 363                 errmsg(2, 0, gettext("can't change %s\n"), path);
 364         }
 365 
 366         /*
 367          * If the group permissions of the file are being modified,
 368          * make sure that the file's ACL (if it has one) is
 369          * modified also, since chmod is supposed to apply group
 370          * permissions changes to both the acl mask and the
 371          * general group permissions.
 372          */
 373 
 374         if (secp != NULL) {
 375                 /* only necessary when not setting ACL or system attributes */
 376                 if (group_clear_bits || group_set_bits)
 377                         handle_acl(dir, group_clear_bits, group_set_bits);
 378         }
 379 
 380         if (chdir(dir) < 0) {
 381                 errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno));
 382                 return (1);
 383         }
 384         if ((dirp = opendir(".")) == NULL) {
 385                 errmsg(2, 0, "%s\n", strerror(errno));
 386                 return (1);
 387         }
 388         ecode = 0;
 389 
 390         /*
 391          * Save parent directory path before recursive chmod.
 392          * We'll need this for error printing purposes. Add
 393          * a trailing '/' to the path except in the case where
 394          * the path is just '/'
 395          */
 396 
 397         if (strlcpy(parentdir, path, PATH_MAX + 1) >= PATH_MAX + 1) {
 398                 errmsg(2, 0, gettext("directory path name too long: %s\n"),
 399                     path);
 400                 return (1);
 401         }
 402         if (strcmp(path, "/") != 0)
 403                 if (strlcat(parentdir, "/", PATH_MAX + 1) >= PATH_MAX + 1) {
 404                         errmsg(2, 0,
 405                             gettext("directory path name too long: %s/\n"),
 406                             parentdir);
 407                         return (1);
 408                 }
 409 
 410 
 411         for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))  {
 412 
 413                 if (strcmp(dp->d_name, ".") == 0 ||  /* skip . and .. */
 414                     strcmp(dp->d_name, "..") == 0) {
 415                         continue;
 416                 }
 417                 if (strlcpy(currdir, parentdir, PATH_MAX + 1) >= PATH_MAX + 1) {
 418                         errmsg(2, 0,
 419                             gettext("directory path name too long: %s\n"),
 420                             parentdir);
 421                         return (1);
 422                 }
 423                 if (strlcat(currdir, dp->d_name, PATH_MAX + 1)
 424                     >= PATH_MAX + 1) {
 425                         errmsg(2, 0,
 426                             gettext("directory path name too long: %s%s\n"),
 427                             currdir, dp->d_name);
 428                         return (1);
 429                 }
 430                 ecode += dochmod(dp->d_name, currdir, umsk, secp, attrnames);
 431         }
 432         (void) closedir(dirp);
 433         if (chdir(savedir) < 0) {
 434                 errmsg(2, 255, gettext("can't change back to %s\n"), savedir);
 435         }
 436         return (ecode ? 1 : 0);
 437 }
 438 
 439 /* PRINTFLIKE3 */
 440 void
 441 errmsg(int severity, int code, char *format, ...)
 442 {
 443         va_list ap;
 444         static char *msg[] = {
 445         "",
 446         "ERROR",
 447         "WARNING",
 448         ""
 449         };
 450 
 451         va_start(ap, format);
 452 
 453         /*
 454          * Always print error message if this is a fatal error (code != 0);
 455          * otherwise, print message if fflag == 0 (no -f option specified)
 456          */
 457         if (!fflag || (code != 0)) {
 458                 (void) fprintf(stderr,
 459                     "chmod: %s: ", gettext(msg[severity]));
 460                 (void) vfprintf(stderr, format, ap);
 461         }
 462 
 463         va_end(ap);
 464 
 465         if (code != 0)
 466                 exit(fflag ? 0 : code);
 467 }
 468 
 469 static void
 470 usage(void)
 471 {
 472         (void) fprintf(stderr, gettext(
 473             "usage:\tchmod [-fR] <absolute-mode> file ...\n"));
 474 
 475         (void) fprintf(stderr, gettext(
 476             "\tchmod [-fR] [-@ attribute] ... "
 477             "S<attribute-operation> file ...\n"));
 478 
 479         (void) fprintf(stderr, gettext(
 480             "\tchmod [-fR] <ACL-operation> file ...\n"));
 481 
 482         (void) fprintf(stderr, gettext(
 483             "\tchmod [-fR] <symbolic-mode-list> file ...\n\n"));
 484 
 485         (void) fprintf(stderr, gettext(
 486             "where \t<symbolic-mode-list> is a comma-separated list of\n"));
 487         (void) fprintf(stderr, gettext(
 488             "\t[ugoa]{+|-|=}[rwxXlstugo]\n\n"));
 489 
 490         (void) fprintf(stderr, gettext(
 491             "where \t<attribute-operation> is a comma-separated list of\n"
 492             "\tone or more of the following\n"));
 493         (void) fprintf(stderr, gettext(
 494             "\t[+|-|=]c[<compact-attribute-list>|{<compact-attribute-list>}]\n"
 495             "\t[+|-|=]v[<verbose-attribute-setting>|"
 496             "\'{\'<verbose-attribute-setting-list>\'}\']\n"
 497             "\t[+|-|=]a\n"));
 498         (void) fprintf(stderr, gettext(
 499             "where \t<compact-attribute-list> is a list of zero or more of\n"));
 500         print_attrs(ATTR_OPTS);
 501         (void) fprintf(stderr, gettext(
 502             "where \t<verbose-attribute-setting> is one of\n"));
 503         print_attrs(ATTR_NAMES);
 504         (void) fprintf(stderr, gettext(
 505             "\tand can be, optionally, immediately preceded by \"no\"\n\n"));
 506 
 507         (void) fprintf(stderr, gettext(
 508             "where \t<ACL-operation> is one of the following\n"));
 509         (void) fprintf(stderr, gettext("\tA-<acl_specification>\n"));
 510         (void) fprintf(stderr, gettext("\tA[number]-\n"));
 511         (void) fprintf(stderr, gettext(
 512             "\tA[number]{+|=}<acl_specification>\n"));
 513         (void) fprintf(stderr, gettext(
 514             "where \t<acl-specification> is a comma-separated list of ACEs\n"));
 515 }
 516 
 517 /*
 518  *  parseargs - generate getopt-friendly argument list for backwards
 519  *              compatibility with earlier Solaris usage (eg, chmod -w
 520  *              foo).
 521  *
 522  *  assumes the existence of a static set of alternates to argc and argv,
 523  *  (namely, mac, and mav[]).
 524  *
 525  */
 526 
 527 static void
 528 parseargs(int ac, char *av[])
 529 {
 530         int i;                  /* current argument                     */
 531         int fflag;              /* arg list contains "--"               */
 532         size_t mav_num;         /* number of entries in mav[]           */
 533 
 534         /*
 535          * We add an extra argument slot, in case we need to jam a "--"
 536          * argument into the list.
 537          */
 538 
 539         mav_num = (size_t)ac+2;
 540 
 541         if ((mav = calloc(mav_num, sizeof (char *))) == NULL) {
 542                 perror("chmod");
 543                 exit(2);
 544         }
 545 
 546         /* scan for the use of "--" in the argument list */
 547 
 548         for (fflag = i = 0; i < ac; i ++) {
 549                 if (strcmp(av[i], "--") == 0)
 550                         fflag = 1;
 551         }
 552 
 553         /* process the arguments */
 554 
 555         for (i = mac = 0;
 556             (av[i] != (char *)NULL) && (av[i][0] != (char)NULL);
 557             i++) {
 558                 if (!fflag && av[i][0] == '-') {
 559                         /*
 560                          *  If there is not already a "--" argument specified,
 561                          *  and the argument starts with '-' but does not
 562                          *  contain any of the official option letters, then it
 563                          *  is probably a mode argument beginning with '-'.
 564                          *  Force a "--" into the argument stream in front of
 565                          *  it.
 566                          */
 567 
 568                         if ((strchr(av[i], 'R') == NULL &&
 569                             strchr(av[i], 'f') == NULL) &&
 570                             strchr(av[i], '@') == NULL) {
 571                                 if ((mav[mac++] = strdup("--")) == NULL) {
 572                                         perror("chmod");
 573                                         exit(2);
 574                                 }
 575                         }
 576                 }
 577 
 578                 if ((mav[mac++] = strdup(av[i])) == NULL) {
 579                         perror("chmod");
 580                         exit(2);
 581                 }
 582         }
 583 
 584         mav[mac] = (char *)NULL;
 585 }
 586 
 587 static int
 588 parse_acl_args(char *arg, sec_args_t **sec_args)
 589 {
 590         acl_t *new_acl = NULL;
 591         int slot;
 592         int len;
 593         int action;
 594         acl_args_t *new_acl_args;
 595         char *acl_spec = NULL;
 596         char *end;
 597 
 598         if (arg[0] != 'A')
 599                 return (1);
 600 
 601         slot = strtol(&arg[1], &end, 10);
 602 
 603         len = strlen(arg);
 604         switch (*end) {
 605         case '+':
 606                 action = ACL_ADD;
 607                 acl_spec = ++end;
 608                 break;
 609         case '-':
 610                 if (len == 2 && arg[0] == 'A' && arg[1] == '-')
 611                         action = ACL_STRIP;
 612                 else
 613                         action = ACL_DELETE;
 614                 if (action != ACL_STRIP) {
 615                         acl_spec = ++end;
 616                         if (acl_spec[0] == '\0') {
 617                                 action = ACL_SLOT_DELETE;
 618                                 acl_spec = NULL;
 619                         } else if (arg[1] != '-')
 620                                 return (1);
 621                 }
 622                 break;
 623         case '=':
 624                 /*
 625                  * Was slot specified?
 626                  */
 627                 if (arg[1] == '=')
 628                         slot = -1;
 629                 action = ACL_REPLACE;
 630                 acl_spec = ++end;
 631                 break;
 632         default:
 633                 return (1);
 634         }
 635 
 636         if ((action == ACL_REPLACE || action == ACL_ADD) && acl_spec[0] == '\0')
 637                 return (1);
 638 
 639         if (acl_spec) {
 640                 if (acl_parse(acl_spec, &new_acl)) {
 641                         exit(1);
 642                 }
 643         }
 644 
 645         new_acl_args = malloc(sizeof (acl_args_t));
 646         if (new_acl_args == NULL)
 647                 return (1);
 648 
 649         new_acl_args->acl_aclp = new_acl;
 650         new_acl_args->acl_slot = slot;
 651         new_acl_args->acl_action = action;
 652 
 653         if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) {
 654                 perror("chmod");
 655                 exit(2);
 656         }
 657         (*sec_args)->sec_type = SEC_ACL;
 658         (*sec_args)->sec_acls = new_acl_args;
 659 
 660         return (0);
 661 }
 662 
 663 /*
 664  * This function is called whenever the group permissions of a file
 665  * is being modified.  According to the chmod(1) manpage, any
 666  * change made to the group permissions must be applied to both
 667  * the acl mask and the acl's GROUP_OBJ.  The chmod(2) already
 668  * set the mask, so this routine needs to make the same change
 669  * to the GROUP_OBJ.
 670  */
 671 static void
 672 handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits)
 673 {
 674         int aclcnt, n;
 675         aclent_t *aclp, *tp;
 676         o_mode_t newperm;
 677         /*
 678          * if this file system support ace_t acl's
 679          * then simply return since we don't have an
 680          * acl mask to deal with
 681          */
 682         if (pathconf(name, _PC_ACL_ENABLED) == _ACL_ACE_ENABLED)
 683                 return;
 684         if ((aclcnt = acl(name, GETACLCNT, 0, NULL)) <= MIN_ACL_ENTRIES)
 685                 return; /* it's just a trivial acl; no need to change it */
 686         if ((aclp = (aclent_t *)malloc((sizeof (aclent_t)) * aclcnt))
 687             == NULL) {
 688                 perror("chmod");
 689                 exit(2);
 690         }
 691 
 692         if (acl(name, GETACL, aclcnt, aclp) < 0) {
 693                 free(aclp);
 694                 (void) fprintf(stderr, "chmod: ");
 695                 perror(name);
 696                 return;
 697         }
 698         for (tp = aclp, n = aclcnt; n--; tp++) {
 699                 if (tp->a_type == GROUP_OBJ) {
 700                         newperm = tp->a_perm;
 701                         if (group_clear_bits != 0)
 702                                 newperm &= ~group_clear_bits;
 703                         if (group_set_bits != 0)
 704                                 newperm |= group_set_bits;
 705                         if (newperm != tp->a_perm) {
 706                                 tp->a_perm = newperm;
 707                                 if (acl(name, SETACL, aclcnt, aclp)
 708                                     < 0) {
 709                                         (void) fprintf(stderr, "chmod: ");
 710                                         perror(name);
 711                                 }
 712                         }
 713                         break;
 714                 }
 715         }
 716         free(aclp);
 717 }
 718 
 719 static int
 720 doacl(char *file, struct stat *st, acl_args_t *acl_args)
 721 {
 722         acl_t *aclp;
 723         acl_t *set_aclp;
 724         int error = 0;
 725         void *to, *from;
 726         int len;
 727         int isdir;
 728         isdir = S_ISDIR(st->st_mode);
 729 
 730         error = acl_get(file, 0, &aclp);
 731 
 732         if (error != 0) {
 733                 errmsg(1, 0, "%s\n", acl_strerror(error));
 734                 return (1);
 735         }
 736         switch (acl_args->acl_action) {
 737         case ACL_ADD:
 738                 if ((error = acl_addentries(aclp,
 739                     acl_args->acl_aclp, acl_args->acl_slot)) != 0) {
 740                         errmsg(1, 0, "%s\n", acl_strerror(error));
 741                         acl_free(aclp);
 742                         return (1);
 743                 }
 744                 set_aclp = aclp;
 745                 break;
 746         case ACL_SLOT_DELETE:
 747                 if (acl_args->acl_slot + 1 > aclp->acl_cnt) {
 748                         errmsg(1, 0,
 749                             gettext("Invalid slot specified for removal\n"));
 750                         acl_free(aclp);
 751                         return (1);
 752                 }
 753 
 754                 if (acl_args->acl_slot == 0 && aclp->acl_cnt == 1) {
 755                         errmsg(1, 0,
 756                             gettext("Can't remove all ACL "
 757                             "entries from a file\n"));
 758                         acl_free(aclp);
 759                         return (1);
 760                 }
 761 
 762                 /*
 763                  * remove a single entry
 764                  *
 765                  * if last entry just adjust acl_cnt
 766                  */
 767 
 768                 if ((acl_args->acl_slot + 1) == aclp->acl_cnt)
 769                         aclp->acl_cnt--;
 770                 else {
 771                         to = (char *)aclp->acl_aclp +
 772                             (acl_args->acl_slot * aclp->acl_entry_size);
 773                         from = (char *)to + aclp->acl_entry_size;
 774                         len = (aclp->acl_cnt - acl_args->acl_slot - 1) *
 775                             aclp->acl_entry_size;
 776                         (void) memmove(to, from, len);
 777                         aclp->acl_cnt--;
 778                 }
 779                 set_aclp = aclp;
 780                 break;
 781 
 782         case ACL_DELETE:
 783                 if ((error = acl_removeentries(aclp, acl_args->acl_aclp,
 784                     acl_args->acl_slot, ACL_REMOVE_ALL)) != 0) {
 785                         errmsg(1, 0, "%s\n", acl_strerror(error));
 786                         acl_free(aclp);
 787                         return (1);
 788                 }
 789 
 790                 if (aclp->acl_cnt == 0) {
 791                         errmsg(1, 0,
 792                             gettext("Can't remove all ACL "
 793                             "entries from a file\n"));
 794                         acl_free(aclp);
 795                         return (1);
 796                 }
 797 
 798                 set_aclp = aclp;
 799                 break;
 800         case ACL_REPLACE:
 801                 if (acl_args->acl_slot >= 0)  {
 802                         error = acl_modifyentries(aclp, acl_args->acl_aclp,
 803                             acl_args->acl_slot);
 804                         if (error) {
 805                                 errmsg(1, 0, "%s\n", acl_strerror(error));
 806                                 acl_free(aclp);
 807                                 return (1);
 808                         }
 809                         set_aclp = aclp;
 810                 } else {
 811                         set_aclp = acl_args->acl_aclp;
 812                 }
 813                 break;
 814         case ACL_STRIP:
 815                 error = acl_strip(file, st->st_uid, st->st_gid, st->st_mode);
 816                 if (error) {
 817                         errmsg(1, 0, "%s\n", acl_strerror(error));
 818                         acl_free(aclp);
 819                         return (1);
 820                 }
 821                 acl_free(aclp);
 822                 return (0);
 823                 /*NOTREACHED*/
 824         default:
 825                 errmsg(1, 2, gettext("Unknown ACL action requested\n"));
 826                 /*NOTREACHED*/
 827         }
 828         error = acl_check(set_aclp, isdir);
 829 
 830         if (error) {
 831                 errmsg(1, 2, "%s\n%s", acl_strerror(error),
 832                     gettext("See chmod(1) for more information on "
 833                     "valid ACL syntax\n"));
 834         }
 835         if ((error = acl_set(file, set_aclp)) != 0) {
 836                         errmsg(1, 0, gettext("Failed to set ACL: %s\n"),
 837                             acl_strerror(error));
 838                         acl_free(aclp);
 839                         return (1);
 840         }
 841         acl_free(aclp);
 842         return (0);
 843 }
 844 
 845 /*
 846  * Prints out the attributes in their verbose form:
 847  *      '{'[["no"]<attribute-name>][,["no"]<attribute-name>]...'}'
 848  * similar to output of ls -/v.
 849  */
 850 static void
 851 print_nvlist(nvlist_t *attr_nvlist)
 852 {
 853         int             firsttime = 1;
 854         boolean_t       value;
 855         nvlist_t        *lptr = attr_nvlist;
 856         nvpair_t        *pair = NULL;
 857 
 858         (void) fprintf(stderr, "\t%c", LEFTBRACE);
 859         while (pair = nvlist_next_nvpair(lptr, pair)) {
 860                 if (nvpair_value_boolean_value(pair, &value) == 0) {
 861                         (void) fprintf(stderr, "%s%s%s",
 862                             firsttime ? "" : A_SEP_TOK,
 863                             (value == A_SET_VAL) ? "" : "no",
 864                             nvpair_name(pair));
 865                         firsttime = 0;
 866                 } else {
 867                         (void) fprintf(stderr, gettext(
 868                             "<error retrieving attributes: %s>"),
 869                             strerror(errno));
 870                         break;
 871                 }
 872         }
 873         (void) fprintf(stderr, "%c\n", RIGHTBRACE);
 874 }
 875 
 876 /*
 877  * Add an attribute name and boolean value to an nvlist if an action is to be
 878  * performed for that attribute.  The nvlist will be used later to set all the
 879  * attributes in the nvlist in one operation through a call to setattrat().
 880  *
 881  * If a set operation ('+') was specified, then a boolean representation of the
 882  * attribute's value will be added to the nvlist for that attribute name.  If an
 883  * inverse operation ('-') was specified, then a boolean representation of the
 884  * inverse of the attribute's value will be added to the nvlist for that
 885  * attribute name.
 886  *
 887  * Returns an nvlist of attribute name and boolean value pairs if there are
 888  * attribute actions to be performed, otherwise returns NULL.
 889  */
 890 static nvlist_t *
 891 set_attrs_nvlist(char *attractptr, int numofattrs)
 892 {
 893         int             attribute_set = 0;
 894         f_attr_t        i;
 895         nvlist_t        *attr_nvlist;
 896 
 897         if (nvlist_alloc(&attr_nvlist, NV_UNIQUE_NAME, 0) != 0) {
 898                 perror("chmod");
 899                 exit(2);
 900         }
 901 
 902         for (i = 0; i < numofattrs; i++) {
 903                 if (attractptr[i] != '\0') {
 904                         if ((nvlist_add_boolean_value(attr_nvlist,
 905                             attr_to_name(i),
 906                             (attractptr[i] == A_SET_OP))) != 0) {
 907                                 errmsg(1, 2, gettext(
 908                                     "unable to propagate attribute names and"
 909                                     "values: %s\n"), strerror(errno));
 910                         } else {
 911                                 attribute_set = 1;
 912                         }
 913                 }
 914         }
 915         return (attribute_set ? attr_nvlist : NULL);
 916 }
 917 
 918 /*
 919  * Set the attributes of file, or if specified, of the named attribute file,
 920  * attrname.  Build an nvlist of attribute names and values and call setattrat()
 921  * to set the attributes in one operation.
 922  *
 923  * Returns 0 if successful, otherwise returns 1.
 924  */
 925 static int
 926 set_file_attrs(char *file, char *attrname, nvlist_t *attr_nvlist)
 927 {
 928         int     rc;
 929         char    *filename;
 930 
 931         if (attrname != NULL) {
 932                 filename = attrname;
 933         } else {
 934                 filename = basename(file);
 935         }
 936 
 937         if ((rc = setattrat(AT_FDCWD, XATTR_VIEW_READWRITE, filename,
 938             attr_nvlist)) != 0) {
 939                 char *emsg;
 940                 switch (errno) {
 941                 case EINVAL:
 942                         emsg = gettext("not supported");
 943                         break;
 944                 case EPERM:
 945                         emsg = gettext("not privileged");
 946                         break;
 947                 default:
 948                         emsg = strerror(rc);
 949                 }
 950                 errmsg(1, 0, gettext(
 951                     "cannot set the following attributes on "
 952                     "%s%s%s%s: %s\n"),
 953                     (attrname == NULL) ? "" : gettext("attribute "),
 954                     (attrname == NULL) ? "" : attrname,
 955                     (attrname == NULL) ? "" : gettext(" of "),
 956                     file, emsg);
 957                 print_nvlist(attr_nvlist);
 958         }
 959 
 960         return (rc);
 961 }
 962 
 963 static int
 964 save_cwd(void)
 965 {
 966         return (open(".", O_RDONLY));
 967 }
 968 
 969 static void
 970 rest_cwd(int cwd)
 971 {
 972         if (cwd != -1) {
 973                 if (fchdir(cwd) != 0) {
 974                         errmsg(1, 1, gettext(
 975                             "can't change to current working directory\n"));
 976                 }
 977                 (void) close(cwd);
 978         }
 979 }
 980 
 981 /*
 982  * Returns 1 if filename is a system attribute file, otherwise
 983  * returns 0.
 984  */
 985 static int
 986 is_sattr(char *filename)
 987 {
 988         return (sysattr_type(filename) != _NOT_SATTR);
 989 }
 990 
 991 /*
 992  * Perform the action on the specified named attribute file for the file
 993  * associated with the input file descriptor.  If the named attribute file
 994  * is "*", then the action is to be performed on all the named attribute files
 995  * of the file associated with the input file descriptor.
 996  */
 997 static int
 998 set_named_attrs(char *file, int parentfd, char *attrname, nvlist_t *attr_nvlist)
 999 {
1000         int             dirfd;
1001         int             error = 0;
1002         DIR             *dirp = NULL;
1003         struct dirent   *dp;
1004         struct stat     st;
1005 
1006         if ((attrname == NULL) || (strcmp(attrname, "*") != 0)) {
1007                 /*
1008                  * Make sure the named attribute exists and extended system
1009                  * attributes are supported on the underlying file system.
1010                  */
1011                 if (attrname != NULL) {
1012                         if (fstatat(parentfd, attrname, &st,
1013                             AT_SYMLINK_NOFOLLOW) < 0) {
1014                                 errmsg(2, 0, gettext(
1015                                     "can't access attribute %s of %s\n"),
1016                                     attrname, file);
1017                                 return (1);
1018                         }
1019                         if (sysattr_support(attrname, _PC_SATTR_ENABLED) != 1) {
1020                                 errmsg(1, 0, gettext(
1021                                     "extended system attributes not supported "
1022                                     "for attribute %s of %s\n"),
1023                                     attrname, file);
1024                                 return (1);
1025                         }
1026                 }
1027 
1028                 error = set_file_attrs(file, attrname, attr_nvlist);
1029 
1030         } else {
1031                 if (((dirfd = dup(parentfd)) == -1) ||
1032                     ((dirp = fdopendir(dirfd)) == NULL)) {
1033                         errmsg(1, 0, gettext(
1034                             "cannot open dir pointer of file %s\n"), file);
1035                         if (dirfd > 0) {
1036                                 (void) close(dirfd);
1037                         }
1038                         return (1);
1039                 }
1040 
1041                 while (dp = readdir(dirp)) {
1042                         /*
1043                          * Process all extended attribute files except
1044                          * ".", "..", and extended system attribute files.
1045                          */
1046                         if ((strcmp(dp->d_name, ".") == 0) ||
1047                             (strcmp(dp->d_name, "..") == 0) ||
1048                             is_sattr(dp->d_name)) {
1049                                 continue;
1050                         }
1051 
1052                         if (set_named_attrs(file, parentfd, dp->d_name,
1053                             attr_nvlist) != 0) {
1054                                 error++;
1055                         }
1056                 }
1057                 if (dirp != NULL) {
1058                         (void) closedir(dirp);
1059                 }
1060         }
1061 
1062         return ((error == 0) ? 0 : 1);
1063 }
1064 
1065 /*
1066  * Set the attributes of the specified file, or if specified with -@ on the
1067  * command line, the specified named attributes of the specified file.
1068  *
1069  * Returns 0 if successful, otherwise returns 1.
1070  */
1071 static int
1072 set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist)
1073 {
1074         char            *parentd;
1075         char            *tpath = NULL;
1076         int             cwd;
1077         int             error = 0;
1078         int             parentfd;
1079         attr_name_t     *tattr = attrnames;
1080 
1081         if (attr_nvlist == NULL) {
1082                 return (0);
1083         }
1084 
1085         if (sysattr_support(file, _PC_SATTR_ENABLED) != 1) {
1086                 errmsg(1, 0, gettext(
1087                     "extended system attributes not supported for %s\n"), file);
1088                 return (1);
1089         }
1090 
1091         /*
1092          * Open the parent directory and change into it before attempting
1093          * to set the attributes of the file.
1094          */
1095         if (attrnames == NULL) {
1096                 tpath = strdup(file);
1097                 parentd = dirname(tpath);
1098                 parentfd = open(parentd, O_RDONLY);
1099         } else {
1100                 parentfd = attropen(file, ".", O_RDONLY);
1101         }
1102         if (parentfd == -1) {
1103                 errmsg(1, 0, gettext(
1104                     "cannot open attribute directory of %s\n"), file);
1105                 if (tpath != NULL) {
1106                         free(tpath);
1107                 }
1108                 return (1);
1109         }
1110 
1111         if ((cwd = save_cwd()) < 0) {
1112                 errmsg(1, 1, gettext(
1113                     "can't get current working directory\n"));
1114         }
1115         if (fchdir(parentfd) != 0) {
1116                 errmsg(1, 0, gettext(
1117                     "can't change to parent %sdirectory of %s\n"),
1118                     (attrnames == NULL) ? "" : gettext("attribute "), file);
1119                 (void) close(cwd);
1120                 (void) close(parentfd);
1121                 if (tpath != NULL) {
1122                         free(tpath);
1123                 }
1124                 return (1);
1125         }
1126 
1127         /*
1128          * If no named attribute file names were provided on the command line
1129          * then set the attributes of the base file, otherwise, set the
1130          * attributes for each of the named attribute files specified.
1131          */
1132         if (attrnames == NULL) {
1133                 error = set_named_attrs(file, parentfd, NULL, attr_nvlist);
1134                 free(tpath);
1135         } else {
1136                 while (tattr != NULL) {
1137                         if (set_named_attrs(file, parentfd, tattr->name,
1138                             attr_nvlist) != 0) {
1139                                 error++;
1140                         }
1141                         tattr = tattr->next;
1142                 }
1143         }
1144         (void) close(parentfd);
1145         rest_cwd(cwd);
1146 
1147         return ((error == 0) ? 0 : 1);
1148 }
1149 
1150 /*
1151  * Prints the attributes in either the compact or verbose form indicated
1152  * by flag.
1153  */
1154 static void
1155 print_attrs(int flag)
1156 {
1157         f_attr_t        i;
1158         static int      numofattrs;
1159         int             firsttime = 1;
1160 
1161         numofattrs = attr_count();
1162 
1163         (void) fprintf(stderr, gettext("\t["));
1164         for (i = 0; i < numofattrs; i++) {
1165                 if ((attr_to_xattr_view(i) != XATTR_VIEW_READWRITE) ||
1166                     (attr_to_data_type(i) != DATA_TYPE_BOOLEAN_VALUE)) {
1167                         continue;
1168                 }
1169                 (void) fprintf(stderr, "%s%s",
1170                     (firsttime == 1) ? "" : gettext("|"),
1171                     (flag == ATTR_OPTS) ? attr_to_option(i) : attr_to_name(i));
1172                 firsttime = 0;
1173         }
1174         (void) fprintf(stderr, gettext("]\n"));
1175 }
1176 
1177 /*
1178  * Record what action should be taken on the specified attribute. Only boolean
1179  * read-write attributes can be manipulated.
1180  *
1181  * Returns 0 if successful, otherwise returns 1.
1182  */
1183 static int
1184 set_attr_args(f_attr_t attr, char action, char *attractptr)
1185 {
1186         if ((attr_to_xattr_view(attr) == XATTR_VIEW_READWRITE) &&
1187             (attr_to_data_type(attr) == DATA_TYPE_BOOLEAN_VALUE)) {
1188                 attractptr[attr] = action;
1189                 return (0);
1190         }
1191         return (1);
1192 }
1193 
1194 /*
1195  * Parses the entry and assigns the appropriate action (either '+' or '-' in
1196  * attribute's position in the character array pointed to by attractptr, where
1197  * upon exit, attractptr is positional and the value of each character specifies
1198  * whether to set (a '+'), clear (a '-'), or leave untouched (a '\0') the
1199  * attribute value.
1200  *
1201  * If the entry is an attribute name, then the A_SET_OP action is to be
1202  * performed for this attribute.  If the entry is an attribute name proceeded
1203  * with "no", then the A_INVERSE_OP action is to be performed for this
1204  * attribute.  If the entry is one or more attribute option letters, then step
1205  * through each of the option letters marking the action to be performed for
1206  * each of the attributes associated with the letter as A_SET_OP.
1207  *
1208  * Returns 0 if the entry was a valid attribute(s) and the action to be
1209  * performed on that attribute(s) has been recorded, otherwise returns 1.
1210  */
1211 static int
1212 parse_entry(char *entry, char action, char atype, int len, char *attractptr)
1213 {
1214         char            aopt[2] = {'\0', '\0'};
1215         char            *aptr;
1216         f_attr_t        attr;
1217 
1218         if (atype == A_VERBOSE_TYPE) {
1219                 if ((attr = name_to_attr(entry)) != F_ATTR_INVAL) {
1220                         return (set_attr_args(attr,
1221                             (action == A_REPLACE_OP) ? A_SET_OP : action,
1222                             attractptr));
1223                 } else if ((len > 2) && (strncmp(entry, "no", 2) == 0) &&
1224                     ((attr = name_to_attr(entry + 2)) != F_ATTR_INVAL)) {
1225                         return (set_attr_args(attr, ((action == A_REPLACE_OP) ||
1226                             (action == A_SET_OP)) ? A_INVERSE_OP : A_SET_OP,
1227                             attractptr));
1228                 } else {
1229                         return (1);
1230                 }
1231         } else if (atype == A_COMPACT_TYPE) {
1232                 for (aptr = entry; *aptr != '\0'; aptr++) {
1233                         *aopt = *aptr;
1234                         /*
1235                          * The output of 'ls' can be used as the attribute mode
1236                          * specification for chmod.  This output can contain a
1237                          * hypen ('-') for each attribute that is not set.  If
1238                          * so, ignore them.  If a replace action is being
1239                          * performed, then all attributes that don't have an
1240                          * action set here, will be cleared down the line.
1241                          */
1242                         if (*aptr == '-') {
1243                                 continue;
1244                         }
1245                         if (set_attr_args(option_to_attr(aopt),
1246                             (action == A_REPLACE_OP) ? A_SET_OP : action,
1247                             attractptr) != 0) {
1248                                 return (1);
1249                         }
1250                 }
1251                 return (0);
1252         }
1253         return (1);
1254 }
1255 
1256 /*
1257  * Parse the attribute specification, aoptsstr.  Upon completion, attr_nvlist
1258  * will point to an nvlist which contains pairs of attribute names and values
1259  * to be set; attr_nvlist will be NULL if it is a no-op.
1260  *
1261  * The attribute specification format is
1262  *      S[oper]attr_type[attribute_list]
1263  * where oper is
1264  *      +       set operation of specified attributes in attribute list.
1265  *              This is the default operation.
1266  *      -       inverse operation of specified attributes in attribute list
1267  *      =       replace operation of all attributes.  All attribute operations
1268  *              depend on those specified in the attribute list.  Attributes
1269  *              not specified in the attribute list will be cleared.
1270  * where attr_type is
1271  *      c       compact type.  Each entry in the attribute list is a character
1272  *              option representing an associated attribute name.
1273  *      v       verbose type.  Each entry in the attribute list is an
1274  *              an attribute name which can optionally be preceeded with "no"
1275  *              (to imply the attribute should be cleared).
1276  *      a       all attributes type.  The oper should be applied to all
1277  *              read-write boolean system attributes.  No attribute list should
1278  *              be specified after an 'a' attribute type.
1279  *
1280  * Returns 0 if aoptsstr contained a valid attribute specification,
1281  * otherwise, returns 1.
1282  */
1283 static int
1284 parse_attr_args(char *aoptsstr, sec_args_t **sec_args)
1285 {
1286         char            action;
1287         char            *attractptr;
1288         char            atype;
1289         char            *entry;
1290         char            *eptr;
1291         char            *nextattr;
1292         char            *nextentry;
1293         char            *subentry;
1294         char            *teptr;
1295         char            tok[] = {'\0', '\0'};
1296         int             len;
1297         f_attr_t        i;
1298         int             numofattrs;
1299 
1300         if ((*aoptsstr != 'S') || (*(aoptsstr + 1) == '\0')) {
1301                 return (1);
1302         }
1303 
1304         if ((eptr = strdup(aoptsstr + 1)) == NULL) {
1305                 perror("chmod");
1306                 exit(2);
1307         }
1308         entry = eptr;
1309 
1310         /*
1311          * Create a positional character array to determine a single attribute
1312          * operation to be performed, where each index represents the system
1313          * attribute affected, and it's value in the array represents the action
1314          * to be performed, i.e., a value of '+' means to set the attribute, a
1315          * value of '-' means to clear the attribute, and a value of '\0' means
1316          * to leave the attribute untouched.  Initially, this positional
1317          * character array is all '\0's, representing a no-op.
1318          */
1319         if ((numofattrs = attr_count()) < 1) {
1320                 errmsg(1, 1, gettext("system attributes not supported\n"));
1321         }
1322 
1323         if ((attractptr = calloc(numofattrs, sizeof (char))) == NULL) {
1324                 perror("chmod");
1325                 exit(2);
1326         }
1327 
1328         if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) {
1329                 perror("chmod");
1330                 exit(2);
1331         }
1332         (*sec_args)->sec_type = SEC_ATTR;
1333         (*sec_args)->sec_attrs = NULL;
1334 
1335         /* Parse each attribute operation within the attribute specification. */
1336         while ((entry != NULL) && (*entry != '\0')) {
1337                 action = A_SET_OP;
1338                 atype = '\0';
1339 
1340                 /* Get the operator. */
1341                 switch (*entry) {
1342                 case A_SET_OP:
1343                 case A_INVERSE_OP:
1344                 case A_REPLACE_OP:
1345                         action = *entry++;
1346                         break;
1347                 case A_COMPACT_TYPE:
1348                 case A_VERBOSE_TYPE:
1349                 case A_ALLATTRS_TYPE:
1350                         atype = *entry++;
1351                         action = A_SET_OP;
1352                         break;
1353                 default:
1354                         break;
1355                 }
1356 
1357                 /* An attribute type must be specified. */
1358                 if (atype == '\0') {
1359                         if ((*entry == A_COMPACT_TYPE) ||
1360                             (*entry == A_VERBOSE_TYPE) ||
1361                             (*entry == A_ALLATTRS_TYPE)) {
1362                                 atype = *entry++;
1363                         } else {
1364                                 return (1);
1365                         }
1366                 }
1367 
1368                 /* Get the attribute specification separator. */
1369                 if (*entry == LEFTBRACE) {
1370                         *tok = RIGHTBRACE;
1371                         entry++;
1372                 } else {
1373                         *tok = A_SEP;
1374                 }
1375 
1376                 /* Get the attribute operation */
1377                 if ((nextentry = strpbrk(entry, tok)) != NULL) {
1378                         *nextentry = '\0';
1379                         nextentry++;
1380                 }
1381 
1382                 /* Check for a no-op */
1383                 if ((*entry == '\0') && (atype != A_ALLATTRS_TYPE) &&
1384                     (action != A_REPLACE_OP)) {
1385                         entry = nextentry;
1386                         continue;
1387                 }
1388 
1389                 /*
1390                  * Step through the attribute operation, setting the
1391                  * appropriate values for the specified attributes in the
1392                  * character array, attractptr. A value of '+' will mean the
1393                  * attribute is to be set, and a value of '-' will mean the
1394                  * attribute is to be cleared.  If the value of an attribute
1395                  * remains '\0', then no action is to be taken on that
1396                  * attribute.  As multiple operations specified are
1397                  * accumulated, a single attribute setting operation is
1398                  * represented in attractptr.
1399                  */
1400                 len = strlen(entry);
1401                 if ((*tok == RIGHTBRACE) || (action == A_REPLACE_OP) ||
1402                     (atype == A_ALLATTRS_TYPE)) {
1403 
1404                         if ((action == A_REPLACE_OP) ||
1405                             (atype == A_ALLATTRS_TYPE)) {
1406                                 (void) memset(attractptr, '\0', numofattrs);
1407                         }
1408 
1409                         if (len > 0) {
1410                                 if ((teptr = strdup(entry)) == NULL) {
1411                                         perror("chmod");
1412                                         exit(2);
1413                                 }
1414                                 subentry = teptr;
1415                                 while (subentry != NULL) {
1416                                         if ((nextattr = strpbrk(subentry,
1417                                             A_SEP_TOK)) != NULL) {
1418                                                 *nextattr = '\0';
1419                                                 nextattr++;
1420                                         }
1421                                         if (parse_entry(subentry, action,
1422                                             atype, len, attractptr) != 0) {
1423                                                 return (1);
1424                                         }
1425                                         subentry = nextattr;
1426                                 }
1427                                 free(teptr);
1428                         }
1429 
1430                         /*
1431                          * If performing the replace action, record the
1432                          * attributes and values for the rest of the
1433                          * attributes that have not already been recorded,
1434                          * otherwise record the specified action for all
1435                          * attributes.  Note: set_attr_args() will only record
1436                          * the attribute and action if it is a boolean
1437                          * read-write attribute so we don't need to worry
1438                          * about checking it here.
1439                          */
1440                         if ((action == A_REPLACE_OP) ||
1441                             (atype == A_ALLATTRS_TYPE)) {
1442                                 for (i = 0; i < numofattrs; i++) {
1443                                         if (attractptr[i] == A_UNDEF_OP) {
1444                                                 (void) set_attr_args(i,
1445                                                     (action == A_SET_OP) ?
1446                                                     A_SET_OP : A_INVERSE_OP,
1447                                                     attractptr);
1448                                         }
1449                                 }
1450                         }
1451 
1452                 } else {
1453                         if (parse_entry(entry, action, atype, len,
1454                             attractptr) != 0) {
1455                                 return (1);
1456                         }
1457                 }
1458                 entry = nextentry;
1459         }
1460 
1461         /*
1462          * Populate an nvlist with attribute name and boolean value pairs
1463          * using the single attribute operation.
1464          */
1465         (*sec_args)->sec_attrs = set_attrs_nvlist(attractptr, numofattrs);
1466         free(attractptr);
1467         free(eptr);
1468 
1469         return (0);
1470 }