Skip to content

Instantly share code, notes, and snippets.

@clausecker
Created May 27, 2017 16:01
Show Gist options
  • Save clausecker/950f607dab60fbc891c421341461cf91 to your computer and use it in GitHub Desktop.
Save clausecker/950f607dab60fbc891c421341461cf91 to your computer and use it in GitHub Desktop.
/*-
* Copyright (c) 2014, 2016 Robert Clausecker
*/
/*
* parse_mode() - parse an octal or symbolic mode string into a mode_t
*/
#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "perm.h"
#include "err.h"
/*
* Character names for the state machine in parse_symbolic(), also used
* in apply_op() to determine the operator name. The purpose of this
* table is to keep the jump tables generated for the switch statements
* short by placing the the values next to each other.
*/
enum class {
CL_PLUS, /* +-= */
CL_MINUS,
CL_EQUAL,
CL_COMMA, /* , */
CL_X, /* rwxXst */
CL_r,
CL_w,
CL_x,
CL_s,
CL_t,
CL_u, /* ugo */
CL_g,
CL_o,
CL_a, /* a */
CL_MAX, /* number of classes */
};
/*
* A string containing at each index the character for the same-valued
* character class.
*/
static const char clstring[CL_MAX] = "+-=,Xrwxstugoa";
static int parse_octal(const char*, mode_t*);
static int parse_symbolic(const char*, mode_t, mode_t*);
static int apply_op(int, int, int, int);
/*
* Parse an octal or symbolic mode string as specified in IEEE Std 1003.1 2008
* Edition for the command chmod from mstr and apply the result to mode,
* applying mask in the process. Notice that this function evaluates the file
* type of mode and does the right thing for both files and directories. Return
* 0 if parsing was successful, 1 otherwise. If parsing was not succesful, the
* content of mode is undefined, otherwise it contains the result of applying
* the mode described in mstr onto mode.
*/
extern int
parse_mode(const char *mstr, mode_t mask, mode_t *mode)
{
if (parse_octal(mstr, mode))
return (0);
return (parse_symbolic(mstr, mask, mode));
}
/*
* Check if mstr contains an octal mode. Return 1 if mstr is an octal mode
* string, 0 otherwise. If mstr contains an octal mode, update mode to said
* mode.
*/
static int
parse_octal(const char *mstr, mode_t *mode)
{
char *endptr;
unsigned long newmode;
if (*mstr == '\0')
return (0);
newmode = strtoul(mstr, &endptr, 8);
if (*endptr != '\0' || newmode > 07777)
return (0);
*mode &= ~07777; /* preserve file mode */
*mode |= newmode;
return (1);
}
/*
* Parse one clause of a symbolic mode string and apply it to mode respecting
* mask. Return 0 on success, 1 on failure.
*
* The parser implemented by this function can be described by this POSIX
* extended regular expression:
*
* [ugoa]*([=+-]([rwxXst]*|[ugo]))+(,[ugoa]*([=+-]([rwxXst]*|[ugo]))+)*
*/
static int
parse_symbolic(const char *mstr, mode_t mask, mode_t *modebuf)
{
/* the parser is implemented as a state machine with four states */
enum states {
BEGIN = 0, /* initial state */
HASOP, /* have an operator */
PLIST, /* permission list */
PCOPY, /* copy permissions */
};
/*
* This is the state machine implemented in this function:
*
* state rwxXst ugo a +-= , EOF
* BEGIN reject BEGIN BEGIN HASOP reject reject
* HASOP PLIST PCOPY reject HASOP BEGIN accept
* PLIST PLIST reject reject HASOP BEGIN accept
* PCOPY reject reject reject HASOP BEGIN accept
*/
size_t i = 0;
int mode = (int)(*modebuf & 07777);
int who = 0, perm, op, state = BEGIN, class;
/* limits for permtab and whotab */
enum {
FIRSTPERM = CL_r,
FIRSTWHO = CL_u,
};
static const short permtab[] = {
[CL_r - FIRSTPERM] = 00444,
[CL_w - FIRSTPERM] = 00222,
[CL_x - FIRSTPERM] = 00111,
[CL_s - FIRSTPERM] = 06000,
[CL_t - FIRSTPERM] = 01000,
};
const char *endptr;
for (i = 0; mstr[i] != '\0'; i++) {
/* fetch character class by looking it up in clstring */
endptr = memchr(clstring, mstr[i], CL_MAX);
if (endptr == NULL)
return (1);
class = (int)(endptr - clstring);
switch (class) {
case CL_X:
if (!(state == HASOP || state == PLIST))
return (1);
if (S_ISDIR(*modebuf) || (*modebuf & 00111) != 0)
perm |= 00111;
state = PLIST;
continue;
case CL_r:
case CL_w:
case CL_x:
case CL_s:
case CL_t:
if (!(state == HASOP || state == PLIST))
return (1);
perm |= permtab[class - FIRSTPERM];
state = PLIST;
continue;
case CL_u:
if (state == BEGIN)
who |= 04700;
else if (state == HASOP) {
perm = mode & 00700;
perm |= perm >> 3 | perm >> 6;
perm |= mode & 04000 | (mode & 04000) >> 1;
state = PCOPY;
} else
return (1);
continue;
case CL_g:
if (state == BEGIN)
who |= 02070;
else if (state == HASOP) {
perm = mode & 00070;
perm |= perm << 3 | perm >> 3;
perm |= mode & 02000 | (mode & 02000) << 1;
state = PCOPY;
} else
return (1);
continue;
case CL_o:
if (state == BEGIN)
who |= 01007;
else if (state == HASOP) {
perm = mode & 00007;
perm |= perm << 3 | perm << 6;
perm |= mode & 01000;
state = PCOPY;
} else
return (1);
continue;
case CL_a:
if (state != BEGIN)
return (1);
who = 07777;
continue;
case CL_PLUS:
case CL_MINUS:
case CL_EQUAL:
if (state == BEGIN) {
/* if no wholist provided, take it from umask */
if (who == 0)
who = ~mask & 07777;
} else
mode = apply_op(mode, op, who, perm);
state = HASOP;
op = class;
perm = 0;
continue;
case CL_COMMA:
if (state == BEGIN)
return (1);
mode = apply_op(mode, op, who, perm);
who = 0;
state = BEGIN;
continue;
}
}
if (state == BEGIN)
return (1);
mode = apply_op(mode, op, who, perm);
*modebuf = (mode_t)(*modebuf & ~07777 | mode & 07777);
return (0);
}
/*
* Apply operator permission bits perm to who in mode using operator op.
*/
static int
apply_op(int mode, int op, int who, int perm)
{
switch (op) {
case CL_PLUS:
return (mode | perm & who);
case CL_MINUS:
return (mode & ~(perm & who));
case CL_EQUAL:
return (mode & ~who | perm & who);
default:
errx(1, "Unreachable case in apply_op() -- report bug!");
/* UNREACHABLE */
return (0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment