1
0
mirror of git://git.sv.gnu.org/coreutils.git synced 2026-06-01 07:16:33 +02:00

Include stat-macros.h, xalloc.h.

(S_ISDIR, S_ISUID, S_ISGID, S_ISVTX, S_IRUSR, S_IWUSR, S_IXUSR):
(S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH, S_IRXWU):
(S_IRWXG, S_IRWXO, CHMOD_MODE_BITS):
Remove.  This is now stat-macros.h's job.
(talloc): Remove.  All callers replaced by xalloc, so that
our invokers don't have to worry about reporting memory failures.
(make_node_op_equals): Remove.
(MODE_ORDINARY_CHAGE, MODE_X_IF_ANY_X, MODE_COPY_EXISTING):
New constants.
(struct mode_change): Moved here from modechange.h.
(mode_append_entry): Remove.
(mode_compile): Remove MASKED_OPS arg, since it encouraged
apps to have incorrect behavior.  Use simpler algorithm for head
and tail.  Don't futz with umask; that's now the job of mode_adjust.
Detect more invalid usages rather than having somewhat-random behavior.
Don't insert an "a=" action, as that leads to incorrect behavior.
(mode_compile, mode_create_from_ref): Return NULL on error instead
of an enum, since now there's only one way to have an error.  All
callers changed.
(mode_adjust): Accept new arg UMASK_VALUE, and interpret it
at the correct time.  Simplify calculation of "+u" and its ilk.
Don't mishandle "+X".
(mode_free): Remove "register" and localize decls.
This commit is contained in:
Paul Eggert
2005-04-28 16:29:00 +00:00
parent 3f4bb8e0c8
commit 498bb2796f
+171 -295
View File
@@ -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
/* Special operations flags. */
enum
{
/* The typical case. */
MODE_ORDINARY_CHANGE,
/* 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)
/* 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,
/* Return newly allocated memory to hold one element of type TYPE. */
#define talloc(type) ((type *) malloc (sizeof (type)))
/* 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
};
/* 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)
/* 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;
mode_t affected = changes->affected;
mode_t value = 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
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;
}