1/* modechange.c -- file mode manipulation 2 3 Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003, 2004, 2005, 4 2006 Free Software Foundation, Inc. 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 18 19/* Written by David MacKenzie <djm@ai.mit.edu> */ 20 21/* The ASCII mode string is compiled into an array of `struct 22 modechange', which can then be applied to each file to be changed. 23 We do this instead of re-parsing the ASCII string for each file 24 because the compiled form requires less computation to use; when 25 changing the mode of many files, this probably results in a 26 performance gain. */ 27 28#include <config.h> 29 30#include "modechange.h" 31#include <sys/stat.h> 32#include "stat-macros.h" 33#include "xalloc.h" 34#include <stdlib.h> 35 36/* The traditional octal values corresponding to each mode bit. */ 37#define SUID 04000 38#define SGID 02000 39#define SVTX 01000 40#define RUSR 00400 41#define WUSR 00200 42#define XUSR 00100 43#define RGRP 00040 44#define WGRP 00020 45#define XGRP 00010 46#define ROTH 00004 47#define WOTH 00002 48#define XOTH 00001 49#define ALLM 07777 /* all octal mode bits */ 50 51/* Convert OCTAL, which uses one of the traditional octal values, to 52 an internal mode_t value. */ 53static mode_t 54octal_to_mode (unsigned int octal) 55{ 56 /* Help the compiler optimize the usual case where mode_t uses 57 the traditional octal representation. */ 58 return ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX 59 && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR 60 && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP 61 && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH) 62 ? octal 63 : (mode_t) ((octal & SUID ? S_ISUID : 0) 64 | (octal & SGID ? S_ISGID : 0) 65 | (octal & SVTX ? S_ISVTX : 0) 66 | (octal & RUSR ? S_IRUSR : 0) 67 | (octal & WUSR ? S_IWUSR : 0) 68 | (octal & XUSR ? S_IXUSR : 0) 69 | (octal & RGRP ? S_IRGRP : 0) 70 | (octal & WGRP ? S_IWGRP : 0) 71 | (octal & XGRP ? S_IXGRP : 0) 72 | (octal & ROTH ? S_IROTH : 0) 73 | (octal & WOTH ? S_IWOTH : 0) 74 | (octal & XOTH ? S_IXOTH : 0))); 75} 76 77/* Special operations flags. */ 78enum 79 { 80 /* For the sentinel at the end of the mode changes array. */ 81 MODE_DONE, 82 83 /* The typical case. */ 84 MODE_ORDINARY_CHANGE, 85 86 /* In addition to the typical case, affect the execute bits if at 87 least one execute bit is set already, or if the file is a 88 directory. */ 89 MODE_X_IF_ANY_X, 90 91 /* Instead of the typical case, copy some existing permissions for 92 u, g, or o onto the other two. Which of u, g, or o is copied 93 is determined by which bits are set in the `value' field. */ 94 MODE_COPY_EXISTING 95 }; 96 97/* Description of a mode change. */ 98struct mode_change 99{ 100 char op; /* One of "=+-". */ 101 char flag; /* Special operations flag. */ 102 mode_t affected; /* Set for u, g, o, or a. */ 103 mode_t value; /* Bits to add/remove. */ 104 mode_t mentioned; /* Bits explicitly mentioned. */ 105}; 106 107/* Return a mode_change array with the specified `=ddd'-style 108 mode change operation, where NEW_MODE is `ddd' and MENTIONED 109 contains the bits explicitly mentioned in the mode are MENTIONED. */ 110 111static struct mode_change * 112make_node_op_equals (mode_t new_mode, mode_t mentioned) 113{ 114 struct mode_change *p = xmalloc (2 * sizeof *p); 115 p->op = '='; 116 p->flag = MODE_ORDINARY_CHANGE; 117 p->affected = CHMOD_MODE_BITS; 118 p->value = new_mode; 119 p->mentioned = mentioned; 120 p[1].flag = MODE_DONE; 121 return p; 122} 123 124/* Return a pointer to an array of file mode change operations created from 125 MODE_STRING, an ASCII string that contains either an octal number 126 specifying an absolute mode, or symbolic mode change operations with 127 the form: 128 [ugoa...][[+-=][rwxXstugo...]...][,...] 129 130 Return NULL if `mode_string' does not contain a valid 131 representation of file mode change operations. */ 132 133struct mode_change * 134mode_compile (char const *mode_string) 135{ 136 /* The array of mode-change directives to be returned. */ 137 struct mode_change *mc; 138 size_t used = 0; 139 140 if ('0' <= *mode_string && *mode_string < '8') 141 { 142 unsigned int octal_mode = 0; 143 mode_t mode; 144 mode_t mentioned; 145 146 do 147 { 148 octal_mode = 8 * octal_mode + *mode_string++ - '0'; 149 if (ALLM < octal_mode) 150 return NULL; 151 } 152 while ('0' <= *mode_string && *mode_string < '8'); 153 154 if (*mode_string) 155 return NULL; 156 157 mode = octal_to_mode (octal_mode); 158 mentioned = (mode & (S_ISUID | S_ISGID)) | S_ISVTX | S_IRWXUGO; 159 return make_node_op_equals (mode, mentioned); 160 } 161 162 /* Allocate enough space to hold the result. */ 163 { 164 size_t needed = 1; 165 char const *p; 166 for (p = mode_string; *p; p++) 167 needed += (*p == '=' || *p == '+' || *p == '-'); 168 mc = xnmalloc (needed, sizeof *mc); 169 } 170 171 /* One loop iteration for each `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'. */ 172 for (;; mode_string++) 173 { 174 /* Which bits in the mode are operated on. */ 175 mode_t affected = 0; 176 177 /* Turn on all the bits in `affected' for each group given. */ 178 for (;; mode_string++) 179 switch (*mode_string) 180 { 181 default: 182 goto invalid; 183 case 'u': 184 affected |= S_ISUID | S_IRWXU; 185 break; 186 case 'g': 187 affected |= S_ISGID | S_IRWXG; 188 break; 189 case 'o': 190 affected |= S_ISVTX | S_IRWXO; 191 break; 192 case 'a': 193 affected |= CHMOD_MODE_BITS; 194 break; 195 case '=': case '+': case '-': 196 goto no_more_affected; 197 } 198 no_more_affected:; 199 200 do 201 { 202 char op = *mode_string++; 203 mode_t value; 204 char flag = MODE_COPY_EXISTING; 205 struct mode_change *change; 206 207 switch (*mode_string++) 208 { 209 case 'u': 210 /* Set the affected bits to the value of the `u' bits 211 on the same file. */ 212 value = S_IRWXU; 213 break; 214 case 'g': 215 /* Set the affected bits to the value of the `g' bits 216 on the same file. */ 217 value = S_IRWXG; 218 break; 219 case 'o': 220 /* Set the affected bits to the value of the `o' bits 221 on the same file. */ 222 value = S_IRWXO; 223 break; 224 225 default: 226 value = 0; 227 flag = MODE_ORDINARY_CHANGE; 228 229 for (mode_string--;; mode_string++) 230 switch (*mode_string) 231 { 232 case 'r': 233 value |= S_IRUSR | S_IRGRP | S_IROTH; 234 break; 235 case 'w': 236 value |= S_IWUSR | S_IWGRP | S_IWOTH; 237 break; 238 case 'x': 239 value |= S_IXUSR | S_IXGRP | S_IXOTH; 240 break; 241 case 'X': 242 flag = MODE_X_IF_ANY_X; 243 break; 244 case 's': 245 /* Set the setuid/gid bits if `u' or `g' is selected. */ 246 value |= S_ISUID | S_ISGID; 247 break; 248 case 't': 249 /* Set the "save text image" bit if `o' is selected. */ 250 value |= S_ISVTX; 251 break; 252 default: 253 goto no_more_values; 254 } 255 no_more_values:; 256 } 257 258 change = &mc[used++]; 259 change->op = op; 260 change->flag = flag; 261 change->affected = affected; 262 change->value = value; 263 change->mentioned = (affected ? affected & value : value); 264 } 265 while (*mode_string == '=' || *mode_string == '+' 266 || *mode_string == '-'); 267 268 if (*mode_string != ',') 269 break; 270 } 271 272 if (*mode_string == 0) 273 { 274 mc[used].flag = MODE_DONE; 275 return mc; 276 } 277 278invalid: 279 free (mc); 280 return NULL; 281} 282 283/* Return a file mode change operation that sets permissions to match those 284 of REF_FILE. Return NULL (setting errno) if REF_FILE can't be accessed. */ 285 286struct mode_change * 287mode_create_from_ref (const char *ref_file) 288{ 289 struct stat ref_stats; 290 291 if (stat (ref_file, &ref_stats) != 0) 292 return NULL; 293 return make_node_op_equals (ref_stats.st_mode, CHMOD_MODE_BITS); 294} 295 296/* Return the file mode bits of OLDMODE (which is the mode of a 297 directory if DIR), assuming the umask is UMASK_VALUE, adjusted as 298 indicated by the list of change operations CHANGES. If DIR, the 299 type 'X' change affects the returned value even if no execute bits 300 were set in OLDMODE, and set user and group ID bits are preserved 301 unless CHANGES mentioned them. If PMODE_BITS is not null, store into 302 *PMODE_BITS a mask denoting file mode bits that are affected by 303 CHANGES. 304 305 The returned value and *PMODE_BITS contain only file mode bits. 306 For example, they have the S_IFMT bits cleared on a standard 307 Unix-like host. */ 308 309mode_t 310mode_adjust (mode_t oldmode, bool dir, mode_t umask_value, 311 struct mode_change const *changes, mode_t *pmode_bits) 312{ 313 /* The adjusted mode. */ 314 mode_t newmode = oldmode & CHMOD_MODE_BITS; 315 316 /* File mode bits that CHANGES cares about. */ 317 mode_t mode_bits = 0; 318 319 for (; changes->flag != MODE_DONE; changes++) 320 { 321 mode_t affected = changes->affected; 322 mode_t omit_change = 323 (dir ? S_ISUID | S_ISGID : 0) & ~ changes->mentioned; 324 mode_t value = changes->value; 325 326 switch (changes->flag) 327 { 328 case MODE_ORDINARY_CHANGE: 329 break; 330 331 case MODE_COPY_EXISTING: 332 /* Isolate in `value' the bits in `newmode' to copy. */ 333 value &= newmode; 334 335 /* Copy the isolated bits to the other two parts. */ 336 value |= ((value & (S_IRUSR | S_IRGRP | S_IROTH) 337 ? S_IRUSR | S_IRGRP | S_IROTH : 0) 338 | (value & (S_IWUSR | S_IWGRP | S_IWOTH) 339 ? S_IWUSR | S_IWGRP | S_IWOTH : 0) 340 | (value & (S_IXUSR | S_IXGRP | S_IXOTH) 341 ? S_IXUSR | S_IXGRP | S_IXOTH : 0)); 342 break; 343 344 case MODE_X_IF_ANY_X: 345 /* Affect the execute bits if execute bits are already set 346 or if the file is a directory. */ 347 if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) | dir) 348 value |= S_IXUSR | S_IXGRP | S_IXOTH; 349 break; 350 } 351 352 /* If WHO was specified, limit the change to the affected bits. 353 Otherwise, apply the umask. Either way, omit changes as 354 requested. */ 355 value &= (affected ? affected : ~umask_value) & ~ omit_change; 356 357 switch (changes->op) 358 { 359 case '=': 360 /* If WHO was specified, preserve the previous values of 361 bits that are not affected by this change operation. 362 Otherwise, clear all the bits. */ 363 { 364 mode_t preserved = (affected ? ~affected : 0) | omit_change; 365 mode_bits |= CHMOD_MODE_BITS & ~preserved; 366 newmode = (newmode & preserved) | value; 367 break; 368 } 369 370 case '+': 371 mode_bits |= value; 372 newmode |= value; 373 break; 374 375 case '-': 376 mode_bits |= value; 377 newmode &= ~value; 378 break; 379 } 380 } 381 382 if (pmode_bits) 383 *pmode_bits = mode_bits; 384 return newmode; 385} 386