10Sstevel@tonic-gate /* 20Sstevel@tonic-gate * CDDL HEADER START 30Sstevel@tonic-gate * 40Sstevel@tonic-gate * The contents of this file are subject to the terms of the 51591Scasper * Common Development and Distribution License (the "License"). 61591Scasper * You may not use this file except in compliance with the License. 70Sstevel@tonic-gate * 80Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 90Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing. 100Sstevel@tonic-gate * See the License for the specific language governing permissions 110Sstevel@tonic-gate * and limitations under the License. 120Sstevel@tonic-gate * 130Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each 140Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 150Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the 160Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying 170Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner] 180Sstevel@tonic-gate * 190Sstevel@tonic-gate * CDDL HEADER END 200Sstevel@tonic-gate */ 210Sstevel@tonic-gate /* 22*12871SAmrita.Sadhukhan@Sun.COM * Copyright (c) 1988, 2010, Oracle and/or its affiliates. All rights reserved. 230Sstevel@tonic-gate */ 240Sstevel@tonic-gate 250Sstevel@tonic-gate /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 260Sstevel@tonic-gate /* All Rights Reserved */ 270Sstevel@tonic-gate /* */ 280Sstevel@tonic-gate 290Sstevel@tonic-gate /* 300Sstevel@tonic-gate * University Copyright- Copyright (c) 1982, 1986, 1988 310Sstevel@tonic-gate * The Regents of the University of California 320Sstevel@tonic-gate * All Rights Reserved 330Sstevel@tonic-gate * 340Sstevel@tonic-gate * University Acknowledgment- Portions of this document are derived from 350Sstevel@tonic-gate * software developed by the University of California, Berkeley, and its 360Sstevel@tonic-gate * contributors. 370Sstevel@tonic-gate */ 380Sstevel@tonic-gate 390Sstevel@tonic-gate /* 400Sstevel@tonic-gate * chmod option mode files 410Sstevel@tonic-gate * where 420Sstevel@tonic-gate * mode is [ugoa][+-=][rwxXlstugo] or an octal number 43789Sahrens * mode is [<+|->A[# <number] ]<aclspec> 445331Samw * mode is S<attrspec> 455331Samw * option is -R, -f, and -@ 460Sstevel@tonic-gate */ 470Sstevel@tonic-gate 480Sstevel@tonic-gate /* 490Sstevel@tonic-gate * Note that many convolutions are necessary 500Sstevel@tonic-gate * due to the re-use of bits between locking 510Sstevel@tonic-gate * and setgid 520Sstevel@tonic-gate */ 530Sstevel@tonic-gate 540Sstevel@tonic-gate #include <unistd.h> 550Sstevel@tonic-gate #include <stdlib.h> 560Sstevel@tonic-gate #include <stdio.h> 570Sstevel@tonic-gate #include <sys/types.h> 580Sstevel@tonic-gate #include <sys/stat.h> 595331Samw #include <fcntl.h> 600Sstevel@tonic-gate #include <dirent.h> 610Sstevel@tonic-gate #include <locale.h> 620Sstevel@tonic-gate #include <string.h> /* strerror() */ 630Sstevel@tonic-gate #include <stdarg.h> 640Sstevel@tonic-gate #include <limits.h> 65789Sahrens #include <ctype.h> 660Sstevel@tonic-gate #include <errno.h> 670Sstevel@tonic-gate #include <sys/acl.h> 68789Sahrens #include <aclutils.h> 695331Samw #include <libnvpair.h> 705331Samw #include <libcmdutils.h> 715331Samw #include <libgen.h> 725331Samw #include <attr.h> 730Sstevel@tonic-gate 740Sstevel@tonic-gate static int rflag; 750Sstevel@tonic-gate static int fflag; 760Sstevel@tonic-gate 770Sstevel@tonic-gate extern int optind; 780Sstevel@tonic-gate extern int errno; 790Sstevel@tonic-gate 800Sstevel@tonic-gate static int mac; /* Alternate to argc (for parseargs) */ 810Sstevel@tonic-gate static char **mav; /* Alternate to argv (for parseargs) */ 820Sstevel@tonic-gate 830Sstevel@tonic-gate static char *ms; /* Points to the mode argument */ 840Sstevel@tonic-gate 855331Samw #define ACL_ADD 1 865331Samw #define ACL_DELETE 2 875331Samw #define ACL_SLOT_DELETE 3 885331Samw #define ACL_REPLACE 4 895331Samw #define ACL_STRIP 5 905331Samw 915331Samw #define LEFTBRACE '{' 925331Samw #define RIGHTBRACE '}' 935331Samw #define A_SEP ',' 945331Samw #define A_SEP_TOK "," 955331Samw 965331Samw #define A_COMPACT_TYPE 'c' 975331Samw #define A_VERBOSE_TYPE 'v' 985331Samw #define A_ALLATTRS_TYPE 'a' 995331Samw 1005331Samw #define A_SET_OP '+' 1015331Samw #define A_INVERSE_OP '-' 1025331Samw #define A_REPLACE_OP '=' 1035331Samw #define A_UNDEF_OP '\0' 1045331Samw 1055331Samw #define A_SET_TEXT "set" 1065331Samw #define A_INVERSE_TEXT "clear" 1075331Samw 1085331Samw #define A_SET_VAL B_TRUE 1095331Samw #define A_CLEAR_VAL B_FALSE 1105331Samw 1115331Samw #define ATTR_OPTS 0 1125331Samw #define ATTR_NAMES 1 1135331Samw 1145331Samw #define sec_acls secptr.acls 1155331Samw #define sec_attrs secptr.attrs 116789Sahrens 117789Sahrens typedef struct acl_args { 118789Sahrens acl_t *acl_aclp; 119789Sahrens int acl_slot; 120789Sahrens int acl_action; 121789Sahrens } acl_args_t; 122789Sahrens 1235331Samw typedef enum { 1245331Samw SEC_ACL, 1255331Samw SEC_ATTR 1265331Samw } chmod_sec_t; 1275331Samw 1285331Samw typedef struct { 1295331Samw chmod_sec_t sec_type; 1305331Samw union { 1315331Samw acl_args_t *acls; 1325331Samw nvlist_t *attrs; 1335331Samw } secptr; 1345331Samw } sec_args_t; 1350Sstevel@tonic-gate 1365331Samw typedef struct attr_name { 1375331Samw char *name; 1385331Samw struct attr_name *next; 1395331Samw } attr_name_t; 1405331Samw 1415331Samw 1425331Samw extern mode_t newmode_common(char *ms, mode_t new_mode, mode_t umsk, 1435331Samw char *file, char *path, o_mode_t *group_clear_bits, 1445331Samw o_mode_t *group_set_bits); 1455331Samw 1465331Samw static int chmodr(char *dir, char *path, mode_t mode, mode_t umsk, 1475331Samw sec_args_t *secp, attr_name_t *attrname); 148789Sahrens static int doacl(char *file, struct stat *st, acl_args_t *aclp); 1495331Samw static int dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp, 1505331Samw attr_name_t *attrnames); 1510Sstevel@tonic-gate static void handle_acl(char *name, o_mode_t group_clear_bits, 152789Sahrens o_mode_t group_set_bits); 1535331Samw void errmsg(int severity, int code, char *format, ...); 1545331Samw static void free_attr_names(attr_name_t *attrnames); 1555331Samw static void parseargs(int ac, char *av[]); 1565331Samw static int parse_acl_args(char *arg, sec_args_t **sec_args); 1575331Samw static int parse_attr_args(char *arg, sec_args_t **sec_args); 1585331Samw static void print_attrs(int flag); 1595331Samw static int set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist); 160789Sahrens static void usage(void); 1610Sstevel@tonic-gate 1620Sstevel@tonic-gate int 1630Sstevel@tonic-gate main(int argc, char *argv[]) 1640Sstevel@tonic-gate { 1655331Samw int i, c; 1665331Samw int status = 0; 1675331Samw mode_t umsk; 1685331Samw sec_args_t *sec_args = NULL; 1695331Samw attr_name_t *attrnames = NULL; 1705331Samw attr_name_t *attrend = NULL; 1715331Samw attr_name_t *tattr; 1720Sstevel@tonic-gate 1730Sstevel@tonic-gate (void) setlocale(LC_ALL, ""); 1740Sstevel@tonic-gate #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 1750Sstevel@tonic-gate #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 1760Sstevel@tonic-gate #endif 1770Sstevel@tonic-gate (void) textdomain(TEXT_DOMAIN); 1780Sstevel@tonic-gate 1790Sstevel@tonic-gate parseargs(argc, argv); 1800Sstevel@tonic-gate 1815331Samw while ((c = getopt(mac, mav, "Rf@:")) != EOF) { 1820Sstevel@tonic-gate switch (c) { 1830Sstevel@tonic-gate case 'R': 1840Sstevel@tonic-gate rflag++; 1850Sstevel@tonic-gate break; 1860Sstevel@tonic-gate case 'f': 1870Sstevel@tonic-gate fflag++; 1880Sstevel@tonic-gate break; 1895331Samw case '@': 1905331Samw if (((tattr = malloc(sizeof (attr_name_t))) == NULL) || 1915331Samw ((tattr->name = strdup(optarg)) == NULL)) { 1925331Samw perror("chmod"); 1935331Samw exit(2); 1945331Samw } 1955331Samw if (attrnames == NULL) { 1965331Samw attrnames = tattr; 1975331Samw attrnames->next = NULL; 1985331Samw } else { 1995331Samw attrend->next = tattr; 2005331Samw } 2015331Samw attrend = tattr; 2025331Samw break; 2030Sstevel@tonic-gate case '?': 2040Sstevel@tonic-gate usage(); 2050Sstevel@tonic-gate exit(2); 2060Sstevel@tonic-gate } 2070Sstevel@tonic-gate } 2080Sstevel@tonic-gate 2090Sstevel@tonic-gate /* 2100Sstevel@tonic-gate * Check for sufficient arguments 2110Sstevel@tonic-gate * or a usage error. 2120Sstevel@tonic-gate */ 2130Sstevel@tonic-gate 2140Sstevel@tonic-gate mac -= optind; 2150Sstevel@tonic-gate mav += optind; 2165331Samw if ((mac >= 2) && (mav[0][0] == 'A')) { 2175331Samw if (attrnames != NULL) { 2185331Samw free_attr_names(attrnames); 2195331Samw attrnames = NULL; 2205331Samw } 2215331Samw if (parse_acl_args(*mav, &sec_args)) { 222789Sahrens usage(); 223789Sahrens exit(2); 224789Sahrens } 2255331Samw } else if ((mac >= 2) && (mav[0][0] == 'S')) { 2265331Samw if (parse_attr_args(*mav, &sec_args)) { 2275331Samw usage(); 2285331Samw exit(2); 2295331Samw 2305331Samw /* A no-op attribute operation was specified. */ 2315331Samw } else if (sec_args->sec_attrs == NULL) { 2325331Samw exit(0); 2335331Samw } 234789Sahrens } else { 235789Sahrens if (mac < 2) { 236789Sahrens usage(); 237789Sahrens exit(2); 238789Sahrens } 2395331Samw if (attrnames != NULL) { 2405331Samw free_attr_names(attrnames); 2415331Samw attrnames = NULL; 2425331Samw } 2430Sstevel@tonic-gate } 2440Sstevel@tonic-gate 2450Sstevel@tonic-gate ms = mav[0]; 2460Sstevel@tonic-gate 2470Sstevel@tonic-gate umsk = umask(0); 2480Sstevel@tonic-gate (void) umask(umsk); 2490Sstevel@tonic-gate 250789Sahrens for (i = 1; i < mac; i++) { 2515331Samw status += dochmod(mav[i], mav[i], umsk, sec_args, attrnames); 252789Sahrens } 2530Sstevel@tonic-gate 2540Sstevel@tonic-gate return (fflag ? 0 : status); 2550Sstevel@tonic-gate } 2560Sstevel@tonic-gate 2575331Samw static void 2585331Samw free_attr_names(attr_name_t *attrnames) 2595331Samw { 2605331Samw attr_name_t *attrnamesptr = attrnames; 2615331Samw attr_name_t *tptr; 2625331Samw 2635331Samw while (attrnamesptr != NULL) { 2645331Samw tptr = attrnamesptr->next; 2655331Samw if (attrnamesptr->name != NULL) { 2665331Samw free(attrnamesptr->name); 2675331Samw } 2685331Samw attrnamesptr = tptr; 2695331Samw } 2705331Samw } 2715331Samw 2720Sstevel@tonic-gate static int 2735331Samw dochmod(char *name, char *path, mode_t umsk, sec_args_t *secp, 2745331Samw attr_name_t *attrnames) 2750Sstevel@tonic-gate { 2760Sstevel@tonic-gate static struct stat st; 2770Sstevel@tonic-gate int linkflg = 0; 2780Sstevel@tonic-gate o_mode_t group_clear_bits, group_set_bits; 2790Sstevel@tonic-gate 2800Sstevel@tonic-gate if (lstat(name, &st) < 0) { 2810Sstevel@tonic-gate errmsg(2, 0, gettext("can't access %s\n"), path); 2820Sstevel@tonic-gate return (1); 2830Sstevel@tonic-gate } 2840Sstevel@tonic-gate 2850Sstevel@tonic-gate if ((st.st_mode & S_IFMT) == S_IFLNK) { 2860Sstevel@tonic-gate linkflg = 1; 2870Sstevel@tonic-gate if (stat(name, &st) < 0) { 2880Sstevel@tonic-gate errmsg(2, 0, gettext("can't access %s\n"), path); 2890Sstevel@tonic-gate return (1); 2900Sstevel@tonic-gate } 2910Sstevel@tonic-gate } 2920Sstevel@tonic-gate 2930Sstevel@tonic-gate /* Do not recurse if directory is object of symbolic link */ 2945331Samw if (rflag && ((st.st_mode & S_IFMT) == S_IFDIR) && !linkflg) { 2955331Samw return (chmodr(name, path, st.st_mode, umsk, secp, attrnames)); 2965331Samw } 2970Sstevel@tonic-gate 2985331Samw if (secp != NULL) { 2995331Samw if (secp->sec_type == SEC_ACL) { 3005331Samw return (doacl(name, &st, secp->sec_acls)); 3015331Samw } else if (secp->sec_type == SEC_ATTR) { 3025331Samw return (set_attrs(name, attrnames, secp->sec_attrs)); 3035331Samw } else { 3045331Samw return (1); 3055331Samw } 3065331Samw } else { 3075331Samw if (chmod(name, newmode_common(ms, st.st_mode, umsk, name, path, 3085331Samw &group_clear_bits, &group_set_bits)) == -1) { 3095331Samw errmsg(2, 0, gettext("can't change %s\n"), path); 3105331Samw return (1); 3115331Samw } 3120Sstevel@tonic-gate } 3130Sstevel@tonic-gate 3140Sstevel@tonic-gate /* 3150Sstevel@tonic-gate * If the group permissions of the file are being modified, 3160Sstevel@tonic-gate * make sure that the file's ACL (if it has one) is 3170Sstevel@tonic-gate * modified also, since chmod is supposed to apply group 3180Sstevel@tonic-gate * permissions changes to both the acl mask and the 3190Sstevel@tonic-gate * general group permissions. 3200Sstevel@tonic-gate */ 3210Sstevel@tonic-gate if (group_clear_bits || group_set_bits) 3220Sstevel@tonic-gate handle_acl(name, group_clear_bits, group_set_bits); 3230Sstevel@tonic-gate 3240Sstevel@tonic-gate return (0); 3250Sstevel@tonic-gate } 3260Sstevel@tonic-gate 3270Sstevel@tonic-gate static int 3285331Samw chmodr(char *dir, char *path, mode_t mode, mode_t umsk, sec_args_t *secp, 3295331Samw attr_name_t *attrnames) 3300Sstevel@tonic-gate { 3310Sstevel@tonic-gate 3320Sstevel@tonic-gate DIR *dirp; 3330Sstevel@tonic-gate struct dirent *dp; 3340Sstevel@tonic-gate char savedir[PATH_MAX]; /* dir name to restore */ 3350Sstevel@tonic-gate char currdir[PATH_MAX+1]; /* current dir name + '/' */ 3360Sstevel@tonic-gate char parentdir[PATH_MAX+1]; /* parent dir name + '/' */ 3370Sstevel@tonic-gate int ecode; 338789Sahrens struct stat st; 3390Sstevel@tonic-gate o_mode_t group_clear_bits, group_set_bits; 3400Sstevel@tonic-gate 3410Sstevel@tonic-gate if (getcwd(savedir, PATH_MAX) == 0) 3420Sstevel@tonic-gate errmsg(2, 255, gettext("chmod: could not getcwd %s\n"), 3430Sstevel@tonic-gate savedir); 3440Sstevel@tonic-gate 3450Sstevel@tonic-gate /* 3460Sstevel@tonic-gate * Change what we are given before doing it's contents 3470Sstevel@tonic-gate */ 3485331Samw if (secp != NULL) { 349789Sahrens if (lstat(dir, &st) < 0) { 350789Sahrens errmsg(2, 0, gettext("can't access %s\n"), path); 351789Sahrens return (1); 352789Sahrens } 3535331Samw if (secp->sec_type == SEC_ACL) { 354*12871SAmrita.Sadhukhan@Sun.COM (void) doacl(dir, &st, secp->sec_acls); 3555331Samw } else if (secp->sec_type == SEC_ATTR) { 356*12871SAmrita.Sadhukhan@Sun.COM (void) set_attrs(dir, attrnames, secp->sec_attrs); 3575331Samw } else { 358789Sahrens return (1); 3595331Samw } 360789Sahrens } else if (chmod(dir, newmode_common(ms, mode, umsk, dir, path, 3610Sstevel@tonic-gate &group_clear_bits, &group_set_bits)) < 0) { 3620Sstevel@tonic-gate errmsg(2, 0, gettext("can't change %s\n"), path); 3630Sstevel@tonic-gate } 3640Sstevel@tonic-gate 3650Sstevel@tonic-gate /* 3660Sstevel@tonic-gate * If the group permissions of the file are being modified, 3670Sstevel@tonic-gate * make sure that the file's ACL (if it has one) is 3680Sstevel@tonic-gate * modified also, since chmod is supposed to apply group 3690Sstevel@tonic-gate * permissions changes to both the acl mask and the 3700Sstevel@tonic-gate * general group permissions. 3710Sstevel@tonic-gate */ 372789Sahrens 3735331Samw if (secp != NULL) { 3745331Samw /* only necessary when not setting ACL or system attributes */ 375789Sahrens if (group_clear_bits || group_set_bits) 376789Sahrens handle_acl(dir, group_clear_bits, group_set_bits); 377789Sahrens } 3780Sstevel@tonic-gate 3790Sstevel@tonic-gate if (chdir(dir) < 0) { 3800Sstevel@tonic-gate errmsg(2, 0, "%s/%s: %s\n", savedir, dir, strerror(errno)); 3810Sstevel@tonic-gate return (1); 3820Sstevel@tonic-gate } 3830Sstevel@tonic-gate if ((dirp = opendir(".")) == NULL) { 3840Sstevel@tonic-gate errmsg(2, 0, "%s\n", strerror(errno)); 3850Sstevel@tonic-gate return (1); 3860Sstevel@tonic-gate } 3870Sstevel@tonic-gate ecode = 0; 3880Sstevel@tonic-gate 3890Sstevel@tonic-gate /* 3900Sstevel@tonic-gate * Save parent directory path before recursive chmod. 3910Sstevel@tonic-gate * We'll need this for error printing purposes. Add 3920Sstevel@tonic-gate * a trailing '/' to the path except in the case where 3930Sstevel@tonic-gate * the path is just '/' 3940Sstevel@tonic-gate */ 3950Sstevel@tonic-gate 3963520Sas145665 if (strlcpy(parentdir, path, PATH_MAX + 1) >= PATH_MAX + 1) { 3973520Sas145665 errmsg(2, 0, gettext("directory path name too long: %s\n"), 3983520Sas145665 path); 3993520Sas145665 return (1); 4003520Sas145665 } 4010Sstevel@tonic-gate if (strcmp(path, "/") != 0) 4023520Sas145665 if (strlcat(parentdir, "/", PATH_MAX + 1) >= PATH_MAX + 1) { 4033520Sas145665 errmsg(2, 0, 4043520Sas145665 gettext("directory path name too long: %s/\n"), 4053520Sas145665 parentdir); 4063520Sas145665 return (1); 4073520Sas145665 } 4083520Sas145665 4090Sstevel@tonic-gate 4100Sstevel@tonic-gate for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) { 4113520Sas145665 4121591Scasper if (strcmp(dp->d_name, ".") == 0 || /* skip . and .. */ 4131591Scasper strcmp(dp->d_name, "..") == 0) { 4141591Scasper continue; 4151591Scasper } 4163520Sas145665 if (strlcpy(currdir, parentdir, PATH_MAX + 1) >= PATH_MAX + 1) { 4173520Sas145665 errmsg(2, 0, 4183520Sas145665 gettext("directory path name too long: %s\n"), 4193520Sas145665 parentdir); 4203520Sas145665 return (1); 4213520Sas145665 } 4223520Sas145665 if (strlcat(currdir, dp->d_name, PATH_MAX + 1) 4233520Sas145665 >= PATH_MAX + 1) { 4243520Sas145665 errmsg(2, 0, 4253520Sas145665 gettext("directory path name too long: %s%s\n"), 4263520Sas145665 currdir, dp->d_name); 4273520Sas145665 return (1); 4283520Sas145665 } 4295331Samw ecode += dochmod(dp->d_name, currdir, umsk, secp, attrnames); 4300Sstevel@tonic-gate } 4310Sstevel@tonic-gate (void) closedir(dirp); 4320Sstevel@tonic-gate if (chdir(savedir) < 0) { 4330Sstevel@tonic-gate errmsg(2, 255, gettext("can't change back to %s\n"), savedir); 4340Sstevel@tonic-gate } 4350Sstevel@tonic-gate return (ecode ? 1 : 0); 4360Sstevel@tonic-gate } 4370Sstevel@tonic-gate 4380Sstevel@tonic-gate /* PRINTFLIKE3 */ 4390Sstevel@tonic-gate void 4400Sstevel@tonic-gate errmsg(int severity, int code, char *format, ...) 4410Sstevel@tonic-gate { 4420Sstevel@tonic-gate va_list ap; 4430Sstevel@tonic-gate static char *msg[] = { 4440Sstevel@tonic-gate "", 4450Sstevel@tonic-gate "ERROR", 4460Sstevel@tonic-gate "WARNING", 4470Sstevel@tonic-gate "" 4480Sstevel@tonic-gate }; 4490Sstevel@tonic-gate 4500Sstevel@tonic-gate va_start(ap, format); 4510Sstevel@tonic-gate 4520Sstevel@tonic-gate /* 4530Sstevel@tonic-gate * Always print error message if this is a fatal error (code == 0); 4540Sstevel@tonic-gate * otherwise, print message if fflag == 0 (no -f option specified) 4550Sstevel@tonic-gate */ 4560Sstevel@tonic-gate if (!fflag || (code != 0)) { 4570Sstevel@tonic-gate (void) fprintf(stderr, 4585331Samw "chmod: %s: ", gettext(msg[severity])); 4590Sstevel@tonic-gate (void) vfprintf(stderr, format, ap); 4600Sstevel@tonic-gate } 4610Sstevel@tonic-gate 4620Sstevel@tonic-gate va_end(ap); 4630Sstevel@tonic-gate 4640Sstevel@tonic-gate if (code != 0) 4650Sstevel@tonic-gate exit(fflag ? 0 : code); 4660Sstevel@tonic-gate } 4670Sstevel@tonic-gate 4680Sstevel@tonic-gate static void 4690Sstevel@tonic-gate usage(void) 4700Sstevel@tonic-gate { 4710Sstevel@tonic-gate (void) fprintf(stderr, gettext( 4720Sstevel@tonic-gate "usage:\tchmod [-fR] <absolute-mode> file ...\n")); 4730Sstevel@tonic-gate 4740Sstevel@tonic-gate (void) fprintf(stderr, gettext( 4755331Samw "\tchmod [-fR] [-@ attribute] ... " 4765331Samw "S<attribute-operation> file ...\n")); 4775331Samw 4785331Samw (void) fprintf(stderr, gettext( 479789Sahrens "\tchmod [-fR] <ACL-operation> file ...\n")); 480789Sahrens 481789Sahrens (void) fprintf(stderr, gettext( 4825331Samw "\tchmod [-fR] <symbolic-mode-list> file ...\n\n")); 483789Sahrens 4840Sstevel@tonic-gate (void) fprintf(stderr, gettext( 4850Sstevel@tonic-gate "where \t<symbolic-mode-list> is a comma-separated list of\n")); 4865331Samw (void) fprintf(stderr, gettext( 4875331Samw "\t[ugoa]{+|-|=}[rwxXlstugo]\n\n")); 4880Sstevel@tonic-gate 4890Sstevel@tonic-gate (void) fprintf(stderr, gettext( 4905331Samw "where \t<attribute-operation> is a comma-separated list of\n" 4915331Samw "\tone or more of the following\n")); 4925331Samw (void) fprintf(stderr, gettext( 4935331Samw "\t[+|-|=]c[<compact-attribute-list>|{<compact-attribute-list>}]\n" 4945331Samw "\t[+|-|=]v[<verbose-attribute-setting>|" 4955331Samw "\'{\'<verbose-attribute-setting-list>\'}\']\n" 4965331Samw "\t[+|-|=]a\n")); 4975331Samw (void) fprintf(stderr, gettext( 4985331Samw "where \t<compact-attribute-list> is a list of zero or more of\n")); 4995331Samw print_attrs(ATTR_OPTS); 5005331Samw (void) fprintf(stderr, gettext( 5015331Samw "where \t<verbose-attribute-setting> is one of\n")); 5025331Samw print_attrs(ATTR_NAMES); 5035331Samw (void) fprintf(stderr, gettext( 5045331Samw "\tand can be, optionally, immediately preceded by \"no\"\n\n")); 505789Sahrens 506789Sahrens (void) fprintf(stderr, gettext( 507789Sahrens "where \t<ACL-operation> is one of the following\n")); 508789Sahrens (void) fprintf(stderr, gettext("\tA-<acl_specification>\n")); 509789Sahrens (void) fprintf(stderr, gettext("\tA[number]-\n")); 510789Sahrens (void) fprintf(stderr, gettext( 511789Sahrens "\tA[number]{+|=}<acl_specification>\n")); 512789Sahrens (void) fprintf(stderr, gettext( 513789Sahrens "where \t<acl-specification> is a comma-separated list of ACEs\n")); 5140Sstevel@tonic-gate } 5150Sstevel@tonic-gate 5160Sstevel@tonic-gate /* 5170Sstevel@tonic-gate * parseargs - generate getopt-friendly argument list for backwards 5180Sstevel@tonic-gate * compatibility with earlier Solaris usage (eg, chmod -w 5190Sstevel@tonic-gate * foo). 5200Sstevel@tonic-gate * 5210Sstevel@tonic-gate * assumes the existence of a static set of alternates to argc and argv, 5220Sstevel@tonic-gate * (namely, mac, and mav[]). 5230Sstevel@tonic-gate * 5240Sstevel@tonic-gate */ 5250Sstevel@tonic-gate 5260Sstevel@tonic-gate static void 5270Sstevel@tonic-gate parseargs(int ac, char *av[]) 5280Sstevel@tonic-gate { 5290Sstevel@tonic-gate int i; /* current argument */ 5300Sstevel@tonic-gate int fflag; /* arg list contains "--" */ 5310Sstevel@tonic-gate size_t mav_num; /* number of entries in mav[] */ 5320Sstevel@tonic-gate 5330Sstevel@tonic-gate /* 5340Sstevel@tonic-gate * We add an extra argument slot, in case we need to jam a "--" 5350Sstevel@tonic-gate * argument into the list. 5360Sstevel@tonic-gate */ 5370Sstevel@tonic-gate 5380Sstevel@tonic-gate mav_num = (size_t)ac+2; 5390Sstevel@tonic-gate 5400Sstevel@tonic-gate if ((mav = calloc(mav_num, sizeof (char *))) == NULL) { 5410Sstevel@tonic-gate perror("chmod"); 5420Sstevel@tonic-gate exit(2); 5430Sstevel@tonic-gate } 5440Sstevel@tonic-gate 5450Sstevel@tonic-gate /* scan for the use of "--" in the argument list */ 5460Sstevel@tonic-gate 5470Sstevel@tonic-gate for (fflag = i = 0; i < ac; i ++) { 5480Sstevel@tonic-gate if (strcmp(av[i], "--") == 0) 5495331Samw fflag = 1; 5500Sstevel@tonic-gate } 5510Sstevel@tonic-gate 5520Sstevel@tonic-gate /* process the arguments */ 5530Sstevel@tonic-gate 5540Sstevel@tonic-gate for (i = mac = 0; 5550Sstevel@tonic-gate (av[i] != (char *)NULL) && (av[i][0] != (char)NULL); 5560Sstevel@tonic-gate i++) { 5570Sstevel@tonic-gate if (!fflag && av[i][0] == '-') { 5580Sstevel@tonic-gate /* 5590Sstevel@tonic-gate * If there is not already a "--" argument specified, 5600Sstevel@tonic-gate * and the argument starts with '-' but does not 5610Sstevel@tonic-gate * contain any of the official option letters, then it 5620Sstevel@tonic-gate * is probably a mode argument beginning with '-'. 5630Sstevel@tonic-gate * Force a "--" into the argument stream in front of 5640Sstevel@tonic-gate * it. 5650Sstevel@tonic-gate */ 5660Sstevel@tonic-gate 5670Sstevel@tonic-gate if ((strchr(av[i], 'R') == NULL && 5685331Samw strchr(av[i], 'f') == NULL) && 5695331Samw strchr(av[i], '@') == NULL) { 5705331Samw if ((mav[mac++] = strdup("--")) == NULL) { 5715331Samw perror("chmod"); 5725331Samw exit(2); 5735331Samw } 5740Sstevel@tonic-gate } 5750Sstevel@tonic-gate } 5760Sstevel@tonic-gate 5775331Samw if ((mav[mac++] = strdup(av[i])) == NULL) { 5785331Samw perror("chmod"); 5795331Samw exit(2); 5805331Samw } 5810Sstevel@tonic-gate } 5820Sstevel@tonic-gate 5830Sstevel@tonic-gate mav[mac] = (char *)NULL; 5840Sstevel@tonic-gate } 5850Sstevel@tonic-gate 5865331Samw static int 5875331Samw parse_acl_args(char *arg, sec_args_t **sec_args) 588789Sahrens { 589789Sahrens acl_t *new_acl = NULL; 590789Sahrens int slot; 591789Sahrens int len; 592789Sahrens int action; 593789Sahrens acl_args_t *new_acl_args; 594789Sahrens char *acl_spec = NULL; 595789Sahrens char *end; 596789Sahrens 597789Sahrens if (arg[0] != 'A') 598789Sahrens return (1); 599789Sahrens 600789Sahrens slot = strtol(&arg[1], &end, 10); 601789Sahrens 602789Sahrens len = strlen(arg); 603789Sahrens switch (*end) { 604789Sahrens case '+': 605789Sahrens action = ACL_ADD; 606789Sahrens acl_spec = ++end; 607789Sahrens break; 608789Sahrens case '-': 609789Sahrens if (len == 2 && arg[0] == 'A' && arg[1] == '-') 610789Sahrens action = ACL_STRIP; 611789Sahrens else 612789Sahrens action = ACL_DELETE; 613789Sahrens if (action != ACL_STRIP) { 614789Sahrens acl_spec = ++end; 615789Sahrens if (acl_spec[0] == '\0') { 616789Sahrens action = ACL_SLOT_DELETE; 617789Sahrens acl_spec = NULL; 618789Sahrens } else if (arg[1] != '-') 619789Sahrens return (1); 620789Sahrens } 621789Sahrens break; 622789Sahrens case '=': 623865Smarks /* 624865Smarks * Was slot specified? 625865Smarks */ 626865Smarks if (arg[1] == '=') 627865Smarks slot = -1; 628789Sahrens action = ACL_REPLACE; 629789Sahrens acl_spec = ++end; 630789Sahrens break; 631789Sahrens default: 632789Sahrens return (1); 633789Sahrens } 634789Sahrens 635789Sahrens if ((action == ACL_REPLACE || action == ACL_ADD) && acl_spec[0] == '\0') 636789Sahrens return (1); 637789Sahrens 638789Sahrens if (acl_spec) { 6391420Smarks if (acl_parse(acl_spec, &new_acl)) { 6401420Smarks exit(1); 641789Sahrens } 642789Sahrens } 643789Sahrens 644789Sahrens new_acl_args = malloc(sizeof (acl_args_t)); 645789Sahrens if (new_acl_args == NULL) 646789Sahrens return (1); 647789Sahrens 648789Sahrens new_acl_args->acl_aclp = new_acl; 649789Sahrens new_acl_args->acl_slot = slot; 650789Sahrens new_acl_args->acl_action = action; 651789Sahrens 6525331Samw if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) { 6535331Samw perror("chmod"); 6545331Samw exit(2); 6555331Samw } 6565331Samw (*sec_args)->sec_type = SEC_ACL; 6575331Samw (*sec_args)->sec_acls = new_acl_args; 658789Sahrens 659789Sahrens return (0); 660789Sahrens } 661789Sahrens 6620Sstevel@tonic-gate /* 6630Sstevel@tonic-gate * This function is called whenever the group permissions of a file 6640Sstevel@tonic-gate * is being modified. According to the chmod(1) manpage, any 6650Sstevel@tonic-gate * change made to the group permissions must be applied to both 6660Sstevel@tonic-gate * the acl mask and the acl's GROUP_OBJ. The chmod(2) already 6670Sstevel@tonic-gate * set the mask, so this routine needs to make the same change 6680Sstevel@tonic-gate * to the GROUP_OBJ. 6690Sstevel@tonic-gate */ 6700Sstevel@tonic-gate static void 6710Sstevel@tonic-gate handle_acl(char *name, o_mode_t group_clear_bits, o_mode_t group_set_bits) 6720Sstevel@tonic-gate { 6730Sstevel@tonic-gate int aclcnt, n; 6740Sstevel@tonic-gate aclent_t *aclp, *tp; 6750Sstevel@tonic-gate o_mode_t newperm; 676789Sahrens /* 677789Sahrens * if this file system support ace_t acl's 678789Sahrens * then simply return since we don't have an 679789Sahrens * acl mask to deal with 680789Sahrens */ 681789Sahrens if (pathconf(name, _PC_ACL_ENABLED) == _ACL_ACE_ENABLED) 682789Sahrens return; 6830Sstevel@tonic-gate if ((aclcnt = acl(name, GETACLCNT, 0, NULL)) <= MIN_ACL_ENTRIES) 6840Sstevel@tonic-gate return; /* it's just a trivial acl; no need to change it */ 6850Sstevel@tonic-gate if ((aclp = (aclent_t *)malloc((sizeof (aclent_t)) * aclcnt)) 6860Sstevel@tonic-gate == NULL) { 6870Sstevel@tonic-gate perror("chmod"); 6880Sstevel@tonic-gate exit(2); 6890Sstevel@tonic-gate } 6900Sstevel@tonic-gate 6910Sstevel@tonic-gate if (acl(name, GETACL, aclcnt, aclp) < 0) { 6920Sstevel@tonic-gate free(aclp); 6930Sstevel@tonic-gate (void) fprintf(stderr, "chmod: "); 6940Sstevel@tonic-gate perror(name); 6950Sstevel@tonic-gate return; 6960Sstevel@tonic-gate } 6970Sstevel@tonic-gate for (tp = aclp, n = aclcnt; n--; tp++) { 6980Sstevel@tonic-gate if (tp->a_type == GROUP_OBJ) { 6990Sstevel@tonic-gate newperm = tp->a_perm; 7000Sstevel@tonic-gate if (group_clear_bits != 0) 7010Sstevel@tonic-gate newperm &= ~group_clear_bits; 7020Sstevel@tonic-gate if (group_set_bits != 0) 7030Sstevel@tonic-gate newperm |= group_set_bits; 7040Sstevel@tonic-gate if (newperm != tp->a_perm) { 7050Sstevel@tonic-gate tp->a_perm = newperm; 7060Sstevel@tonic-gate if (acl(name, SETACL, aclcnt, aclp) 7070Sstevel@tonic-gate < 0) { 7080Sstevel@tonic-gate (void) fprintf(stderr, "chmod: "); 7090Sstevel@tonic-gate perror(name); 7100Sstevel@tonic-gate } 7110Sstevel@tonic-gate } 7120Sstevel@tonic-gate break; 7130Sstevel@tonic-gate } 7140Sstevel@tonic-gate } 7150Sstevel@tonic-gate free(aclp); 7160Sstevel@tonic-gate } 717789Sahrens 718789Sahrens static int 719789Sahrens doacl(char *file, struct stat *st, acl_args_t *acl_args) 720789Sahrens { 721789Sahrens acl_t *aclp; 722789Sahrens acl_t *set_aclp; 723789Sahrens int error = 0; 724789Sahrens void *to, *from; 725789Sahrens int len; 726789Sahrens int isdir; 727789Sahrens isdir = S_ISDIR(st->st_mode); 728789Sahrens 729789Sahrens error = acl_get(file, 0, &aclp); 730789Sahrens 731789Sahrens if (error != 0) { 732789Sahrens errmsg(1, 1, "%s\n", acl_strerror(error)); 733789Sahrens return (1); 734789Sahrens } 735789Sahrens switch (acl_args->acl_action) { 736789Sahrens case ACL_ADD: 737789Sahrens if ((error = acl_addentries(aclp, 7385331Samw acl_args->acl_aclp, acl_args->acl_slot)) != 0) { 7395331Samw errmsg(1, 1, "%s\n", acl_strerror(error)); 7405331Samw acl_free(aclp); 7415331Samw return (1); 742789Sahrens } 743789Sahrens set_aclp = aclp; 744789Sahrens break; 745789Sahrens case ACL_SLOT_DELETE: 746789Sahrens if (acl_args->acl_slot + 1 > aclp->acl_cnt) { 747789Sahrens errmsg(1, 1, 748789Sahrens gettext("Invalid slot specified for removal\n")); 749789Sahrens acl_free(aclp); 750789Sahrens return (1); 751789Sahrens } 752789Sahrens 753789Sahrens if (acl_args->acl_slot == 0 && aclp->acl_cnt == 1) { 754789Sahrens errmsg(1, 1, 755789Sahrens gettext("Can't remove all ACL " 756789Sahrens "entries from a file\n")); 757789Sahrens acl_free(aclp); 758789Sahrens return (1); 759789Sahrens } 760789Sahrens 761789Sahrens /* 762789Sahrens * remove a single entry 763789Sahrens * 764789Sahrens * if last entry just adjust acl_cnt 765789Sahrens */ 766789Sahrens 767789Sahrens if ((acl_args->acl_slot + 1) == aclp->acl_cnt) 768789Sahrens aclp->acl_cnt--; 769789Sahrens else { 770789Sahrens to = (char *)aclp->acl_aclp + 771789Sahrens (acl_args->acl_slot * aclp->acl_entry_size); 772789Sahrens from = (char *)to + aclp->acl_entry_size; 773789Sahrens len = (aclp->acl_cnt - acl_args->acl_slot - 1) * 774789Sahrens aclp->acl_entry_size; 775789Sahrens (void) memmove(to, from, len); 776789Sahrens aclp->acl_cnt--; 777789Sahrens } 778789Sahrens set_aclp = aclp; 779789Sahrens break; 780789Sahrens 781789Sahrens case ACL_DELETE: 782789Sahrens if ((error = acl_removeentries(aclp, acl_args->acl_aclp, 783789Sahrens acl_args->acl_slot, ACL_REMOVE_ALL)) != 0) { 784789Sahrens errmsg(1, 1, "%s\n", acl_strerror(error)); 785789Sahrens acl_free(aclp); 786789Sahrens return (1); 787789Sahrens } 788789Sahrens 789789Sahrens if (aclp->acl_cnt == 0) { 790789Sahrens errmsg(1, 1, 791789Sahrens gettext("Can't remove all ACL " 792789Sahrens "entries from a file\n")); 793789Sahrens acl_free(aclp); 794789Sahrens return (1); 795789Sahrens } 796789Sahrens 797789Sahrens set_aclp = aclp; 798789Sahrens break; 799789Sahrens case ACL_REPLACE: 800789Sahrens if (acl_args->acl_slot >= 0) { 801789Sahrens error = acl_modifyentries(aclp, acl_args->acl_aclp, 802789Sahrens acl_args->acl_slot); 803789Sahrens if (error) { 804789Sahrens errmsg(1, 1, "%s\n", acl_strerror(error)); 805789Sahrens acl_free(aclp); 806789Sahrens return (1); 807789Sahrens } 808789Sahrens set_aclp = aclp; 809789Sahrens } else { 810789Sahrens set_aclp = acl_args->acl_aclp; 811789Sahrens } 812789Sahrens break; 813789Sahrens case ACL_STRIP: 814789Sahrens error = acl_strip(file, st->st_uid, st->st_gid, st->st_mode); 815789Sahrens if (error) { 816789Sahrens errmsg(1, 1, "%s\n", acl_strerror(error)); 817789Sahrens return (1); 818789Sahrens } 819789Sahrens acl_free(aclp); 820789Sahrens return (0); 821789Sahrens /*NOTREACHED*/ 822789Sahrens default: 823789Sahrens errmsg(1, 0, gettext("Unknown ACL action requested\n")); 824789Sahrens return (1); 825789Sahrens break; 826789Sahrens } 827789Sahrens error = acl_check(set_aclp, isdir); 828789Sahrens 829789Sahrens if (error) { 830789Sahrens errmsg(1, 0, "%s\n%s", acl_strerror(error), 831789Sahrens gettext("See chmod(1) for more information on " 832789Sahrens "valid ACL syntax\n")); 833789Sahrens return (1); 834789Sahrens } 835789Sahrens if ((error = acl_set(file, set_aclp)) != 0) { 836789Sahrens errmsg(1, 0, gettext("Failed to set ACL: %s\n"), 837789Sahrens acl_strerror(error)); 838789Sahrens acl_free(aclp); 839789Sahrens return (1); 840789Sahrens } 841789Sahrens acl_free(aclp); 842789Sahrens return (0); 843789Sahrens } 8445331Samw 8455331Samw /* 8465331Samw * Prints out the attributes in their verbose form: 8475331Samw * '{'[["no"]<attribute-name>][,["no"]<attribute-name>]...'}' 8485331Samw * similar to output of ls -/v. 8495331Samw */ 8505331Samw static void 8515331Samw print_nvlist(nvlist_t *attr_nvlist) 8525331Samw { 8535331Samw int firsttime = 1; 8545331Samw boolean_t value; 8555331Samw nvlist_t *lptr = attr_nvlist; 8565331Samw nvpair_t *pair = NULL; 8575331Samw 8585331Samw (void) fprintf(stderr, "\t%c", LEFTBRACE); 8595331Samw while (pair = nvlist_next_nvpair(lptr, pair)) { 8605331Samw if (nvpair_value_boolean_value(pair, &value) == 0) { 8615331Samw (void) fprintf(stderr, "%s%s%s", 8625331Samw firsttime ? "" : A_SEP_TOK, 8635331Samw (value == A_SET_VAL) ? "" : "no", 8645331Samw nvpair_name(pair)); 8655331Samw firsttime = 0; 8665331Samw } else { 8675331Samw (void) fprintf(stderr, gettext( 8685331Samw "<error retrieving attributes: %s>"), 8695331Samw strerror(errno)); 8705331Samw break; 8715331Samw } 8725331Samw } 8735331Samw (void) fprintf(stderr, "%c\n", RIGHTBRACE); 8745331Samw } 8755331Samw 8765331Samw /* 8775331Samw * Add an attribute name and boolean value to an nvlist if an action is to be 8785331Samw * performed for that attribute. The nvlist will be used later to set all the 8795331Samw * attributes in the nvlist in one operation through a call to setattrat(). 8805331Samw * 8815331Samw * If a set operation ('+') was specified, then a boolean representation of the 8825331Samw * attribute's value will be added to the nvlist for that attribute name. If an 8835331Samw * inverse operation ('-') was specified, then a boolean representation of the 8845331Samw * inverse of the attribute's value will be added to the nvlist for that 8855331Samw * attribute name. 8865331Samw * 8875331Samw * Returns an nvlist of attribute name and boolean value pairs if there are 8885331Samw * attribute actions to be performed, otherwise returns NULL. 8895331Samw */ 8905331Samw static nvlist_t * 8915331Samw set_attrs_nvlist(char *attractptr, int numofattrs) 8925331Samw { 8935331Samw int attribute_set = 0; 8945331Samw f_attr_t i; 8955331Samw nvlist_t *attr_nvlist; 8965331Samw 8975331Samw if (nvlist_alloc(&attr_nvlist, NV_UNIQUE_NAME, 0) != 0) { 8985331Samw perror("chmod"); 8995331Samw exit(2); 9005331Samw } 9015331Samw 9025331Samw for (i = 0; i < numofattrs; i++) { 9035331Samw if (attractptr[i] != '\0') { 9045331Samw if ((nvlist_add_boolean_value(attr_nvlist, 9055331Samw attr_to_name(i), 9065331Samw (attractptr[i] == A_SET_OP))) != 0) { 9075331Samw errmsg(1, 2, gettext( 9085331Samw "unable to propagate attribute names and" 9095331Samw "values: %s\n"), strerror(errno)); 9105331Samw } else { 9115331Samw attribute_set = 1; 9125331Samw } 9135331Samw } 9145331Samw } 9155331Samw return (attribute_set ? attr_nvlist : NULL); 9165331Samw } 9175331Samw 9185331Samw /* 9195331Samw * Set the attributes of file, or if specified, of the named attribute file, 9205331Samw * attrname. Build an nvlist of attribute names and values and call setattrat() 9215331Samw * to set the attributes in one operation. 9225331Samw * 9235331Samw * Returns 0 if successful, otherwise returns 1. 9245331Samw */ 9255331Samw static int 9265331Samw set_file_attrs(char *file, char *attrname, nvlist_t *attr_nvlist) 9275331Samw { 9285331Samw int rc; 9295331Samw char *filename; 9305331Samw 9315331Samw if (attrname != NULL) { 9325331Samw filename = attrname; 9335331Samw } else { 9345331Samw filename = basename(file); 9355331Samw } 9365331Samw 9375331Samw if ((rc = setattrat(AT_FDCWD, XATTR_VIEW_READWRITE, filename, 9385331Samw attr_nvlist)) != 0) { 9395331Samw char *emsg; 9405331Samw switch (errno) { 9415331Samw case EINVAL: 9425331Samw emsg = gettext("not supported"); 9435331Samw break; 9445331Samw case EPERM: 9455331Samw emsg = gettext("not privileged"); 9465331Samw break; 9475331Samw default: 9485331Samw emsg = strerror(rc); 9495331Samw } 9505331Samw errmsg(1, 0, gettext( 9515331Samw "cannot set the following attributes on " 9525331Samw "%s%s%s%s: %s\n"), 9535331Samw (attrname == NULL) ? "" : gettext("attribute "), 9545331Samw (attrname == NULL) ? "" : attrname, 9555331Samw (attrname == NULL) ? "" : gettext(" of "), 9565331Samw file, emsg); 9575331Samw print_nvlist(attr_nvlist); 9585331Samw } 9595331Samw 9605331Samw return (rc); 9615331Samw } 9625331Samw 9635331Samw static int 9645331Samw save_cwd(void) 9655331Samw { 9665331Samw return (open(".", O_RDONLY)); 9675331Samw } 9685331Samw 9695331Samw static void 9705331Samw rest_cwd(int cwd) 9715331Samw { 9725331Samw if (cwd != -1) { 9735331Samw if (fchdir(cwd) != 0) { 9745331Samw errmsg(1, 1, gettext( 9755331Samw "can't change to current working directory\n")); 9765331Samw } 9775331Samw (void) close(cwd); 9785331Samw } 9795331Samw } 9805331Samw 9815331Samw /* 9825331Samw * Returns 1 if filename is a system attribute file, otherwise 9835331Samw * returns 0. 9845331Samw */ 9855331Samw static int 9865331Samw is_sattr(char *filename) 9875331Samw { 9885331Samw return (sysattr_type(filename) != _NOT_SATTR); 9895331Samw } 9905331Samw 9915331Samw /* 9925331Samw * Perform the action on the specified named attribute file for the file 9935331Samw * associated with the input file descriptor. If the named attribute file 9945331Samw * is "*", then the action is to be performed on all the named attribute files 9955331Samw * of the file associated with the input file descriptor. 9965331Samw */ 9975331Samw static int 9985331Samw set_named_attrs(char *file, int parentfd, char *attrname, nvlist_t *attr_nvlist) 9995331Samw { 10005331Samw int dirfd; 10015331Samw int error = 0; 10025331Samw DIR *dirp = NULL; 10035331Samw struct dirent *dp; 10045331Samw struct stat st; 10055331Samw 10065331Samw if ((attrname == NULL) || (strcmp(attrname, "*") != 0)) { 10075331Samw /* 10085331Samw * Make sure the named attribute exists and extended system 10095331Samw * attributes are supported on the underlying file system. 10105331Samw */ 10115331Samw if (attrname != NULL) { 10125331Samw if (fstatat(parentfd, attrname, &st, 10135331Samw AT_SYMLINK_NOFOLLOW) < 0) { 10145331Samw errmsg(2, 0, gettext( 10155331Samw "can't access attribute %s of %s\n"), 10165331Samw attrname, file); 10175331Samw return (1); 10185331Samw } 10195331Samw if (sysattr_support(attrname, _PC_SATTR_ENABLED) != 1) { 10205331Samw errmsg(1, 0, gettext( 10215331Samw "extended system attributes not supported " 10225331Samw "for attribute %s of %s\n"), 10235331Samw attrname, file); 10245331Samw return (1); 10255331Samw } 10265331Samw } 10275331Samw 10285331Samw error = set_file_attrs(file, attrname, attr_nvlist); 10295331Samw 10305331Samw } else { 10315331Samw if (((dirfd = dup(parentfd)) == -1) || 10325331Samw ((dirp = fdopendir(dirfd)) == NULL)) { 10335331Samw errmsg(1, 0, gettext( 10345331Samw "cannot open dir pointer of file %s\n"), file); 10355331Samw if (dirfd > 0) { 10365331Samw (void) close(dirfd); 10375331Samw } 10385331Samw return (1); 10395331Samw } 10405331Samw 10415331Samw while (dp = readdir(dirp)) { 10425331Samw /* 10435331Samw * Process all extended attribute files except 10445331Samw * ".", "..", and extended system attribute files. 10455331Samw */ 10465331Samw if ((strcmp(dp->d_name, ".") == 0) || 10475331Samw (strcmp(dp->d_name, "..") == 0) || 10485331Samw is_sattr(dp->d_name)) { 10495331Samw continue; 10505331Samw } 10515331Samw 10525331Samw if (set_named_attrs(file, parentfd, dp->d_name, 10535331Samw attr_nvlist) != 0) { 10545331Samw error++; 10555331Samw } 10565331Samw } 10575331Samw if (dirp != NULL) { 10585331Samw (void) closedir(dirp); 10595331Samw } 10605331Samw } 10615331Samw 10625331Samw return ((error == 0) ? 0 : 1); 10635331Samw } 10645331Samw 10655331Samw /* 10665331Samw * Set the attributes of the specified file, or if specified with -@ on the 10675331Samw * command line, the specified named attributes of the specified file. 10685331Samw * 10695331Samw * Returns 0 if successful, otherwise returns 1. 10705331Samw */ 10715331Samw static int 10725331Samw set_attrs(char *file, attr_name_t *attrnames, nvlist_t *attr_nvlist) 10735331Samw { 10745331Samw char *parentd; 10755331Samw char *tpath = NULL; 10765331Samw int cwd; 10775331Samw int error = 0; 10785331Samw int parentfd; 10795331Samw attr_name_t *tattr = attrnames; 10805331Samw 10815331Samw if (attr_nvlist == NULL) { 10825331Samw return (0); 10835331Samw } 10845331Samw 10855331Samw if (sysattr_support(file, _PC_SATTR_ENABLED) != 1) { 10865331Samw errmsg(1, 0, gettext( 10875331Samw "extended system attributes not supported for %s\n"), file); 10885331Samw return (1); 10895331Samw } 10905331Samw 10915331Samw /* 10925331Samw * Open the parent directory and change into it before attempting 10935331Samw * to set the attributes of the file. 10945331Samw */ 10955331Samw if (attrnames == NULL) { 10965331Samw tpath = strdup(file); 10975331Samw parentd = dirname(tpath); 10985331Samw parentfd = open(parentd, O_RDONLY); 10995331Samw } else { 11005331Samw parentfd = attropen(file, ".", O_RDONLY); 11015331Samw } 11025331Samw if (parentfd == -1) { 11035331Samw errmsg(1, 0, gettext( 11045331Samw "cannot open attribute directory of %s\n"), file); 11055331Samw if (tpath != NULL) { 11065331Samw free(tpath); 11075331Samw } 11085331Samw return (1); 11095331Samw } 11105331Samw 11115331Samw if ((cwd = save_cwd()) < 0) { 11125331Samw errmsg(1, 1, gettext( 11135331Samw "can't get current working directory\n")); 11145331Samw } 11155331Samw if (fchdir(parentfd) != 0) { 11165331Samw errmsg(1, 0, gettext( 11175331Samw "can't change to parent %sdirectory of %s\n"), 11185331Samw (attrnames == NULL) ? "" : gettext("attribute "), file); 11195331Samw (void) close(cwd); 11205331Samw (void) close(parentfd); 11215331Samw if (tpath != NULL) { 11225331Samw free(tpath); 11235331Samw } 11245331Samw return (1); 11255331Samw } 11265331Samw 11275331Samw /* 11285331Samw * If no named attribute file names were provided on the command line 11295331Samw * then set the attributes of the base file, otherwise, set the 11305331Samw * attributes for each of the named attribute files specified. 11315331Samw */ 11325331Samw if (attrnames == NULL) { 11335331Samw error = set_named_attrs(file, parentfd, NULL, attr_nvlist); 11345331Samw free(tpath); 11355331Samw } else { 11365331Samw while (tattr != NULL) { 11375331Samw if (set_named_attrs(file, parentfd, tattr->name, 11385331Samw attr_nvlist) != 0) { 11395331Samw error++; 11405331Samw } 11415331Samw tattr = tattr->next; 11425331Samw } 11435331Samw } 11445331Samw (void) close(parentfd); 11455331Samw rest_cwd(cwd); 11465331Samw 11475331Samw return ((error == 0) ? 0 : 1); 11485331Samw } 11495331Samw 11505331Samw /* 11515331Samw * Prints the attributes in either the compact or verbose form indicated 11525331Samw * by flag. 11535331Samw */ 11545331Samw static void 11555331Samw print_attrs(int flag) 11565331Samw { 11575331Samw f_attr_t i; 11585331Samw static int numofattrs; 11595331Samw int firsttime = 1; 11605331Samw 11615331Samw numofattrs = attr_count(); 11625331Samw 11635331Samw (void) fprintf(stderr, gettext("\t[")); 11645331Samw for (i = 0; i < numofattrs; i++) { 11655331Samw if ((attr_to_xattr_view(i) != XATTR_VIEW_READWRITE) || 11665331Samw (attr_to_data_type(i) != DATA_TYPE_BOOLEAN_VALUE)) { 11675331Samw continue; 11685331Samw } 11695331Samw (void) fprintf(stderr, "%s%s", 11705331Samw (firsttime == 1) ? "" : gettext("|"), 11715331Samw (flag == ATTR_OPTS) ? attr_to_option(i) : attr_to_name(i)); 11725331Samw firsttime = 0; 11735331Samw } 11745331Samw (void) fprintf(stderr, gettext("]\n")); 11755331Samw } 11765331Samw 11775331Samw /* 11785331Samw * Record what action should be taken on the specified attribute. Only boolean 11795331Samw * read-write attributes can be manipulated. 11805331Samw * 11815331Samw * Returns 0 if successful, otherwise returns 1. 11825331Samw */ 11835331Samw static int 11845331Samw set_attr_args(f_attr_t attr, char action, char *attractptr) 11855331Samw { 11865331Samw if ((attr_to_xattr_view(attr) == XATTR_VIEW_READWRITE) && 11875331Samw (attr_to_data_type(attr) == DATA_TYPE_BOOLEAN_VALUE)) { 11885331Samw attractptr[attr] = action; 11895331Samw return (0); 11905331Samw } 11915331Samw return (1); 11925331Samw } 11935331Samw 11945331Samw /* 11955331Samw * Parses the entry and assigns the appropriate action (either '+' or '-' in 11965331Samw * attribute's position in the character array pointed to by attractptr, where 11975331Samw * upon exit, attractptr is positional and the value of each character specifies 11985331Samw * whether to set (a '+'), clear (a '-'), or leave untouched (a '\0') the 11995331Samw * attribute value. 12005331Samw * 12015331Samw * If the entry is an attribute name, then the A_SET_OP action is to be 12025331Samw * performed for this attribute. If the entry is an attribute name proceeded 12035331Samw * with "no", then the A_INVERSE_OP action is to be performed for this 12045331Samw * attribute. If the entry is one or more attribute option letters, then step 12055331Samw * through each of the option letters marking the action to be performed for 12065331Samw * each of the attributes associated with the letter as A_SET_OP. 12075331Samw * 12085331Samw * Returns 0 if the entry was a valid attribute(s) and the action to be 12095331Samw * performed on that attribute(s) has been recorded, otherwise returns 1. 12105331Samw */ 12115331Samw static int 12125331Samw parse_entry(char *entry, char action, char atype, int len, char *attractptr) 12135331Samw { 12145331Samw char aopt[2] = {'\0', '\0'}; 12155331Samw char *aptr; 12165331Samw f_attr_t attr; 12175331Samw 12185331Samw if (atype == A_VERBOSE_TYPE) { 12195331Samw if ((attr = name_to_attr(entry)) != F_ATTR_INVAL) { 12205331Samw return (set_attr_args(attr, 12215331Samw (action == A_REPLACE_OP) ? A_SET_OP : action, 12225331Samw attractptr)); 12235331Samw } else if ((len > 2) && (strncmp(entry, "no", 2) == 0) && 12245331Samw ((attr = name_to_attr(entry + 2)) != F_ATTR_INVAL)) { 12255331Samw return (set_attr_args(attr, ((action == A_REPLACE_OP) || 12265331Samw (action == A_SET_OP)) ? A_INVERSE_OP : A_SET_OP, 12275331Samw attractptr)); 12285331Samw } else { 12295331Samw return (1); 12305331Samw } 12315331Samw } else if (atype == A_COMPACT_TYPE) { 12325331Samw for (aptr = entry; *aptr != '\0'; aptr++) { 12335331Samw *aopt = *aptr; 12345331Samw /* 12355331Samw * The output of 'ls' can be used as the attribute mode 12365331Samw * specification for chmod. This output can contain a 12375331Samw * hypen ('-') for each attribute that is not set. If 12385331Samw * so, ignore them. If a replace action is being 12395331Samw * performed, then all attributes that don't have an 12405331Samw * action set here, will be cleared down the line. 12415331Samw */ 12425331Samw if (*aptr == '-') { 12435331Samw continue; 12445331Samw } 12455331Samw if (set_attr_args(option_to_attr(aopt), 12465331Samw (action == A_REPLACE_OP) ? A_SET_OP : action, 12475331Samw attractptr) != 0) { 12485331Samw return (1); 12495331Samw } 12505331Samw } 12515331Samw return (0); 12525331Samw } 12535331Samw return (1); 12545331Samw } 12555331Samw 12565331Samw /* 12575331Samw * Parse the attribute specification, aoptsstr. Upon completion, attr_nvlist 12585331Samw * will point to an nvlist which contains pairs of attribute names and values 12595331Samw * to be set; attr_nvlist will be NULL if it is a no-op. 12605331Samw * 12615331Samw * The attribute specification format is 12625331Samw * S[oper]attr_type[attribute_list] 12635331Samw * where oper is 12645331Samw * + set operation of specified attributes in attribute list. 12655331Samw * This is the default operation. 12665331Samw * - inverse operation of specified attributes in attribute list 12675331Samw * = replace operation of all attributes. All attribute operations 12685331Samw * depend on those specified in the attribute list. Attributes 12695331Samw * not specified in the attribute list will be cleared. 12705331Samw * where attr_type is 12715331Samw * c compact type. Each entry in the attribute list is a character 12725331Samw * option representing an associated attribute name. 12735331Samw * v verbose type. Each entry in the attribute list is an 12745331Samw * an attribute name which can optionally be preceeded with "no" 12755331Samw * (to imply the attribute should be cleared). 12765331Samw * a all attributes type. The oper should be applied to all 12775331Samw * read-write boolean system attributes. No attribute list should 12785331Samw * be specified after an 'a' attribute type. 12795331Samw * 12805331Samw * Returns 0 if aoptsstr contained a valid attribute specification, 12815331Samw * otherwise, returns 1. 12825331Samw */ 12835331Samw static int 12845331Samw parse_attr_args(char *aoptsstr, sec_args_t **sec_args) 12855331Samw { 12865331Samw char action; 12875331Samw char *attractptr; 12885331Samw char atype; 12895331Samw char *entry; 12905331Samw char *eptr; 12915331Samw char *nextattr; 12925331Samw char *nextentry; 12935331Samw char *subentry; 12945331Samw char *teptr; 12955331Samw char tok[] = {'\0', '\0'}; 12965331Samw int len; 12975331Samw f_attr_t i; 12985331Samw int numofattrs; 12995331Samw 13005331Samw if ((*aoptsstr != 'S') || (*(aoptsstr + 1) == '\0')) { 13015331Samw return (1); 13025331Samw } 13035331Samw 13045331Samw if ((eptr = strdup(aoptsstr + 1)) == NULL) { 13055331Samw perror("chmod"); 13065331Samw exit(2); 13075331Samw } 13085331Samw entry = eptr; 13095331Samw 13105331Samw /* 13115331Samw * Create a positional character array to determine a single attribute 13125331Samw * operation to be performed, where each index represents the system 13135331Samw * attribute affected, and it's value in the array represents the action 13145331Samw * to be performed, i.e., a value of '+' means to set the attribute, a 13155331Samw * value of '-' means to clear the attribute, and a value of '\0' means 13165331Samw * to leave the attribute untouched. Initially, this positional 13175331Samw * character array is all '\0's, representing a no-op. 13185331Samw */ 13195331Samw if ((numofattrs = attr_count()) < 1) { 13205331Samw errmsg(1, 1, gettext("system attributes not supported\n")); 13215331Samw } 13225331Samw 13235331Samw if ((attractptr = calloc(numofattrs, sizeof (char))) == NULL) { 13245331Samw perror("chmod"); 13255331Samw exit(2); 13265331Samw } 13275331Samw 13285331Samw if ((*sec_args = malloc(sizeof (sec_args_t))) == NULL) { 13295331Samw perror("chmod"); 13305331Samw exit(2); 13315331Samw } 13325331Samw (*sec_args)->sec_type = SEC_ATTR; 13335331Samw (*sec_args)->sec_attrs = NULL; 13345331Samw 13355331Samw /* Parse each attribute operation within the attribute specification. */ 13365331Samw while ((entry != NULL) && (*entry != '\0')) { 13375331Samw action = A_SET_OP; 13385331Samw atype = '\0'; 13395331Samw 13405331Samw /* Get the operator. */ 13415331Samw switch (*entry) { 13425331Samw case A_SET_OP: 13435331Samw case A_INVERSE_OP: 13445331Samw case A_REPLACE_OP: 13455331Samw action = *entry++; 13465331Samw break; 13475331Samw case A_COMPACT_TYPE: 13485331Samw case A_VERBOSE_TYPE: 13495331Samw case A_ALLATTRS_TYPE: 13505331Samw atype = *entry++; 13515331Samw action = A_SET_OP; 13525331Samw break; 13535331Samw default: 13545331Samw break; 13555331Samw } 13565331Samw 13575331Samw /* An attribute type must be specified. */ 13585331Samw if (atype == '\0') { 13595331Samw if ((*entry == A_COMPACT_TYPE) || 13605331Samw (*entry == A_VERBOSE_TYPE) || 13615331Samw (*entry == A_ALLATTRS_TYPE)) { 13625331Samw atype = *entry++; 13635331Samw } else { 13645331Samw return (1); 13655331Samw } 13665331Samw } 13675331Samw 13685331Samw /* Get the attribute specification separator. */ 13695331Samw if (*entry == LEFTBRACE) { 13705331Samw *tok = RIGHTBRACE; 13715331Samw entry++; 13725331Samw } else { 13735331Samw *tok = A_SEP; 13745331Samw } 13755331Samw 13765331Samw /* Get the attribute operation */ 13775331Samw if ((nextentry = strpbrk(entry, tok)) != NULL) { 13785331Samw *nextentry = '\0'; 13795331Samw nextentry++; 13805331Samw } 13815331Samw 13825331Samw /* Check for a no-op */ 13835331Samw if ((*entry == '\0') && (atype != A_ALLATTRS_TYPE) && 13845331Samw (action != A_REPLACE_OP)) { 13855331Samw entry = nextentry; 13865331Samw continue; 13875331Samw } 13885331Samw 13895331Samw /* 13905331Samw * Step through the attribute operation, setting the 13915331Samw * appropriate values for the specified attributes in the 13925331Samw * character array, attractptr. A value of '+' will mean the 13935331Samw * attribute is to be set, and a value of '-' will mean the 13945331Samw * attribute is to be cleared. If the value of an attribute 13955331Samw * remains '\0', then no action is to be taken on that 13965331Samw * attribute. As multiple operations specified are 13975331Samw * accumulated, a single attribute setting operation is 13985331Samw * represented in attractptr. 13995331Samw */ 14005331Samw len = strlen(entry); 14015331Samw if ((*tok == RIGHTBRACE) || (action == A_REPLACE_OP) || 14025331Samw (atype == A_ALLATTRS_TYPE)) { 14035331Samw 14045331Samw if ((action == A_REPLACE_OP) || 14055331Samw (atype == A_ALLATTRS_TYPE)) { 14065331Samw (void) memset(attractptr, '\0', numofattrs); 14075331Samw } 14085331Samw 14095331Samw if (len > 0) { 14105331Samw if ((teptr = strdup(entry)) == NULL) { 14115331Samw perror("chmod"); 14125331Samw exit(2); 14135331Samw } 14145331Samw subentry = teptr; 14155331Samw while (subentry != NULL) { 14165331Samw if ((nextattr = strpbrk(subentry, 14175331Samw A_SEP_TOK)) != NULL) { 14185331Samw *nextattr = '\0'; 14195331Samw nextattr++; 14205331Samw } 14215331Samw if (parse_entry(subentry, action, 14225331Samw atype, len, attractptr) != 0) { 14235331Samw return (1); 14245331Samw } 14255331Samw subentry = nextattr; 14265331Samw } 14275331Samw free(teptr); 14285331Samw } 14295331Samw 14305331Samw /* 14315331Samw * If performing the replace action, record the 14325331Samw * attributes and values for the rest of the 14335331Samw * attributes that have not already been recorded, 14345331Samw * otherwise record the specified action for all 14355331Samw * attributes. Note: set_attr_args() will only record 14365331Samw * the attribute and action if it is a boolean 14375331Samw * read-write attribute so we don't need to worry 14385331Samw * about checking it here. 14395331Samw */ 14405331Samw if ((action == A_REPLACE_OP) || 14415331Samw (atype == A_ALLATTRS_TYPE)) { 14425331Samw for (i = 0; i < numofattrs; i++) { 14435331Samw if (attractptr[i] == A_UNDEF_OP) { 14445331Samw (void) set_attr_args(i, 14455331Samw (action == A_SET_OP) ? 14465331Samw A_SET_OP : A_INVERSE_OP, 14475331Samw attractptr); 14485331Samw } 14495331Samw } 14505331Samw } 14515331Samw 14525331Samw } else { 14535331Samw if (parse_entry(entry, action, atype, len, 14545331Samw attractptr) != 0) { 14555331Samw return (1); 14565331Samw } 14575331Samw } 14585331Samw entry = nextentry; 14595331Samw } 14605331Samw 14615331Samw /* 14625331Samw * Populate an nvlist with attribute name and boolean value pairs 14635331Samw * using the single attribute operation. 14645331Samw */ 14655331Samw (*sec_args)->sec_attrs = set_attrs_nvlist(attractptr, numofattrs); 14665331Samw free(attractptr); 14675331Samw free(eptr); 14685331Samw 14695331Samw return (0); 14705331Samw } 1471