Created
May 27, 2017 16:01
-
-
Save clausecker/950f607dab60fbc891c421341461cf91 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /*- | |
| * 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