X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fmodechange.c;h=8ac18799f8091534900d3ae25721ca8e42f2dba8;hb=7ef6c64e210ac0979d7e8ac69bc5b5208c2405ab;hp=a2697f67ff0243d0ee1a2335caa5aa10bef8334d;hpb=267a39bafd249d7eb9c37df06dc6defcf41cb343;p=gnulib.git diff --git a/lib/modechange.c b/lib/modechange.c index a2697f67f..8ac18799f 100644 --- a/lib/modechange.c +++ b/lib/modechange.c @@ -1,12 +1,12 @@ /* modechange.c -- file mode manipulation - Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003, 2004, 2005 - Free Software Foundation, Inc. + Copyright (C) 1989-1990, 1997-1999, 2001, 2003-2006, 2009-2014 Free Software + Foundation, Inc. - This program is free software; you can redistribute it and/or modify + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2, or (at your option) - any later version. + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -14,21 +14,18 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + along with this program. If not, see . */ /* Written by David MacKenzie */ -/* The ASCII mode string is compiled into an array of `struct +/* The ASCII mode string is compiled into an array of 'struct modechange', which can then be applied to each file to be changed. We do this instead of re-parsing the ASCII string for each file because the compiled form requires less computation to use; when changing the mode of many files, this probably results in a performance gain. */ -#if HAVE_CONFIG_H -# include -#endif +#include #include "modechange.h" #include @@ -51,6 +48,32 @@ #define XOTH 00001 #define ALLM 07777 /* all octal mode bits */ +/* Convert OCTAL, which uses one of the traditional octal values, to + an internal mode_t value. */ +static mode_t +octal_to_mode (unsigned int octal) +{ + /* Help the compiler optimize the usual case where mode_t uses + the traditional octal representation. */ + return ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX + && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR + && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP + && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH) + ? octal + : (mode_t) ((octal & SUID ? S_ISUID : 0) + | (octal & SGID ? S_ISGID : 0) + | (octal & SVTX ? S_ISVTX : 0) + | (octal & RUSR ? S_IRUSR : 0) + | (octal & WUSR ? S_IWUSR : 0) + | (octal & XUSR ? S_IXUSR : 0) + | (octal & RGRP ? S_IRGRP : 0) + | (octal & WGRP ? S_IWGRP : 0) + | (octal & XGRP ? S_IXGRP : 0) + | (octal & ROTH ? S_IROTH : 0) + | (octal & WOTH ? S_IWOTH : 0) + | (octal & XOTH ? S_IXOTH : 0))); +} + /* Special operations flags. */ enum { @@ -67,30 +90,33 @@ enum /* Instead of the typical case, copy some existing permissions for u, g, or o onto the other two. Which of u, g, or o is copied - is determined by which bits are set in the `value' field. */ + is determined by which bits are set in the 'value' field. */ MODE_COPY_EXISTING }; /* Description of a mode change. */ struct mode_change { - char op; /* One of "=+-". */ - char flag; /* Special operations flag. */ - mode_t affected; /* Set for u, g, o, or a. */ - mode_t value; /* Bits to add/remove. */ + char op; /* One of "=+-". */ + char flag; /* Special operations flag. */ + mode_t affected; /* Set for u, g, o, or a. */ + mode_t value; /* Bits to add/remove. */ + mode_t mentioned; /* Bits explicitly mentioned. */ }; -/* Return a mode_change array with the specified `=ddd'-style - mode change operation, where NEW_MODE is `ddd'. */ +/* Return a mode_change array with the specified "=ddd"-style + mode change operation, where NEW_MODE is "ddd" and MENTIONED + contains the bits explicitly mentioned in the mode are MENTIONED. */ static struct mode_change * -make_node_op_equals (mode_t new_mode) +make_node_op_equals (mode_t new_mode, mode_t mentioned) { struct mode_change *p = xmalloc (2 * sizeof *p); p->op = '='; p->flag = MODE_ORDINARY_CHANGE; p->affected = CHMOD_MODE_BITS; p->value = new_mode; + p->mentioned = mentioned; p[1].flag = MODE_DONE; return p; } @@ -101,7 +127,7 @@ make_node_op_equals (mode_t new_mode) the form: [ugoa...][[+-=][rwxXstugo...]...][,...] - Return NULL if `mode_string' does not contain a valid + Return NULL if 'mode_string' does not contain a valid representation of file mode change operations. */ struct mode_change * @@ -110,153 +136,169 @@ mode_compile (char const *mode_string) /* The array of mode-change directives to be returned. */ struct mode_change *mc; size_t used = 0; + char const *p; if ('0' <= *mode_string && *mode_string < '8') { + unsigned int octal_mode = 0; mode_t mode; - unsigned int octal_value = 0; + mode_t mentioned; + p = mode_string; do - { - octal_value = 8 * octal_value + *mode_string++ - '0'; - if (ALLM < octal_value) - return NULL; - } - while ('0' <= *mode_string && *mode_string < '8'); - - /* Help the compiler optimize the usual case where mode_t uses - the traditional octal representation. */ - mode = ((S_ISUID == SUID && S_ISGID == SGID && S_ISVTX == SVTX - && S_IRUSR == RUSR && S_IWUSR == WUSR && S_IXUSR == XUSR - && S_IRGRP == RGRP && S_IWGRP == WGRP && S_IXGRP == XGRP - && S_IROTH == ROTH && S_IWOTH == WOTH && S_IXOTH == XOTH) - ? octal_value - : (mode_t) ((octal_value & SUID ? S_ISUID : 0) - | (octal_value & SGID ? S_ISGID : 0) - | (octal_value & SVTX ? S_ISVTX : 0) - | (octal_value & RUSR ? S_IRUSR : 0) - | (octal_value & WUSR ? S_IWUSR : 0) - | (octal_value & XUSR ? S_IXUSR : 0) - | (octal_value & RGRP ? S_IRGRP : 0) - | (octal_value & WGRP ? S_IWGRP : 0) - | (octal_value & XGRP ? S_IXGRP : 0) - | (octal_value & ROTH ? S_IROTH : 0) - | (octal_value & WOTH ? S_IWOTH : 0) - | (octal_value & XOTH ? S_IXOTH : 0))); - - return make_node_op_equals (mode); + { + octal_mode = 8 * octal_mode + *p++ - '0'; + if (ALLM < octal_mode) + return NULL; + } + while ('0' <= *p && *p < '8'); + + if (*p) + return NULL; + + mode = octal_to_mode (octal_mode); + mentioned = (p - mode_string < 5 + ? (mode & (S_ISUID | S_ISGID)) | S_ISVTX | S_IRWXUGO + : CHMOD_MODE_BITS); + return make_node_op_equals (mode, mentioned); } /* Allocate enough space to hold the result. */ { size_t needed = 1; - char const *p; for (p = mode_string; *p; p++) needed += (*p == '=' || *p == '+' || *p == '-'); mc = xnmalloc (needed, sizeof *mc); } - /* One loop iteration for each `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'. */ - for (;; mode_string++) + /* One loop iteration for each + '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+'. */ + for (p = mode_string; ; p++) { /* Which bits in the mode are operated on. */ mode_t affected = 0; - /* Turn on all the bits in `affected' for each group given. */ - for (;; mode_string++) - switch (*mode_string) - { - default: - goto invalid; - case 'u': - affected |= S_ISUID | S_IRWXU; - break; - case 'g': - affected |= S_ISGID | S_IRWXG; - break; - case 'o': - affected |= S_ISVTX | S_IRWXO; - break; - case 'a': - affected |= CHMOD_MODE_BITS; - break; - case '=': case '+': case '-': - goto no_more_affected; - } + /* Turn on all the bits in 'affected' for each group given. */ + for (;; p++) + switch (*p) + { + default: + goto invalid; + case 'u': + affected |= S_ISUID | S_IRWXU; + break; + case 'g': + affected |= S_ISGID | S_IRWXG; + break; + case 'o': + affected |= S_ISVTX | S_IRWXO; + break; + case 'a': + affected |= CHMOD_MODE_BITS; + break; + case '=': case '+': case '-': + goto no_more_affected; + } no_more_affected:; do - { - char op = *mode_string++; - mode_t value; - char flag = MODE_COPY_EXISTING; - struct mode_change *change; - - switch (*mode_string++) - { - case 'u': - /* Set the affected bits to the value of the `u' bits - on the same file. */ - value = S_IRWXU; - break; - case 'g': - /* Set the affected bits to the value of the `g' bits - on the same file. */ - value = S_IRWXG; - break; - case 'o': - /* Set the affected bits to the value of the `o' bits - on the same file. */ - value = S_IRWXO; - break; - - default: - value = 0; - flag = MODE_ORDINARY_CHANGE; - - for (mode_string--;; mode_string++) - switch (*mode_string) - { - case 'r': - value |= S_IRUSR | S_IRGRP | S_IROTH; - break; - case 'w': - value |= S_IWUSR | S_IWGRP | S_IWOTH; - break; - case 'x': - value |= S_IXUSR | S_IXGRP | S_IXOTH; - break; - case 'X': - flag = MODE_X_IF_ANY_X; - break; - case 's': - /* Set the setuid/gid bits if `u' or `g' is selected. */ - value |= S_ISUID | S_ISGID; - break; - case 't': - /* Set the "save text image" bit if `o' is selected. */ - value |= S_ISVTX; - break; - default: - goto no_more_values; - } - no_more_values:; - } - - change = &mc[used++]; - change->op = op; - change->flag = flag; - change->affected = affected; - change->value = value; - } - while (*mode_string == '=' || *mode_string == '+' - || *mode_string == '-'); - - if (*mode_string != ',') - break; + { + char op = *p++; + mode_t value; + mode_t mentioned = 0; + char flag = MODE_COPY_EXISTING; + struct mode_change *change; + + switch (*p) + { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + { + unsigned int octal_mode = 0; + + do + { + octal_mode = 8 * octal_mode + *p++ - '0'; + if (ALLM < octal_mode) + return NULL; + } + while ('0' <= *p && *p < '8'); + + if (affected || (*p && *p != ',')) + return NULL; + affected = mentioned = CHMOD_MODE_BITS; + value = octal_to_mode (octal_mode); + flag = MODE_ORDINARY_CHANGE; + break; + } + + case 'u': + /* Set the affected bits to the value of the "u" bits + on the same file. */ + value = S_IRWXU; + p++; + break; + case 'g': + /* Set the affected bits to the value of the "g" bits + on the same file. */ + value = S_IRWXG; + p++; + break; + case 'o': + /* Set the affected bits to the value of the "o" bits + on the same file. */ + value = S_IRWXO; + p++; + break; + + default: + value = 0; + flag = MODE_ORDINARY_CHANGE; + + for (;; p++) + switch (*p) + { + case 'r': + value |= S_IRUSR | S_IRGRP | S_IROTH; + break; + case 'w': + value |= S_IWUSR | S_IWGRP | S_IWOTH; + break; + case 'x': + value |= S_IXUSR | S_IXGRP | S_IXOTH; + break; + case 'X': + flag = MODE_X_IF_ANY_X; + break; + case 's': + /* Set the setuid/gid bits if 'u' or 'g' is selected. */ + value |= S_ISUID | S_ISGID; + break; + case 't': + /* Set the "save text image" bit if 'o' is selected. */ + value |= S_ISVTX; + break; + default: + goto no_more_values; + } + no_more_values:; + } + + change = &mc[used++]; + change->op = op; + change->flag = flag; + change->affected = affected; + change->value = value; + change->mentioned = + (mentioned ? mentioned : affected ? affected & value : value); + } + while (*p == '=' || *p == '+' || *p == '-'); + + if (*p != ',') + break; } - if (*mode_string == 0) + if (*p == 0) { mc[used].flag = MODE_DONE; return mc; @@ -277,74 +319,96 @@ mode_create_from_ref (const char *ref_file) if (stat (ref_file, &ref_stats) != 0) return NULL; - return make_node_op_equals (ref_stats.st_mode); + return make_node_op_equals (ref_stats.st_mode, CHMOD_MODE_BITS); } -/* Return file mode OLDMODE, adjusted as indicated by the list of change - operations CHANGES, which are interpreted assuming the umask is - UMASK_VALUE. If OLDMODE is a directory, the type `X' - change affects it even if no execute bits were set in OLDMODE. - The returned value has the S_IFMT bits cleared. */ +/* Return the file mode bits of OLDMODE (which is the mode of a + directory if DIR), assuming the umask is UMASK_VALUE, adjusted as + indicated by the list of change operations CHANGES. If DIR, the + type 'X' change affects the returned value even if no execute bits + were set in OLDMODE, and set user and group ID bits are preserved + unless CHANGES mentioned them. If PMODE_BITS is not null, store into + *PMODE_BITS a mask denoting file mode bits that are affected by + CHANGES. + + The returned value and *PMODE_BITS contain only file mode bits. + For example, they have the S_IFMT bits cleared on a standard + Unix-like host. */ mode_t -mode_adjust (mode_t oldmode, struct mode_change const *changes, - mode_t umask_value) +mode_adjust (mode_t oldmode, bool dir, mode_t umask_value, + struct mode_change const *changes, mode_t *pmode_bits) { /* The adjusted mode. */ mode_t newmode = oldmode & CHMOD_MODE_BITS; + /* File mode bits that CHANGES cares about. */ + mode_t mode_bits = 0; + for (; changes->flag != MODE_DONE; changes++) { mode_t affected = changes->affected; + mode_t omit_change = + (dir ? S_ISUID | S_ISGID : 0) & ~ changes->mentioned; mode_t value = changes->value; switch (changes->flag) - { - case MODE_ORDINARY_CHANGE: - break; - - case MODE_COPY_EXISTING: - /* Isolate in `value' the bits in `newmode' to copy. */ - value &= newmode; - - /* Copy the isolated bits to the other two parts. */ - value |= ((value & (S_IRUSR | S_IRGRP | S_IROTH) - ? S_IRUSR | S_IRGRP | S_IROTH : 0) - | (value & (S_IWUSR | S_IWGRP | S_IWOTH) - ? S_IWUSR | S_IWGRP | S_IWOTH : 0) - | (value & (S_IXUSR | S_IXGRP | S_IXOTH) - ? S_IXUSR | S_IXGRP | S_IXOTH : 0)); - break; - - case MODE_X_IF_ANY_X: - /* Affect the execute bits if execute bits are already set - or if the file is a directory. */ - if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) || S_ISDIR (oldmode)) - value |= S_IXUSR | S_IXGRP | S_IXOTH; - break; - } + { + case MODE_ORDINARY_CHANGE: + break; + + case MODE_COPY_EXISTING: + /* Isolate in 'value' the bits in 'newmode' to copy. */ + value &= newmode; + + /* Copy the isolated bits to the other two parts. */ + value |= ((value & (S_IRUSR | S_IRGRP | S_IROTH) + ? S_IRUSR | S_IRGRP | S_IROTH : 0) + | (value & (S_IWUSR | S_IWGRP | S_IWOTH) + ? S_IWUSR | S_IWGRP | S_IWOTH : 0) + | (value & (S_IXUSR | S_IXGRP | S_IXOTH) + ? S_IXUSR | S_IXGRP | S_IXOTH : 0)); + break; + + case MODE_X_IF_ANY_X: + /* Affect the execute bits if execute bits are already set + or if the file is a directory. */ + if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) | dir) + value |= S_IXUSR | S_IXGRP | S_IXOTH; + break; + } /* If WHO was specified, limit the change to the affected bits. - Otherwise, apply the umask. */ - value &= (affected ? affected : ~umask_value); + Otherwise, apply the umask. Either way, omit changes as + requested. */ + value &= (affected ? affected : ~umask_value) & ~ omit_change; switch (changes->op) - { - case '=': - /* If WHO was specified, preserve the previous values of - bits that are not affected by this change operation. - Otherwise, clear all the bits. */ - newmode = (affected ? newmode & ~affected : 0); - /* Fall through. */ - case '+': - newmode |= value; - break; - - case '-': - newmode &= ~value; - break; - } + { + case '=': + /* If WHO was specified, preserve the previous values of + bits that are not affected by this change operation. + Otherwise, clear all the bits. */ + { + mode_t preserved = (affected ? ~affected : 0) | omit_change; + mode_bits |= CHMOD_MODE_BITS & ~preserved; + newmode = (newmode & preserved) | value; + break; + } + + case '+': + mode_bits |= value; + newmode |= value; + break; + + case '-': + mode_bits |= value; + newmode &= ~value; + break; + } } + if (pmode_bits) + *pmode_bits = mode_bits; return newmode; }