#include <config.h>
#include "modechange.h"
#include <sys/stat.h>
#include "stat-macros.h"
#include "xalloc.h"
#include <stdlib.h>
#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
static mode_t
octal_to_mode (unsigned int octal)
{
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)));
}
enum
{
MODE_DONE,
MODE_ORDINARY_CHANGE,
MODE_X_IF_ANY_X,
MODE_COPY_EXISTING
};
struct mode_change
{
char op;
char flag;
mode_t affected;
mode_t value;
mode_t mentioned;
};
static struct mode_change *
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;
}
struct mode_change *
mode_compile (char const *mode_string)
{
struct mode_change *mc;
size_t used = 0;
if ('0' <= *mode_string && *mode_string < '8')
{
unsigned int octal_mode = 0;
mode_t mode;
mode_t mentioned;
do
{
octal_mode = 8 * octal_mode + *mode_string++ - '0';
if (ALLM < octal_mode)
return NULL;
}
while ('0' <= *mode_string && *mode_string < '8');
if (*mode_string)
return NULL;
mode = octal_to_mode (octal_mode);
mentioned = (mode & (S_ISUID | S_ISGID)) | S_ISVTX | S_IRWXUGO;
return make_node_op_equals (mode, mentioned);
}
{
size_t needed = 1;
char const *p;
for (p = mode_string; *p; p++)
needed += (*p == '=' || *p == '+' || *p == '-');
mc = xnmalloc (needed, sizeof *mc);
}
for (;; mode_string++)
{
mode_t affected = 0;
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;
}
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':
value = S_IRWXU;
break;
case 'g':
value = S_IRWXG;
break;
case 'o':
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':
value |= S_ISUID | S_ISGID;
break;
case 't':
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 = (affected ? affected & value : value);
}
while (*mode_string == '=' || *mode_string == '+'
|| *mode_string == '-');
if (*mode_string != ',')
break;
}
if (*mode_string == 0)
{
mc[used].flag = MODE_DONE;
return mc;
}
invalid:
free (mc);
return NULL;
}
struct mode_change *
mode_create_from_ref (const char *ref_file)
{
struct stat ref_stats;
if (stat (ref_file, &ref_stats) != 0)
return NULL;
return make_node_op_equals (ref_stats.st_mode, CHMOD_MODE_BITS);
}
mode_t
mode_adjust (mode_t oldmode, bool dir, mode_t umask_value,
struct mode_change const *changes, mode_t *pmode_bits)
{
mode_t newmode = oldmode & CHMOD_MODE_BITS;
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:
value &= newmode;
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:
if ((newmode & (S_IXUSR | S_IXGRP | S_IXOTH)) | dir)
value |= S_IXUSR | S_IXGRP | S_IXOTH;
break;
}
value &= (affected ? affected : ~umask_value) & ~ omit_change;
switch (changes->op)
{
case '=':
{
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;
}