diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/modechange.c | 474 |
1 files changed, 175 insertions, 299 deletions
diff --git a/lib/modechange.c b/lib/modechange.c index 991235b09..8880ec09a 100644 --- a/lib/modechange.c +++ b/lib/modechange.c @@ -1,7 +1,7 @@ /* modechange.c -- file mode manipulation - Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003, 2004 Free - Software Foundation, Inc. + Copyright (C) 1989, 1990, 1997, 1998, 1999, 2001, 2003, 2004, 2005 + Free Software Foundation, Inc. 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 @@ -24,7 +24,7 @@ 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. */ + performance gain. */ #if HAVE_CONFIG_H # include <config.h> @@ -32,19 +32,13 @@ #include "modechange.h" #include <sys/stat.h> +#include "stat-macros.h" +#include "xalloc.h" #include "xstrtol.h" #include <stdbool.h> #include <stddef.h> #include <stdlib.h> -#if STAT_MACROS_BROKEN -# undef S_ISDIR -#endif - -#if !defined(S_ISDIR) && defined(S_IFDIR) -# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) -#endif - /* The traditional octal values corresponding to each mode bit. */ #define SUID 04000 #define SGID 02000 @@ -60,127 +54,54 @@ #define XOTH 00001 #define ALLM 07777 /* all octal mode bits */ -#ifndef S_ISUID -# define S_ISUID SUID -#endif -#ifndef S_ISGID -# define S_ISGID SGID -#endif -#ifndef S_ISVTX -# define S_ISVTX SVTX -#endif -#ifndef S_IRUSR -# define S_IRUSR RUSR -#endif -#ifndef S_IWUSR -# define S_IWUSR WUSR -#endif -#ifndef S_IXUSR -# define S_IXUSR XUSR -#endif -#ifndef S_IRGRP -# define S_IRGRP RGRP -#endif -#ifndef S_IWGRP -# define S_IWGRP WGRP -#endif -#ifndef S_IXGRP -# define S_IXGRP XGRP -#endif -#ifndef S_IROTH -# define S_IROTH ROTH -#endif -#ifndef S_IWOTH -# define S_IWOTH WOTH -#endif -#ifndef S_IXOTH -# define S_IXOTH XOTH -#endif -#ifndef S_IRWXU -# define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) -#endif -#ifndef S_IRWXG -# define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) -#endif -#ifndef S_IRWXO -# define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) -#endif - -/* All the mode bits that can be affected by chmod. */ -#define CHMOD_MODE_BITS \ - (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) - -/* Return newly allocated memory to hold one element of type TYPE. */ -#define talloc(type) ((type *) malloc (sizeof (type))) - -/* Create a mode_change entry with the specified `=ddd'-style - mode change operation, where NEW_MODE is `ddd'. Return the - new entry, or NULL upon failure. */ - -static struct mode_change * -make_node_op_equals (mode_t new_mode) +/* Special operations flags. */ +enum + { + /* The typical case. */ + MODE_ORDINARY_CHANGE, + + /* In addition to the typical case, affect the execute bits if at + least one execute bit is set already, or if the file is a + directory. */ + MODE_X_IF_ANY_X, + + /* 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. */ + MODE_COPY_EXISTING + }; + +/* Description of a mode change. */ +struct mode_change { - struct mode_change *p; - p = talloc (struct mode_change); - if (p == NULL) - return p; - p->next = NULL; - p->op = '='; - p->flags = 0; - p->value = new_mode; - p->affected = CHMOD_MODE_BITS; /* Affect all permissions. */ - return p; -} - -/* Append entry E to the end of the link list with the specified - HEAD and TAIL. */ - -static void -mode_append_entry (struct mode_change **head, - struct mode_change **tail, - struct mode_change *e) -{ - if (*head == NULL) - *head = *tail = e; - else - { - (*tail)->next = e; - *tail = e; - } -} + 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. */ + struct mode_change *next; /* Link to next change in list. */ +}; /* Return a linked list of file mode change operations created from MODE_STRING, an ASCII string that contains either an octal number specifying an absolute mode, or symbolic mode change operations with the form: [ugoa...][[+-=][rwxXstugo...]...][,...] - MASKED_OPS is a bitmask indicating which symbolic mode operators (=+-) - should not affect bits set in the umask when no users are given. - Operators not selected in MASKED_OPS ignore the umask. - Return MODE_INVALID if `mode_string' does not contain a valid - representation of file mode change operations; - return MODE_MEMORY_EXHAUSTED if there is insufficient memory. */ + Return NULL if `mode_string' does not contain a valid + representation of file mode change operations. */ struct mode_change * -mode_compile (const char *mode_string, unsigned int masked_ops) +mode_compile (char const *mode_string) { - struct mode_change *head; /* First element of the linked list. */ - struct mode_change *tail; /* An element of the linked list. */ + struct mode_change *head; /* First element of the linked list. */ + struct mode_change **tail = &head; /* Pointer to list end. */ unsigned long octal_value; /* The mode value, if octal. */ - mode_t umask_value; /* The umask value (surprise). */ - - head = NULL; -#ifdef lint - tail = NULL; -#endif if (xstrtoul (mode_string, NULL, 8, &octal_value, "") == LONGINT_OK) { - struct mode_change *p; mode_t mode; if (octal_value != (octal_value & ALLM)) - return MODE_INVALID; + return NULL; /* Help the compiler optimize the usual case where mode_t uses the traditional octal representation. */ @@ -202,166 +123,132 @@ mode_compile (const char *mode_string, unsigned int masked_ops) | (octal_value & WOTH ? S_IWOTH : 0) | (octal_value & XOTH ? S_IXOTH : 0))); - p = make_node_op_equals (mode); - if (p == NULL) - return MODE_MEMORY_EXHAUSTED; - mode_append_entry (&head, &tail, p); + head = xmalloc (sizeof *head); + head->op = '='; + head->flag = MODE_ORDINARY_CHANGE; + head->affected = CHMOD_MODE_BITS; + head->value = mode; + head->next = NULL; return head; } - umask_value = umask (0); - umask (umask_value); /* Restore the old value. */ - - /* One loop iteration for each "ugoa...=+-rwxXstugo...[=+-rwxXstugo...]". */ + /* One loop iteration for each `[ugoa]*([-+=]([rwxXst]*|[ugo]))+'. */ for (;; mode_string++) { - /* Which bits in the mode are operated on. */ - mode_t affected_bits = 0; - /* `affected_bits' modified by umask. */ - mode_t affected_masked; - /* Operators to actually use umask on. */ - unsigned int ops_to_mask = 0; + /* Which bits in the mode are operated on. */ + mode_t affected = 0; - bool who_specified_p; - - /* Turn on all the bits in `affected_bits' for each group given. */ + /* Turn on all the bits in `affected' for each group given. */ for (;; mode_string++) switch (*mode_string) { case 'u': - affected_bits |= S_ISUID | S_IRWXU; + affected |= S_ISUID | S_IRWXU; break; case 'g': - affected_bits |= S_ISGID | S_IRWXG; + affected |= S_ISGID | S_IRWXG; break; case 'o': - affected_bits |= S_ISVTX | S_IRWXO; + affected |= S_ISVTX | S_IRWXO; break; case 'a': - affected_bits |= CHMOD_MODE_BITS; + affected |= CHMOD_MODE_BITS; break; - default: + case '=': case '+': case '-': goto no_more_affected; + default: + goto invalid; } + no_more_affected:; - no_more_affected: - /* If none specified, affect all bits, except perhaps those - set in the umask. */ - if (affected_bits) - who_specified_p = true; - else + do { - who_specified_p = false; - affected_bits = CHMOD_MODE_BITS; - ops_to_mask = masked_ops; - } + char op = *mode_string++; + mode_t value; + char flag = MODE_COPY_EXISTING; + struct mode_change *change; - while (*mode_string == '=' || *mode_string == '+' || *mode_string == '-') - { - struct mode_change *change = talloc (struct mode_change); - if (change == NULL) + switch (*mode_string++) { - mode_free (head); - return MODE_MEMORY_EXHAUSTED; + 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->next = NULL; - change->op = *mode_string; /* One of "=+-". */ - affected_masked = affected_bits; + change = xmalloc (sizeof *change); + change->op = op; + change->flag = flag; + change->affected = affected; + change->value = value; - /* Per the Single Unix Spec, if `who' is not specified and the - `=' operator is used, then clear all the bits first. */ - if (!who_specified_p && - ops_to_mask & (*mode_string == '=' ? MODE_MASK_EQUALS : 0)) - { - struct mode_change *p = make_node_op_equals (0); - if (p == NULL) - return MODE_MEMORY_EXHAUSTED; - mode_append_entry (&head, &tail, p); - } - - if (ops_to_mask & (*mode_string == '=' ? MODE_MASK_EQUALS - : *mode_string == '+' ? MODE_MASK_PLUS - : MODE_MASK_MINUS)) - affected_masked &= ~umask_value; - change->affected = affected_masked; - change->value = 0; - change->flags = 0; - - /* Add the element to the tail of the list, so the operations - are performed in the correct order. */ - mode_append_entry (&head, &tail, change); - - /* Set `value' according to the bits set in `affected_masked'. */ - for (++mode_string;; ++mode_string) - switch (*mode_string) - { - case 'r': - change->value |= ((S_IRUSR | S_IRGRP | S_IROTH) - & affected_masked); - break; - case 'w': - change->value |= ((S_IWUSR | S_IWGRP | S_IWOTH) - & affected_masked); - break; - case 'X': - change->flags |= MODE_X_IF_ANY_X; - /* Fall through. */ - case 'x': - change->value |= ((S_IXUSR | S_IXGRP | S_IXOTH) - & affected_masked); - break; - case 's': - /* Set the setuid/gid bits if `u' or `g' is selected. */ - change->value |= (S_ISUID | S_ISGID) & affected_masked; - break; - case 't': - /* Set the "save text image" bit if `o' is selected. */ - change->value |= S_ISVTX & affected_masked; - break; - case 'u': - /* Set the affected bits to the value of the `u' bits - on the same file. */ - if (change->value) - goto invalid; - change->value = S_IRWXU; - change->flags |= MODE_COPY_EXISTING; - break; - case 'g': - /* Set the affected bits to the value of the `g' bits - on the same file. */ - if (change->value) - goto invalid; - change->value = S_IRWXG; - change->flags |= MODE_COPY_EXISTING; - break; - case 'o': - /* Set the affected bits to the value of the `o' bits - on the same file. */ - if (change->value) - goto invalid; - change->value = S_IRWXO; - change->flags |= MODE_COPY_EXISTING; - break; - default: - goto no_more_values; - } - no_more_values:; + *tail = change; + tail = &change->next; } + while (*mode_string == '=' || *mode_string == '+' + || *mode_string == '-'); if (*mode_string != ',') break; } if (*mode_string == 0) - return head; + { + *tail = NULL; + return head; + } + invalid: + *tail = NULL; mode_free (head); - return MODE_INVALID; + return NULL; } /* Return a file mode change operation that sets permissions to match those - of REF_FILE. Return MODE_BAD_REFERENCE if REF_FILE can't be accessed. */ + of REF_FILE. Return NULL (setting errno) if REF_FILE can't be accessed. */ struct mode_change * mode_create_from_ref (const char *ref_file) @@ -369,16 +256,12 @@ mode_create_from_ref (const char *ref_file) struct mode_change *change; /* the only change element */ struct stat ref_stats; - if (stat (ref_file, &ref_stats)) - return MODE_BAD_REFERENCE; - - change = talloc (struct mode_change); - - if (change == NULL) - return MODE_MEMORY_EXHAUSTED; + if (stat (ref_file, &ref_stats) != 0) + return NULL; + change = xmalloc (sizeof *change); change->op = '='; - change->flags = 0; + change->flag = MODE_ORDINARY_CHANGE; change->affected = CHMOD_MODE_BITS; change->value = ref_stats.st_mode; change->next = NULL; @@ -387,90 +270,83 @@ mode_create_from_ref (const char *ref_file) } /* Return file mode OLDMODE, adjusted as indicated by the list of change - operations CHANGES. If OLDMODE is a directory, the type `X' + 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. */ + The returned value has the S_IFMT bits cleared. */ mode_t -mode_adjust (mode_t oldmode, const struct mode_change *changes) +mode_adjust (mode_t oldmode, struct mode_change const *changes, + mode_t umask_value) { - mode_t newmode; /* The adjusted mode and one operand. */ - mode_t value; /* The other operand. */ - - newmode = oldmode & CHMOD_MODE_BITS; + /* The adjusted mode. */ + mode_t newmode = oldmode & CHMOD_MODE_BITS; for (; changes; changes = changes->next) { - if (changes->flags & MODE_COPY_EXISTING) - { - /* Isolate in `value' the bits in `newmode' to copy, given in - the mask `changes->value'. */ - value = newmode & changes->value; - - if (changes->value & S_IRWXU) - /* Copy `u' permissions onto `g' and `o'. */ - value |= ( (value & S_IRUSR ? S_IRGRP | S_IROTH : 0) - | (value & S_IWUSR ? S_IWGRP | S_IWOTH : 0) - | (value & S_IXUSR ? S_IXGRP | S_IXOTH : 0)); - else if (changes->value & S_IRWXG) - /* Copy `g' permissions onto `u' and `o'. */ - value |= ( (value & S_IRGRP ? S_IRUSR | S_IROTH : 0) - | (value & S_IWGRP ? S_IWUSR | S_IWOTH : 0) - | (value & S_IXGRP ? S_IXUSR | S_IXOTH : 0)); - else - /* Copy `o' permissions onto `u' and `g'. */ - value |= ( (value & S_IROTH ? S_IRUSR | S_IRGRP : 0) - | (value & S_IWOTH ? S_IWUSR | S_IWGRP : 0) - | (value & S_IXOTH ? S_IXUSR | S_IXGRP : 0)); - - /* In order to change only `u', `g', or `o' permissions, - or some combination thereof, clear unselected bits. - This cannot be done in mode_compile because the value - to which the `changes->affected' mask is applied depends - on the old mode of each file. */ - value &= changes->affected; - } - else + mode_t affected = changes->affected; + mode_t value = changes->value; + + switch (changes->flag) { - value = changes->value; - /* If `X', do not affect the execute bits if the file is not a - directory and no execute bits are already set. */ - if ((changes->flags & MODE_X_IF_ANY_X) - && !S_ISDIR (oldmode) - && (newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) - /* Clear the execute bits. */ - value &= ~ (S_IXUSR | S_IXGRP | S_IXOTH); + 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; } + /* If WHO was specified, limit the change to the affected bits. + Otherwise, apply the umask. */ + value &= (affected ? affected : ~umask_value); + switch (changes->op) { case '=': - /* Preserve the previous values in `newmode' of bits that are - not affected by this change operation. */ - newmode = (newmode & ~changes->affected) | value; - break; + /* 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; } } + return newmode; } /* Free the memory used by the list of file mode change operations - CHANGES. */ + CHANGES. */ void -mode_free (register struct mode_change *changes) +mode_free (struct mode_change *changes) { - register struct mode_change *next; - while (changes) { - next = changes->next; + struct mode_change *next = changes->next; free (changes); changes = next; } |