| 1 |
/*- |
| 2 |
* SPDX-License-Identifier: BSD-3-Clause |
| 3 |
* |
| 4 |
* Copyright (c) 1989, 1993, 1994 |
| 5 |
* The Regents of the University of California. All rights reserved. |
| 6 |
* |
| 7 |
* Redistribution and use in source and binary forms, with or without |
| 8 |
* modification, are permitted provided that the following conditions |
| 9 |
* are met: |
| 10 |
* 1. Redistributions of source code must retain the above copyright |
| 11 |
* notice, this list of conditions and the following disclaimer. |
| 12 |
* 2. Redistributions in binary form must reproduce the above copyright |
| 13 |
* notice, this list of conditions and the following disclaimer in the |
| 14 |
* documentation and/or other materials provided with the distribution. |
| 15 |
* 3. Neither the name of the University nor the names of its contributors |
| 16 |
* may be used to endorse or promote products derived from this software |
| 17 |
* without specific prior written permission. |
| 18 |
* |
| 19 |
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| 20 |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 21 |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 22 |
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| 23 |
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 24 |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| 25 |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| 26 |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| 27 |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| 28 |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| 29 |
* SUCH DAMAGE. |
| 30 |
*/ |
| 31 |
|
| 32 |
#if 0 |
| 33 |
#ifndef lint |
| 34 |
static char const copyright[] = |
| 35 |
"@(#) Copyright (c) 1989, 1993, 1994\n\ |
| 36 |
The Regents of the University of California. All rights reserved.\n"; |
| 37 |
#endif /* not lint */ |
| 38 |
|
| 39 |
#ifndef lint |
| 40 |
static char sccsid[] = "@(#)chmod.c 8.8 (Berkeley) 4/1/94"; |
| 41 |
#endif /* not lint */ |
| 42 |
#endif |
| 43 |
#include <sys/cdefs.h> |
| 44 |
__FBSDID("$FreeBSD$"); |
| 45 |
|
| 46 |
#include <sys/param.h> |
| 47 |
#include <sys/stat.h> |
| 48 |
|
| 49 |
#include <err.h> |
| 50 |
#include <errno.h> |
| 51 |
#include <fcntl.h> |
| 52 |
#include <fts.h> |
| 53 |
#include <limits.h> |
| 54 |
#include <signal.h> |
| 55 |
#include <stdio.h> |
| 56 |
#include <stdlib.h> |
| 57 |
#include <string.h> |
| 58 |
#include <unistd.h> |
| 59 |
|
| 60 |
static volatile sig_atomic_t siginfo; |
| 61 |
|
| 62 |
static void usage(void); |
| 63 |
static int may_have_nfs4acl(const FTSENT *ent, int hflag); |
| 64 |
|
| 65 |
static void |
| 66 |
siginfo_handler(int sig __unused) |
| 67 |
{ |
| 68 |
|
| 69 |
siginfo = 1; |
| 70 |
} |
| 71 |
|
| 72 |
int |
| 73 |
main(int argc, char *argv[]) |
| 74 |
{ |
| 75 |
FTS *ftsp; |
| 76 |
FTSENT *p; |
| 77 |
mode_t *set; |
| 78 |
int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval; |
| 79 |
int vflag; |
| 80 |
char *mode; |
| 81 |
mode_t newmode; |
| 82 |
|
| 83 |
set = NULL; |
| 84 |
Hflag = Lflag = Rflag = fflag = hflag = vflag = 0; |
| 85 |
while ((ch = getopt(argc, argv, "HLPRXfghorstuvwx")) != -1) |
| 86 |
switch (ch) { |
| 87 |
case 'H': |
| 88 |
Hflag = 1; |
| 89 |
Lflag = 0; |
| 90 |
break; |
| 91 |
case 'L': |
| 92 |
Lflag = 1; |
| 93 |
Hflag = 0; |
| 94 |
break; |
| 95 |
case 'P': |
| 96 |
Hflag = Lflag = 0; |
| 97 |
break; |
| 98 |
case 'R': |
| 99 |
Rflag = 1; |
| 100 |
break; |
| 101 |
case 'f': |
| 102 |
fflag = 1; |
| 103 |
break; |
| 104 |
case 'h': |
| 105 |
/* |
| 106 |
* In System V the -h option causes chmod to change |
| 107 |
* the mode of the symbolic link. 4.4BSD's symbolic |
| 108 |
* links didn't have modes, so it was an undocumented |
| 109 |
* noop. In FreeBSD 3.0, lchmod(2) is introduced and |
| 110 |
* this option does real work. |
| 111 |
*/ |
| 112 |
hflag = 1; |
| 113 |
break; |
| 114 |
/* |
| 115 |
* XXX |
| 116 |
* "-[rwx]" are valid mode commands. If they are the entire |
| 117 |
* argument, getopt has moved past them, so decrement optind. |
| 118 |
* Regardless, we're done argument processing. |
| 119 |
*/ |
| 120 |
case 'g': case 'o': case 'r': case 's': |
| 121 |
case 't': case 'u': case 'w': case 'X': case 'x': |
| 122 |
if (argv[optind - 1][0] == '-' && |
| 123 |
argv[optind - 1][1] == ch && |
| 124 |
argv[optind - 1][2] == '\0') |
| 125 |
--optind; |
| 126 |
goto done; |
| 127 |
case 'v': |
| 128 |
vflag++; |
| 129 |
break; |
| 130 |
case '?': |
| 131 |
default: |
| 132 |
usage(); |
| 133 |
} |
| 134 |
done: argv += optind; |
| 135 |
argc -= optind; |
| 136 |
|
| 137 |
if (argc < 2) |
| 138 |
usage(); |
| 139 |
|
| 140 |
(void)signal(SIGINFO, siginfo_handler); |
| 141 |
|
| 142 |
if (Rflag) { |
| 143 |
if (hflag) |
| 144 |
errx(1, "the -R and -h options may not be " |
| 145 |
"specified together."); |
| 146 |
if (Lflag) { |
| 147 |
fts_options = FTS_LOGICAL; |
| 148 |
} else { |
| 149 |
fts_options = FTS_PHYSICAL; |
| 150 |
|
| 151 |
if (Hflag) { |
| 152 |
fts_options |= FTS_COMFOLLOW; |
| 153 |
} |
| 154 |
} |
| 155 |
} else if (hflag) { |
| 156 |
fts_options = FTS_PHYSICAL; |
| 157 |
} else { |
| 158 |
fts_options = FTS_LOGICAL; |
| 159 |
} |
| 160 |
|
| 161 |
mode = *argv; |
| 162 |
if ((set = setmode(mode)) == NULL) |
| 163 |
errx(1, "invalid file mode: %s", mode); |
| 164 |
|
| 165 |
if ((ftsp = fts_open(++argv, fts_options, 0)) == NULL) |
| 166 |
err(1, "fts_open"); |
| 167 |
for (rval = 0; (p = fts_read(ftsp)) != NULL;) { |
| 168 |
int atflag; |
| 169 |
|
| 170 |
if ((fts_options & FTS_LOGICAL) || |
| 171 |
((fts_options & FTS_COMFOLLOW) && |
| 172 |
p->fts_level == FTS_ROOTLEVEL)) |
| 173 |
atflag = 0; |
| 174 |
else |
| 175 |
atflag = AT_SYMLINK_NOFOLLOW; |
| 176 |
|
| 177 |
switch (p->fts_info) { |
| 178 |
case FTS_D: |
| 179 |
if (!Rflag) |
| 180 |
fts_set(ftsp, p, FTS_SKIP); |
| 181 |
break; |
| 182 |
case FTS_DNR: /* Warn, chmod. */ |
| 183 |
warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); |
| 184 |
rval = 1; |
| 185 |
break; |
| 186 |
case FTS_DP: /* Already changed at FTS_D. */ |
| 187 |
continue; |
| 188 |
case FTS_ERR: /* Warn, continue. */ |
| 189 |
case FTS_NS: |
| 190 |
warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); |
| 191 |
rval = 1; |
| 192 |
continue; |
| 193 |
default: |
| 194 |
break; |
| 195 |
} |
| 196 |
newmode = getmode(set, p->fts_statp->st_mode); |
| 197 |
/* |
| 198 |
* With NFSv4 ACLs, it is possible that applying a mode |
| 199 |
* identical to the one computed from an ACL will change |
| 200 |
* that ACL. |
| 201 |
*/ |
| 202 |
if (may_have_nfs4acl(p, hflag) == 0 && |
| 203 |
(newmode & ALLPERMS) == (p->fts_statp->st_mode & ALLPERMS)) |
| 204 |
continue; |
| 205 |
if (fchmodat(AT_FDCWD, p->fts_accpath, newmode, atflag) == -1 |
| 206 |
&& !fflag) { |
| 207 |
warn("%s", p->fts_path); |
| 208 |
rval = 1; |
| 209 |
} else if (vflag || siginfo) { |
| 210 |
(void)printf("%s", p->fts_path); |
| 211 |
|
| 212 |
if (vflag > 1 || siginfo) { |
| 213 |
char m1[12], m2[12]; |
| 214 |
|
| 215 |
strmode(p->fts_statp->st_mode, m1); |
| 216 |
strmode((p->fts_statp->st_mode & |
| 217 |
S_IFMT) | newmode, m2); |
| 218 |
(void)printf(": 0%o [%s] -> 0%o [%s]", |
| 219 |
p->fts_statp->st_mode, m1, |
| 220 |
(p->fts_statp->st_mode & S_IFMT) | |
| 221 |
newmode, m2); |
| 222 |
} |
| 223 |
(void)printf("\n"); |
| 224 |
siginfo = 0; |
| 225 |
} |
| 226 |
} |
| 227 |
if (errno) |
| 228 |
err(1, "fts_read"); |
| 229 |
exit(rval); |
| 230 |
} |
| 231 |
|
| 232 |
static void |
| 233 |
usage(void) |
| 234 |
{ |
| 235 |
(void)fprintf(stderr, |
| 236 |
"usage: chmod [-fhv] [-R [-H | -L | -P]] mode file ...\n"); |
| 237 |
exit(1); |
| 238 |
} |
| 239 |
|
| 240 |
static int |
| 241 |
may_have_nfs4acl(const FTSENT *ent, int hflag) |
| 242 |
{ |
| 243 |
int ret; |
| 244 |
static dev_t previous_dev = NODEV; |
| 245 |
static int supports_acls = -1; |
| 246 |
|
| 247 |
if (previous_dev != ent->fts_statp->st_dev) { |
| 248 |
previous_dev = ent->fts_statp->st_dev; |
| 249 |
supports_acls = 0; |
| 250 |
|
| 251 |
if (hflag) |
| 252 |
ret = lpathconf(ent->fts_accpath, _PC_ACL_NFS4); |
| 253 |
else |
| 254 |
ret = pathconf(ent->fts_accpath, _PC_ACL_NFS4); |
| 255 |
if (ret > 0) |
| 256 |
supports_acls = 1; |
| 257 |
else if (ret < 0 && errno != EINVAL) |
| 258 |
warn("%s", ent->fts_path); |
| 259 |
} |
| 260 |
|
| 261 |
return (supports_acls); |
| 262 |
} |