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