#if HAVE_CONFIG_H
# include <config.h>
#endif
#include "modechange.h"
#include <sys/stat.h>
#include "xstrtol.h"
#if STDC_HEADERS
# include <stdlib.h>
#else
char *malloc ();
#endif
#ifndef NULL
# define NULL 0
#endif
#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
#define SUID 04000
#define SGID 02000
#define SVTX 01000
#define RUSR 00400
#define WUSR 00200
#define XUSR 00100
#define RGRP 00040
#define WGRP 00020
#define XGRP 00010
#define ROTH 00004
#define WOTH 00002
#define XOTH 00001
#define ALLM 07777
#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
#define CHMOD_MODE_BITS \
(S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
#define talloc(type) ((type *) malloc (sizeof (type)))
static struct mode_change *
make_node_op_equals (mode_t new_mode)
{
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;
return p;
}
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;
}
}
struct mode_change *
mode_compile (const char *mode_string, unsigned int masked_ops)
{
struct mode_change *head;
struct mode_change *tail;
unsigned long octal_value;
mode_t umask_value;
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;
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
: ((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)));
p = make_node_op_equals (mode);
if (p == NULL)
return MODE_MEMORY_EXHAUSTED;
mode_append_entry (&head, &tail, p);
return head;
}
umask_value = umask (0);
umask (umask_value);
--mode_string;
do
{
mode_t affected_bits = 0;
mode_t affected_masked;
unsigned ops_to_mask = 0;
int who_specified_p;
affected_bits = 0;
ops_to_mask = 0;
for (++mode_string;; ++mode_string)
switch (*mode_string)
{
case 'u':
affected_bits |= S_ISUID | S_IRWXU;
break;
case 'g':
affected_bits |= S_ISGID | S_IRWXG;
break;
case 'o':
affected_bits |= S_ISVTX | S_IRWXO;
break;
case 'a':
affected_bits |= CHMOD_MODE_BITS;
break;
default:
goto no_more_affected;
}
no_more_affected:
if (affected_bits)
who_specified_p = 1;
else
{
who_specified_p = 0;
affected_bits = CHMOD_MODE_BITS;
ops_to_mask = masked_ops;
}
while (*mode_string == '=' || *mode_string == '+' || *mode_string == '-')
{
struct mode_change *change = talloc (struct mode_change);
if (change == NULL)
{
mode_free (head);
return MODE_MEMORY_EXHAUSTED;
}
change->next = NULL;
change->op = *mode_string;
affected_masked = affected_bits;
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;
mode_append_entry (&head, &tail, change);
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;
case 'x':
change->value |= ((S_IXUSR | S_IXGRP | S_IXOTH)
& affected_masked);
break;
case 's':
change->value |= (S_ISUID | S_ISGID) & affected_masked;
break;
case 't':
change->value |= S_ISVTX & affected_masked;
break;
case 'u':
if (change->value)
goto invalid;
change->value = S_IRWXU;
change->flags |= MODE_COPY_EXISTING;
break;
case 'g':
if (change->value)
goto invalid;
change->value = S_IRWXG;
change->flags |= MODE_COPY_EXISTING;
break;
case 'o':
if (change->value)
goto invalid;
change->value = S_IRWXO;
change->flags |= MODE_COPY_EXISTING;
break;
default:
goto no_more_values;
}
no_more_values:;
}
} while (*mode_string == ',');
if (*mode_string == 0)
return head;
invalid:
mode_free (head);
return MODE_INVALID;
}
struct mode_change *
mode_create_from_ref (const char *ref_file)
{
struct mode_change *change;
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;
change->op = '=';
change->flags = 0;
change->affected = CHMOD_MODE_BITS;
change->value = ref_stats.st_mode;
change->next = NULL;
return change;
}
mode_t
mode_adjust (mode_t oldmode, const struct mode_change *changes)
{
mode_t newmode;
mode_t value;
newmode = oldmode & CHMOD_MODE_BITS;
for (; changes; changes = changes->next)
{
if (changes->flags & MODE_COPY_EXISTING)
{
value = newmode & changes->value;
if (changes->value & S_IRWXU)
value |= ((value & S_IRUSR ? S_IRGRP | S_IROTH : 0)
| (value & S_IWUSR ? S_IWGRP | S_IROTH : 0)
| (value & S_IXUSR ? S_IXGRP | S_IXOTH : 0));
else if (changes->value & S_IRWXG)
value |= ((value & S_IRGRP ? S_IRUSR | S_IROTH : 0)
| (value & S_IWGRP ? S_IWUSR | S_IROTH : 0)
| (value & S_IXGRP ? S_IXUSR | S_IXOTH : 0));
else
value |= ((value & S_IROTH ? S_IRUSR | S_IRGRP : 0)
| (value & S_IWOTH ? S_IWUSR | S_IRGRP : 0)
| (value & S_IXOTH ? S_IXUSR | S_IXGRP : 0));
value &= changes->affected;
}
else
{
value = changes->value;
if ((changes->flags & MODE_X_IF_ANY_X)
&& !S_ISDIR (oldmode)
&& (newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0)
value &= ~ (S_IXUSR | S_IXGRP | S_IXOTH);
}
switch (changes->op)
{
case '=':
newmode = (newmode & ~changes->affected) | value;
break;
case '+':
newmode |= value;
break;
case '-':
newmode &= ~value;
break;
}
}
return newmode;
}
void
mode_free (register struct mode_change *changes)
{
register struct mode_change *next;
while (changes)
{
next = changes->next;
free (changes);
changes = next;
}
}