diff options
Diffstat (limited to '')
-rw-r--r-- | chmod.c | 249 |
1 files changed, 249 insertions, 0 deletions
@@ -0,0 +1,249 @@ +/* + * Implement the core of the --chmod option. + * + * Copyright (C) 2002 Scott Howard + * Copyright (C) 2005-2020 Wayne Davison + * + * 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 + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, visit the http://fsf.org website. + */ + +#include "rsync.h" +#include "itypes.h" + +extern mode_t orig_umask; + +#define FLAG_X_KEEP (1<<0) +#define FLAG_DIRS_ONLY (1<<1) +#define FLAG_FILES_ONLY (1<<2) + +struct chmod_mode_struct { + struct chmod_mode_struct *next; + int ModeAND, ModeOR; + char flags; +}; + +#define CHMOD_ADD 1 +#define CHMOD_SUB 2 +#define CHMOD_EQ 3 +#define CHMOD_SET 4 + +#define STATE_ERROR 0 +#define STATE_1ST_HALF 1 +#define STATE_2ND_HALF 2 +#define STATE_OCTAL_NUM 3 + +/* Parse a chmod-style argument, and break it down into one or more AND/OR + * pairs in a linked list. We return a pointer to new items on success + * (appending the items to the specified list), or NULL on error. */ +struct chmod_mode_struct *parse_chmod(const char *modestr, + struct chmod_mode_struct **root_mode_ptr) +{ + int state = STATE_1ST_HALF; + int where = 0, what = 0, op = 0, topbits = 0, topoct = 0, flags = 0; + struct chmod_mode_struct *first_mode = NULL, *curr_mode = NULL, + *prev_mode = NULL; + + while (state != STATE_ERROR) { + if (!*modestr || *modestr == ',') { + int bits; + + if (!op) { + state = STATE_ERROR; + break; + } + prev_mode = curr_mode; + curr_mode = new_array(struct chmod_mode_struct, 1); + if (prev_mode) + prev_mode->next = curr_mode; + else + first_mode = curr_mode; + curr_mode->next = NULL; + + if (where) + bits = where * what; + else { + where = 0111; + bits = (where * what) & ~orig_umask; + } + + switch (op) { + case CHMOD_ADD: + curr_mode->ModeAND = CHMOD_BITS; + curr_mode->ModeOR = bits + topoct; + break; + case CHMOD_SUB: + curr_mode->ModeAND = CHMOD_BITS - bits - topoct; + curr_mode->ModeOR = 0; + break; + case CHMOD_EQ: + curr_mode->ModeAND = CHMOD_BITS - (where * 7) - (topoct ? topbits : 0); + curr_mode->ModeOR = bits + topoct; + break; + case CHMOD_SET: + curr_mode->ModeAND = 0; + curr_mode->ModeOR = bits; + break; + } + + curr_mode->flags = flags; + + if (!*modestr) + break; + modestr++; + + state = STATE_1ST_HALF; + where = what = op = topoct = topbits = flags = 0; + } + + switch (state) { + case STATE_1ST_HALF: + switch (*modestr) { + case 'D': + if (flags & FLAG_FILES_ONLY) + state = STATE_ERROR; + flags |= FLAG_DIRS_ONLY; + break; + case 'F': + if (flags & FLAG_DIRS_ONLY) + state = STATE_ERROR; + flags |= FLAG_FILES_ONLY; + break; + case 'u': + where |= 0100; + topbits |= 04000; + break; + case 'g': + where |= 0010; + topbits |= 02000; + break; + case 'o': + where |= 0001; + break; + case 'a': + where |= 0111; + break; + case '+': + op = CHMOD_ADD; + state = STATE_2ND_HALF; + break; + case '-': + op = CHMOD_SUB; + state = STATE_2ND_HALF; + break; + case '=': + op = CHMOD_EQ; + state = STATE_2ND_HALF; + break; + default: + if (isDigit(modestr) && *modestr < '8' && !where) { + op = CHMOD_SET; + state = STATE_OCTAL_NUM; + where = 1; + what = *modestr - '0'; + } else + state = STATE_ERROR; + break; + } + break; + case STATE_2ND_HALF: + switch (*modestr) { + case 'r': + what |= 4; + break; + case 'w': + what |= 2; + break; + case 'X': + flags |= FLAG_X_KEEP; + /* FALL THROUGH */ + case 'x': + what |= 1; + break; + case 's': + if (topbits) + topoct |= topbits; + else + topoct = 04000; + break; + case 't': + topoct |= 01000; + break; + default: + state = STATE_ERROR; + break; + } + break; + case STATE_OCTAL_NUM: + if (isDigit(modestr) && *modestr < '8') { + what = what*8 + *modestr - '0'; + if (what > CHMOD_BITS) + state = STATE_ERROR; + } else + state = STATE_ERROR; + break; + } + modestr++; + } + + if (state == STATE_ERROR) { + free_chmod_mode(first_mode); + return NULL; + } + + if (!(curr_mode = *root_mode_ptr)) + *root_mode_ptr = first_mode; + else { + while (curr_mode->next) + curr_mode = curr_mode->next; + curr_mode->next = first_mode; + } + + return first_mode; +} + + +/* Takes an existing file permission and a list of AND/OR changes, and + * create a new permissions. */ +int tweak_mode(int mode, struct chmod_mode_struct *chmod_modes) +{ + int IsX = mode & 0111; + int NonPerm = mode & ~CHMOD_BITS; + + for ( ; chmod_modes; chmod_modes = chmod_modes->next) { + if ((chmod_modes->flags & FLAG_DIRS_ONLY) && !S_ISDIR(NonPerm)) + continue; + if ((chmod_modes->flags & FLAG_FILES_ONLY) && S_ISDIR(NonPerm)) + continue; + mode &= chmod_modes->ModeAND; + if ((chmod_modes->flags & FLAG_X_KEEP) && !IsX && !S_ISDIR(NonPerm)) + mode |= chmod_modes->ModeOR & ~0111; + else + mode |= chmod_modes->ModeOR; + } + + return mode | NonPerm; +} + +/* Free the linked list created by parse_chmod. */ +int free_chmod_mode(struct chmod_mode_struct *chmod_modes) +{ + struct chmod_mode_struct *next; + + while (chmod_modes) { + next = chmod_modes->next; + free(chmod_modes); + chmod_modes = next; + } + return 0; +} |