| /* |
| * Create a squashfs filesystem. This is a highly compressed read only |
| * filesystem. |
| * |
| * Copyright (c) 2011, 2012, 2013, 2014 |
| * Phillip Lougher <phillip@squashfs.org.uk> |
| * |
| * 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 2, |
| * 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, write to the Free Software |
| * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| * |
| * action.c |
| */ |
| |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <fnmatch.h> |
| #include <pwd.h> |
| #include <grp.h> |
| #include <sys/wait.h> |
| #include <regex.h> |
| #include <limits.h> |
| #include <errno.h> |
| |
| #ifndef FNM_EXTMATCH /* glibc extension */ |
| #define FNM_EXTMATCH 0 |
| #endif |
| |
| #include "squashfs_fs.h" |
| #include "mksquashfs.h" |
| #include "action.h" |
| #include "error.h" |
| |
| /* |
| * code to parse actions |
| */ |
| |
| static char *cur_ptr, *source; |
| static struct action *fragment_spec = NULL; |
| static struct action *exclude_spec = NULL; |
| static struct action *empty_spec = NULL; |
| static struct action *move_spec = NULL; |
| static struct action *prune_spec = NULL; |
| static struct action *other_spec = NULL; |
| static int fragment_count = 0; |
| static int exclude_count = 0; |
| static int empty_count = 0; |
| static int move_count = 0; |
| static int prune_count = 0; |
| static int other_count = 0; |
| static struct action_entry *parsing_action; |
| |
| static struct file_buffer *def_fragment = NULL; |
| |
| static struct token_entry token_table[] = { |
| { "(", TOK_OPEN_BRACKET, 1, }, |
| { ")", TOK_CLOSE_BRACKET, 1 }, |
| { "&&", TOK_AND, 2 }, |
| { "||", TOK_OR, 2 }, |
| { "!", TOK_NOT, 1 }, |
| { ",", TOK_COMMA, 1 }, |
| { "@", TOK_AT, 1}, |
| { " ", TOK_WHITE_SPACE, 1 }, |
| { "\t ", TOK_WHITE_SPACE, 1 }, |
| { "", -1, 0 } |
| }; |
| |
| |
| static struct test_entry test_table[]; |
| |
| static struct action_entry action_table[]; |
| |
| static struct expr *parse_expr(int subexp); |
| |
| extern char *pathname(struct dir_ent *); |
| |
| extern char *subpathname(struct dir_ent *); |
| |
| extern int read_file(char *filename, char *type, int (parse_line)(char *)); |
| |
| /* |
| * Lexical analyser |
| */ |
| #define STR_SIZE 256 |
| |
| static int get_token(char **string) |
| { |
| /* string buffer */ |
| static char *str = NULL; |
| static int size = 0; |
| |
| char *str_ptr; |
| int cur_size, i, quoted; |
| |
| while (1) { |
| if (*cur_ptr == '\0') |
| return TOK_EOF; |
| for (i = 0; token_table[i].token != -1; i++) |
| if (strncmp(cur_ptr, token_table[i].string, |
| token_table[i].size) == 0) |
| break; |
| if (token_table[i].token != TOK_WHITE_SPACE) |
| break; |
| cur_ptr ++; |
| } |
| |
| if (token_table[i].token != -1) { |
| cur_ptr += token_table[i].size; |
| return token_table[i].token; |
| } |
| |
| /* string */ |
| if(str == NULL) { |
| str = malloc(STR_SIZE); |
| if(str == NULL) |
| MEM_ERROR(); |
| size = STR_SIZE; |
| } |
| |
| /* Initialise string being read */ |
| str_ptr = str; |
| cur_size = 0; |
| quoted = 0; |
| |
| while(1) { |
| while(*cur_ptr == '"') { |
| cur_ptr ++; |
| quoted = !quoted; |
| } |
| |
| if(*cur_ptr == '\0') { |
| /* inside quoted string EOF, otherwise end of string */ |
| if(quoted) |
| return TOK_EOF; |
| else |
| break; |
| } |
| |
| if(!quoted) { |
| for(i = 0; token_table[i].token != -1; i++) |
| if (strncmp(cur_ptr, token_table[i].string, |
| token_table[i].size) == 0) |
| break; |
| if (token_table[i].token != -1) |
| break; |
| } |
| |
| if(*cur_ptr == '\\') { |
| cur_ptr ++; |
| if(*cur_ptr == '\0') |
| return TOK_EOF; |
| } |
| |
| if(cur_size + 2 > size) { |
| char *tmp; |
| |
| size = (cur_size + 1 + STR_SIZE) & ~(STR_SIZE - 1); |
| |
| tmp = realloc(str, size); |
| if(tmp == NULL) |
| MEM_ERROR(); |
| |
| str_ptr = str_ptr - str + tmp; |
| str = tmp; |
| } |
| |
| *str_ptr ++ = *cur_ptr ++; |
| cur_size ++; |
| } |
| |
| *str_ptr = '\0'; |
| *string = str; |
| return TOK_STRING; |
| } |
| |
| |
| static int peek_token(char **string) |
| { |
| char *saved = cur_ptr; |
| int token = get_token(string); |
| |
| cur_ptr = saved; |
| |
| return token; |
| } |
| |
| |
| /* |
| * Expression parser |
| */ |
| static void free_parse_tree(struct expr *expr) |
| { |
| if(expr->type == ATOM_TYPE) { |
| int i; |
| |
| for(i = 0; i < expr->atom.test->args; i++) |
| free(expr->atom.argv[i]); |
| |
| free(expr->atom.argv); |
| } else if (expr->type == UNARY_TYPE) |
| free_parse_tree(expr->unary_op.expr); |
| else { |
| free_parse_tree(expr->expr_op.lhs); |
| free_parse_tree(expr->expr_op.rhs); |
| } |
| |
| free(expr); |
| } |
| |
| |
| static struct expr *create_expr(struct expr *lhs, int op, struct expr *rhs) |
| { |
| struct expr *expr; |
| |
| if (rhs == NULL) { |
| free_parse_tree(lhs); |
| return NULL; |
| } |
| |
| expr = malloc(sizeof(*expr)); |
| if (expr == NULL) |
| MEM_ERROR(); |
| |
| expr->type = OP_TYPE; |
| expr->expr_op.lhs = lhs; |
| expr->expr_op.rhs = rhs; |
| expr->expr_op.op = op; |
| |
| return expr; |
| } |
| |
| |
| static struct expr *create_unary_op(struct expr *lhs, int op) |
| { |
| struct expr *expr; |
| |
| if (lhs == NULL) |
| return NULL; |
| |
| expr = malloc(sizeof(*expr)); |
| if (expr == NULL) |
| MEM_ERROR(); |
| |
| expr->type = UNARY_TYPE; |
| expr->unary_op.expr = lhs; |
| expr->unary_op.op = op; |
| |
| return expr; |
| } |
| |
| |
| static struct expr *parse_test(char *name) |
| { |
| char *string, **argv = NULL; |
| int token, args = 0; |
| int i; |
| struct test_entry *test; |
| struct expr *expr; |
| |
| for (i = 0; test_table[i].args != -1; i++) |
| if (strcmp(name, test_table[i].name) == 0) |
| break; |
| |
| test = &test_table[i]; |
| |
| if (test->args == -1) { |
| SYNTAX_ERROR("Non-existent test \"%s\"\n", name); |
| return NULL; |
| } |
| |
| if(parsing_action->type == EXCLUDE_ACTION && !test->exclude_ok) { |
| fprintf(stderr, "Failed to parse action \"%s\"\n", source); |
| fprintf(stderr, "Test \"%s\" cannot be used in exclude " |
| "actions\n", name); |
| fprintf(stderr, "Use prune action instead ...\n"); |
| return NULL; |
| } |
| |
| expr = malloc(sizeof(*expr)); |
| if (expr == NULL) |
| MEM_ERROR(); |
| |
| expr->type = ATOM_TYPE; |
| |
| expr->atom.test = test; |
| expr->atom.data = NULL; |
| |
| /* |
| * If the test has no arguments, then go straight to checking if there's |
| * enough arguments |
| */ |
| token = peek_token(&string); |
| |
| if (token != TOK_OPEN_BRACKET) |
| goto skip_args; |
| |
| get_token(&string); |
| |
| /* |
| * speculatively read all the arguments, and then see if the |
| * number of arguments read is the number expected, this handles |
| * tests with a variable number of arguments |
| */ |
| token = get_token(&string); |
| if (token == TOK_CLOSE_BRACKET) |
| goto skip_args; |
| |
| while(1) { |
| if (token != TOK_STRING) { |
| SYNTAX_ERROR("Unexpected token \"%s\", expected " |
| "argument\n", TOK_TO_STR(token, string)); |
| goto failed; |
| } |
| |
| argv = realloc(argv, (args + 1) * sizeof(char *)); |
| if (argv == NULL) |
| MEM_ERROR(); |
| |
| argv[args ++ ] = strdup(string); |
| |
| token = get_token(&string); |
| |
| if (token == TOK_CLOSE_BRACKET) |
| break; |
| |
| if (token != TOK_COMMA) { |
| SYNTAX_ERROR("Unexpected token \"%s\", expected " |
| "\",\" or \")\"\n", TOK_TO_STR(token, string)); |
| goto failed; |
| } |
| token = get_token(&string); |
| } |
| |
| skip_args: |
| /* |
| * expected number of arguments? |
| */ |
| if(test->args != -2 && args != test->args) { |
| SYNTAX_ERROR("Unexpected number of arguments, expected %d, " |
| "got %d\n", test->args, args); |
| goto failed; |
| } |
| |
| expr->atom.args = args; |
| expr->atom.argv = argv; |
| |
| if (test->parse_args) { |
| int res = test->parse_args(test, &expr->atom); |
| |
| if (res == 0) |
| goto failed; |
| } |
| |
| return expr; |
| |
| failed: |
| free(argv); |
| free(expr); |
| return NULL; |
| } |
| |
| |
| static struct expr *get_atom() |
| { |
| char *string; |
| int token = get_token(&string); |
| |
| switch(token) { |
| case TOK_NOT: |
| return create_unary_op(get_atom(), token); |
| case TOK_OPEN_BRACKET: |
| return parse_expr(1); |
| case TOK_STRING: |
| return parse_test(string); |
| default: |
| SYNTAX_ERROR("Unexpected token \"%s\", expected test " |
| "operation, \"!\", or \"(\"\n", |
| TOK_TO_STR(token, string)); |
| return NULL; |
| } |
| } |
| |
| |
| static struct expr *parse_expr(int subexp) |
| { |
| struct expr *expr = get_atom(); |
| |
| while (expr) { |
| char *string; |
| int op = get_token(&string); |
| |
| if (op == TOK_EOF) { |
| if (subexp) { |
| free_parse_tree(expr); |
| SYNTAX_ERROR("Expected \"&&\", \"||\" or " |
| "\")\", got EOF\n"); |
| return NULL; |
| } |
| break; |
| } |
| |
| if (op == TOK_CLOSE_BRACKET) { |
| if (!subexp) { |
| free_parse_tree(expr); |
| SYNTAX_ERROR("Unexpected \")\", expected " |
| "\"&&\", \"!!\" or EOF\n"); |
| return NULL; |
| } |
| break; |
| } |
| |
| if (op != TOK_AND && op != TOK_OR) { |
| free_parse_tree(expr); |
| SYNTAX_ERROR("Unexpected token \"%s\", expected " |
| "\"&&\" or \"||\"\n", TOK_TO_STR(op, string)); |
| return NULL; |
| } |
| |
| expr = create_expr(expr, op, get_atom()); |
| } |
| |
| return expr; |
| } |
| |
| |
| /* |
| * Action parser |
| */ |
| int parse_action(char *s, int verbose) |
| { |
| char *string, **argv = NULL; |
| int i, token, args = 0; |
| struct expr *expr; |
| struct action_entry *action; |
| void *data = NULL; |
| struct action **spec_list; |
| int spec_count; |
| |
| cur_ptr = source = s; |
| token = get_token(&string); |
| |
| if (token != TOK_STRING) { |
| SYNTAX_ERROR("Unexpected token \"%s\", expected name\n", |
| TOK_TO_STR(token, string)); |
| return 0; |
| } |
| |
| for (i = 0; action_table[i].args != -1; i++) |
| if (strcmp(string, action_table[i].name) == 0) |
| break; |
| |
| if (action_table[i].args == -1) { |
| SYNTAX_ERROR("Non-existent action \"%s\"\n", string); |
| return 0; |
| } |
| |
| action = &action_table[i]; |
| |
| token = get_token(&string); |
| |
| if (token == TOK_AT) |
| goto skip_args; |
| |
| if (token != TOK_OPEN_BRACKET) { |
| SYNTAX_ERROR("Unexpected token \"%s\", expected \"(\"\n", |
| TOK_TO_STR(token, string)); |
| goto failed; |
| } |
| |
| /* |
| * speculatively read all the arguments, and then see if the |
| * number of arguments read is the number expected, this handles |
| * actions with a variable number of arguments |
| */ |
| token = get_token(&string); |
| if (token == TOK_CLOSE_BRACKET) |
| goto skip_args; |
| |
| while (1) { |
| if (token != TOK_STRING) { |
| SYNTAX_ERROR("Unexpected token \"%s\", expected " |
| "argument\n", TOK_TO_STR(token, string)); |
| goto failed; |
| } |
| |
| argv = realloc(argv, (args + 1) * sizeof(char *)); |
| if (argv == NULL) |
| MEM_ERROR(); |
| |
| argv[args ++] = strdup(string); |
| |
| token = get_token(&string); |
| |
| if (token == TOK_CLOSE_BRACKET) |
| break; |
| |
| if (token != TOK_COMMA) { |
| SYNTAX_ERROR("Unexpected token \"%s\", expected " |
| "\",\" or \")\"\n", TOK_TO_STR(token, string)); |
| goto failed; |
| } |
| token = get_token(&string); |
| } |
| |
| skip_args: |
| /* |
| * expected number of arguments? |
| */ |
| if(action->args != -2 && args != action->args) { |
| SYNTAX_ERROR("Unexpected number of arguments, expected %d, " |
| "got %d\n", action->args, args); |
| goto failed; |
| } |
| |
| if (action->parse_args) { |
| int res = action->parse_args(action, args, argv, &data); |
| |
| if (res == 0) |
| goto failed; |
| } |
| |
| if (token == TOK_CLOSE_BRACKET) |
| token = get_token(&string); |
| |
| if (token != TOK_AT) { |
| SYNTAX_ERROR("Unexpected token \"%s\", expected \"@\"\n", |
| TOK_TO_STR(token, string)); |
| goto failed; |
| } |
| |
| parsing_action = action; |
| expr = parse_expr(0); |
| |
| if (expr == NULL) |
| goto failed; |
| |
| /* |
| * choose action list and increment action counter |
| */ |
| switch(action->type) { |
| case FRAGMENT_ACTION: |
| spec_count = fragment_count ++; |
| spec_list = &fragment_spec; |
| break; |
| case EXCLUDE_ACTION: |
| spec_count = exclude_count ++; |
| spec_list = &exclude_spec; |
| break; |
| case EMPTY_ACTION: |
| spec_count = empty_count ++; |
| spec_list = &empty_spec; |
| break; |
| case MOVE_ACTION: |
| spec_count = move_count ++; |
| spec_list = &move_spec; |
| break; |
| case PRUNE_ACTION: |
| spec_count = prune_count ++; |
| spec_list = &prune_spec; |
| break; |
| default: |
| spec_count = other_count ++; |
| spec_list = &other_spec; |
| } |
| |
| *spec_list = realloc(*spec_list, (spec_count + 1) * |
| sizeof(struct action)); |
| if (*spec_list == NULL) |
| MEM_ERROR(); |
| |
| (*spec_list)[spec_count].type = action->type; |
| (*spec_list)[spec_count].action = action; |
| (*spec_list)[spec_count].args = args; |
| (*spec_list)[spec_count].argv = argv; |
| (*spec_list)[spec_count].expr = expr; |
| (*spec_list)[spec_count].data = data; |
| (*spec_list)[spec_count].verbose = verbose; |
| |
| return 1; |
| |
| failed: |
| free(argv); |
| return 0; |
| } |
| |
| |
| /* |
| * Evaluate expressions |
| */ |
| |
| #define ALLOC_SZ 128 |
| |
| #define LOG_ENABLE 0 |
| #define LOG_DISABLE 1 |
| #define LOG_PRINT 2 |
| #define LOG_ENABLED 3 |
| |
| char *_expr_log(char *string, int cmnd) |
| { |
| static char *expr_msg = NULL; |
| static int cur_size = 0, alloc_size = 0; |
| int size; |
| |
| switch(cmnd) { |
| case LOG_ENABLE: |
| expr_msg = malloc(ALLOC_SZ); |
| alloc_size = ALLOC_SZ; |
| cur_size = 0; |
| return expr_msg; |
| case LOG_DISABLE: |
| free(expr_msg); |
| alloc_size = cur_size = 0; |
| return expr_msg = NULL; |
| case LOG_ENABLED: |
| return expr_msg; |
| default: |
| if(expr_msg == NULL) |
| return NULL; |
| break; |
| } |
| |
| /* if string is empty append '\0' */ |
| size = strlen(string) ? : 1; |
| |
| if(alloc_size - cur_size < size) { |
| /* buffer too small, expand */ |
| alloc_size = (cur_size + size + ALLOC_SZ - 1) & ~(ALLOC_SZ - 1); |
| |
| expr_msg = realloc(expr_msg, alloc_size); |
| if(expr_msg == NULL) |
| MEM_ERROR(); |
| } |
| |
| memcpy(expr_msg + cur_size, string, size); |
| cur_size += size; |
| |
| return expr_msg; |
| } |
| |
| |
| char *expr_log_cmnd(int cmnd) |
| { |
| return _expr_log(NULL, cmnd); |
| } |
| |
| |
| char *expr_log(char *string) |
| { |
| return _expr_log(string, LOG_PRINT); |
| } |
| |
| |
| void expr_log_atom(struct atom *atom) |
| { |
| int i; |
| |
| if(atom->test->handle_logging) |
| return; |
| |
| expr_log(atom->test->name); |
| |
| if(atom->args) { |
| expr_log("("); |
| for(i = 0; i < atom->args; i++) { |
| expr_log(atom->argv[i]); |
| if (i + 1 < atom->args) |
| expr_log(","); |
| } |
| expr_log(")"); |
| } |
| } |
| |
| |
| void expr_log_match(int match) |
| { |
| if(match) |
| expr_log("=True"); |
| else |
| expr_log("=False"); |
| } |
| |
| |
| static int eval_expr_log(struct expr *expr, struct action_data *action_data) |
| { |
| int match; |
| |
| switch (expr->type) { |
| case ATOM_TYPE: |
| expr_log_atom(&expr->atom); |
| match = expr->atom.test->fn(&expr->atom, action_data); |
| expr_log_match(match); |
| break; |
| case UNARY_TYPE: |
| expr_log("!"); |
| match = !eval_expr_log(expr->unary_op.expr, action_data); |
| break; |
| default: |
| expr_log("("); |
| match = eval_expr_log(expr->expr_op.lhs, action_data); |
| |
| if ((expr->expr_op.op == TOK_AND && match) || |
| (expr->expr_op.op == TOK_OR && !match)) { |
| expr_log(token_table[expr->expr_op.op].string); |
| match = eval_expr_log(expr->expr_op.rhs, action_data); |
| } |
| expr_log(")"); |
| break; |
| } |
| |
| return match; |
| } |
| |
| |
| static int eval_expr(struct expr *expr, struct action_data *action_data) |
| { |
| int match; |
| |
| switch (expr->type) { |
| case ATOM_TYPE: |
| match = expr->atom.test->fn(&expr->atom, action_data); |
| break; |
| case UNARY_TYPE: |
| match = !eval_expr(expr->unary_op.expr, action_data); |
| break; |
| default: |
| match = eval_expr(expr->expr_op.lhs, action_data); |
| |
| if ((expr->expr_op.op == TOK_AND && match) || |
| (expr->expr_op.op == TOK_OR && !match)) |
| match = eval_expr(expr->expr_op.rhs, action_data); |
| break; |
| } |
| |
| return match; |
| } |
| |
| |
| static int eval_expr_top(struct action *action, struct action_data *action_data) |
| { |
| if(action->verbose) { |
| int match, n; |
| |
| expr_log_cmnd(LOG_ENABLE); |
| |
| if(action_data->subpath) |
| expr_log(action_data->subpath); |
| |
| expr_log("="); |
| expr_log(action->action->name); |
| |
| if(action->args) { |
| expr_log("("); |
| for (n = 0; n < action->args; n++) { |
| expr_log(action->argv[n]); |
| if(n + 1 < action->args) |
| expr_log(","); |
| } |
| expr_log(")"); |
| } |
| |
| expr_log("@"); |
| |
| match = eval_expr_log(action->expr, action_data); |
| |
| /* |
| * Print the evaluated expression log, if the |
| * result matches the logging specified |
| */ |
| if((match && (action->verbose & ACTION_LOG_TRUE)) || (!match |
| && (action->verbose & ACTION_LOG_FALSE))) |
| progressbar_info("%s\n", expr_log("")); |
| |
| expr_log_cmnd(LOG_DISABLE); |
| |
| return match; |
| } else |
| return eval_expr(action->expr, action_data); |
| } |
| |
| |
| /* |
| * Read action file, passing each line to parse_action() for |
| * parsing. |
| * |
| * One action per line, of the form |
| * action(arg1,arg2)@expr(arg1,arg2).... |
| * |
| * Actions can be split across multiple lines using "\". |
| * |
| * Blank lines and comment lines indicated by # are supported. |
| */ |
| int parse_action_true(char *s) |
| { |
| return parse_action(s, ACTION_LOG_TRUE); |
| } |
| |
| |
| int parse_action_false(char *s) |
| { |
| return parse_action(s, ACTION_LOG_FALSE); |
| } |
| |
| |
| int parse_action_verbose(char *s) |
| { |
| return parse_action(s, ACTION_LOG_VERBOSE); |
| } |
| |
| |
| int parse_action_nonverbose(char *s) |
| { |
| return parse_action(s, ACTION_LOG_NONE); |
| } |
| |
| |
| int read_action_file(char *filename, int verbose) |
| { |
| switch(verbose) { |
| case ACTION_LOG_TRUE: |
| return read_file(filename, "action", parse_action_true); |
| case ACTION_LOG_FALSE: |
| return read_file(filename, "action", parse_action_false); |
| case ACTION_LOG_VERBOSE: |
| return read_file(filename, "action", parse_action_verbose); |
| default: |
| return read_file(filename, "action", parse_action_nonverbose); |
| } |
| } |
| |
| |
| /* |
| * helper to evaluate whether action/test acts on this file type |
| */ |
| static int file_type_match(int st_mode, int type) |
| { |
| switch(type) { |
| case ACTION_DIR: |
| return S_ISDIR(st_mode); |
| case ACTION_REG: |
| return S_ISREG(st_mode); |
| case ACTION_ALL: |
| return S_ISREG(st_mode) || S_ISDIR(st_mode) || |
| S_ISCHR(st_mode) || S_ISBLK(st_mode) || |
| S_ISFIFO(st_mode) || S_ISSOCK(st_mode); |
| case ACTION_LNK: |
| return S_ISLNK(st_mode); |
| case ACTION_ALL_LNK: |
| default: |
| return 1; |
| } |
| } |
| |
| |
| /* |
| * General action evaluation code |
| */ |
| int actions() |
| { |
| return other_count; |
| } |
| |
| |
| void eval_actions(struct dir_info *root, struct dir_ent *dir_ent) |
| { |
| int i, match; |
| struct action_data action_data; |
| int st_mode = dir_ent->inode->buf.st_mode; |
| |
| action_data.name = dir_ent->name; |
| action_data.pathname = strdup(pathname(dir_ent)); |
| action_data.subpath = strdup(subpathname(dir_ent)); |
| action_data.buf = &dir_ent->inode->buf; |
| action_data.depth = dir_ent->our_dir->depth; |
| action_data.dir_ent = dir_ent; |
| action_data.root = root; |
| |
| for (i = 0; i < other_count; i++) { |
| struct action *action = &other_spec[i]; |
| |
| if (!file_type_match(st_mode, action->action->file_types)) |
| /* action does not operate on this file type */ |
| continue; |
| |
| match = eval_expr_top(action, &action_data); |
| |
| if (match) |
| action->action->run_action(action, dir_ent); |
| } |
| |
| free(action_data.pathname); |
| free(action_data.subpath); |
| } |
| |
| |
| /* |
| * Fragment specific action code |
| */ |
| void *eval_frag_actions(struct dir_info *root, struct dir_ent *dir_ent) |
| { |
| int i, match; |
| struct action_data action_data; |
| |
| action_data.name = dir_ent->name; |
| action_data.pathname = strdup(pathname(dir_ent)); |
| action_data.subpath = strdup(subpathname(dir_ent)); |
| action_data.buf = &dir_ent->inode->buf; |
| action_data.depth = dir_ent->our_dir->depth; |
| action_data.dir_ent = dir_ent; |
| action_data.root = root; |
| |
| for (i = 0; i < fragment_count; i++) { |
| match = eval_expr_top(&fragment_spec[i], &action_data); |
| if (match) { |
| free(action_data.pathname); |
| free(action_data.subpath); |
| return &fragment_spec[i].data; |
| } |
| } |
| |
| free(action_data.pathname); |
| free(action_data.subpath); |
| return &def_fragment; |
| } |
| |
| |
| void *get_frag_action(void *fragment) |
| { |
| struct action *spec_list_end = &fragment_spec[fragment_count]; |
| struct action *action; |
| |
| if (fragment == NULL) |
| return &def_fragment; |
| |
| if (fragment_count == 0) |
| return NULL; |
| |
| if (fragment == &def_fragment) |
| action = &fragment_spec[0] - 1; |
| else |
| action = fragment - offsetof(struct action, data); |
| |
| if (++action == spec_list_end) |
| return NULL; |
| |
| return &action->data; |
| } |
| |
| |
| /* |
| * Exclude specific action code |
| */ |
| int exclude_actions() |
| { |
| return exclude_count; |
| } |
| |
| |
| int eval_exclude_actions(char *name, char *pathname, char *subpath, |
| struct stat *buf, int depth, struct dir_ent *dir_ent) |
| { |
| int i, match = 0; |
| struct action_data action_data; |
| |
| action_data.name = name; |
| action_data.pathname = pathname; |
| action_data.subpath = subpath; |
| action_data.buf = buf; |
| action_data.depth = depth; |
| action_data.dir_ent = dir_ent; |
| |
| for (i = 0; i < exclude_count && !match; i++) |
| match = eval_expr_top(&exclude_spec[i], &action_data); |
| |
| return match; |
| } |
| |
| |
| /* |
| * Fragment specific action code |
| */ |
| static void frag_action(struct action *action, struct dir_ent *dir_ent) |
| { |
| struct inode_info *inode = dir_ent->inode; |
| |
| inode->no_fragments = 0; |
| } |
| |
| static void no_frag_action(struct action *action, struct dir_ent *dir_ent) |
| { |
| struct inode_info *inode = dir_ent->inode; |
| |
| inode->no_fragments = 1; |
| } |
| |
| static void always_frag_action(struct action *action, struct dir_ent *dir_ent) |
| { |
| struct inode_info *inode = dir_ent->inode; |
| |
| inode->always_use_fragments = 1; |
| } |
| |
| static void no_always_frag_action(struct action *action, struct dir_ent *dir_ent) |
| { |
| struct inode_info *inode = dir_ent->inode; |
| |
| inode->always_use_fragments = 0; |
| } |
| |
| |
| /* |
| * Compression specific action code |
| */ |
| static void comp_action(struct action *action, struct dir_ent *dir_ent) |
| { |
| struct inode_info *inode = dir_ent->inode; |
| |
| inode->noD = inode->noF = 0; |
| } |
| |
| static void uncomp_action(struct action *action, struct dir_ent *dir_ent) |
| { |
| struct inode_info *inode = dir_ent->inode; |
| |
| inode->noD = inode->noF = 1; |
| } |
| |
| |
| /* |
| * Uid/gid specific action code |
| */ |
| static long long parse_uid(char *arg) { |
| char *b; |
| long long uid = strtoll(arg, &b, 10); |
| |
| if (*b == '\0') { |
| if (uid < 0 || uid >= (1LL << 32)) { |
| SYNTAX_ERROR("Uid out of range\n"); |
| return -1; |
| } |
| } else { |
| struct passwd *passwd = getpwnam(arg); |
| |
| if (passwd) |
| uid = passwd->pw_uid; |
| else { |
| SYNTAX_ERROR("Invalid uid or unknown user\n"); |
| return -1; |
| } |
| } |
| |
| return uid; |
| } |
| |
| |
| static long long parse_gid(char *arg) { |
| char *b; |
| long long gid = strtoll(arg, &b, 10); |
| |
| if (*b == '\0') { |
| if (gid < 0 || gid >= (1LL << 32)) { |
| SYNTAX_ERROR("Gid out of range\n"); |
| return -1; |
| } |
| } else { |
| struct group *group = getgrnam(arg); |
| |
| if (group) |
| gid = group->gr_gid; |
| else { |
| SYNTAX_ERROR("Invalid gid or unknown group\n"); |
| return -1; |
| } |
| } |
| |
| return gid; |
| } |
| |
| |
| static int parse_uid_args(struct action_entry *action, int args, char **argv, |
| void **data) |
| { |
| long long uid; |
| struct uid_info *uid_info; |
| |
| uid = parse_uid(argv[0]); |
| if (uid == -1) |
| return 0; |
| |
| uid_info = malloc(sizeof(struct uid_info)); |
| if (uid_info == NULL) |
| MEM_ERROR(); |
| |
| uid_info->uid = uid; |
| *data = uid_info; |
| |
| return 1; |
| } |
| |
| |
| static int parse_gid_args(struct action_entry *action, int args, char **argv, |
| void **data) |
| { |
| long long gid; |
| struct gid_info *gid_info; |
| |
| gid = parse_gid(argv[0]); |
| if (gid == -1) |
| return 0; |
| |
| gid_info = malloc(sizeof(struct gid_info)); |
| if (gid_info == NULL) |
| MEM_ERROR(); |
| |
| gid_info->gid = gid; |
| *data = gid_info; |
| |
| return 1; |
| } |
| |
| |
| static int parse_guid_args(struct action_entry *action, int args, char **argv, |
| void **data) |
| { |
| long long uid, gid; |
| struct guid_info *guid_info; |
| |
| uid = parse_uid(argv[0]); |
| if (uid == -1) |
| return 0; |
| |
| gid = parse_gid(argv[1]); |
| if (gid == -1) |
| return 0; |
| |
| guid_info = malloc(sizeof(struct guid_info)); |
| if (guid_info == NULL) |
| MEM_ERROR(); |
| |
| guid_info->uid = uid; |
| guid_info->gid = gid; |
| *data = guid_info; |
| |
| return 1; |
| } |
| |
| |
| static void uid_action(struct action *action, struct dir_ent *dir_ent) |
| { |
| struct inode_info *inode = dir_ent->inode; |
| struct uid_info *uid_info = action->data; |
| |
| inode->buf.st_uid = uid_info->uid; |
| } |
| |
| static void gid_action(struct action *action, struct dir_ent *dir_ent) |
| { |
| struct inode_info *inode = dir_ent->inode; |
| struct gid_info *gid_info = action->data; |
| |
| inode->buf.st_gid = gid_info->gid; |
| } |
| |
| static void guid_action(struct action *action, struct dir_ent *dir_ent) |
| { |
| struct inode_info *inode = dir_ent->inode; |
| struct guid_info *guid_info = action->data; |
| |
| inode->buf.st_uid = guid_info->uid; |
| inode->buf.st_gid = guid_info->gid; |
| |
| } |
| |
| |
| /* |
| * Mode specific action code |
| */ |
| static int parse_octal_mode_args(int args, char **argv, |
| void **data) |
| { |
| int n, bytes; |
| unsigned int mode; |
| struct mode_data *mode_data; |
| |
| /* octal mode number? */ |
| n = sscanf(argv[0], "%o%n", &mode, &bytes); |
| if (n == 0) |
| return -1; /* not an octal number arg */ |
| |
| |
| /* check there's no trailing junk */ |
| if (argv[0][bytes] != '\0') { |
| SYNTAX_ERROR("Unexpected trailing bytes after octal " |
| "mode number\n"); |
| return 0; /* bad octal number arg */ |
| } |
| |
| /* check there's only one argument */ |
| if (args > 1) { |
| SYNTAX_ERROR("Octal mode number is first argument, " |
| "expected one argument, got %d\n", args); |
| return 0; /* bad octal number arg */ |
| } |
| |
| /* check mode is within range */ |
| if (mode > 07777) { |
| SYNTAX_ERROR("Octal mode %o is out of range\n", mode); |
| return 0; /* bad octal number arg */ |
| } |
| |
| mode_data = malloc(sizeof(struct mode_data)); |
| if (mode_data == NULL) |
| MEM_ERROR(); |
| |
| mode_data->operation = ACTION_MODE_OCT; |
| mode_data->mode = mode; |
| mode_data->next = NULL; |
| *data = mode_data; |
| |
| return 1; |
| } |
| |
| |
| /* |
| * Parse symbolic mode of format [ugoa]*[[+-=]PERMS]+ |
| * PERMS = [rwxXst]+ or [ugo] |
| */ |
| static int parse_sym_mode_arg(char *arg, struct mode_data **head, |
| struct mode_data **cur) |
| { |
| struct mode_data *mode_data; |
| int mode; |
| int mask = 0; |
| int op; |
| char X; |
| |
| if (arg[0] != 'u' && arg[0] != 'g' && arg[0] != 'o' && arg[0] != 'a') { |
| /* no ownership specifiers, default to a */ |
| mask = 0777; |
| goto parse_operation; |
| } |
| |
| /* parse ownership specifiers */ |
| while(1) { |
| switch(*arg) { |
| case 'u': |
| mask |= 04700; |
| break; |
| case 'g': |
| mask |= 02070; |
| break; |
| case 'o': |
| mask |= 01007; |
| break; |
| case 'a': |
| mask = 07777; |
| break; |
| default: |
| goto parse_operation; |
| } |
| arg ++; |
| } |
| |
| parse_operation: |
| /* trap a symbolic mode with just an ownership specification */ |
| if(*arg == '\0') { |
| SYNTAX_ERROR("Expected one of '+', '-' or '=', got EOF\n"); |
| goto failed; |
| } |
| |
| while(*arg != '\0') { |
| mode = 0; |
| X = 0; |
| |
| switch(*arg) { |
| case '+': |
| op = ACTION_MODE_ADD; |
| break; |
| case '-': |
| op = ACTION_MODE_REM; |
| break; |
| case '=': |
| op = ACTION_MODE_SET; |
| break; |
| default: |
| SYNTAX_ERROR("Expected one of '+', '-' or '=', got " |
| "'%c'\n", *arg); |
| goto failed; |
| } |
| |
| arg ++; |
| |
| /* Parse PERMS */ |
| if (*arg == 'u' || *arg == 'g' || *arg == 'o') { |
| /* PERMS = [ugo] */ |
| mode = - *arg; |
| arg ++; |
| } else { |
| /* PERMS = [rwxXst]* */ |
| while(1) { |
| switch(*arg) { |
| case 'r': |
| mode |= 0444; |
| break; |
| case 'w': |
| mode |= 0222; |
| break; |
| case 'x': |
| mode |= 0111; |
| break; |
| case 's': |
| mode |= 06000; |
| break; |
| case 't': |
| mode |= 01000; |
| break; |
| case 'X': |
| X = 1; |
| break; |
| case '+': |
| case '-': |
| case '=': |
| case '\0': |
| mode &= mask; |
| goto perms_parsed; |
| default: |
| SYNTAX_ERROR("Unrecognised permission " |
| "'%c'\n", *arg); |
| goto failed; |
| } |
| |
| arg ++; |
| } |
| } |
| |
| perms_parsed: |
| mode_data = malloc(sizeof(*mode_data)); |
| if (mode_data == NULL) |
| MEM_ERROR(); |
| |
| mode_data->operation = op; |
| mode_data->mode = mode; |
| mode_data->mask = mask; |
| mode_data->X = X; |
| mode_data->next = NULL; |
| |
| if (*cur) { |
| (*cur)->next = mode_data; |
| *cur = mode_data; |
| } else |
| *head = *cur = mode_data; |
| } |
| |
| return 1; |
| |
| failed: |
| return 0; |
| } |
| |
| |
| static int parse_sym_mode_args(struct action_entry *action, int args, |
| char **argv, void **data) |
| { |
| int i, res = 1; |
| struct mode_data *head = NULL, *cur = NULL; |
| |
| for (i = 0; i < args && res; i++) |
| res = parse_sym_mode_arg(argv[i], &head, &cur); |
| |
| *data = head; |
| |
| return res; |
| } |
| |
| |
| static int parse_mode_args(struct action_entry *action, int args, |
| char **argv, void **data) |
| { |
| int res; |
| |
| if (args == 0) { |
| SYNTAX_ERROR("Mode action expects one or more arguments\n"); |
| return 0; |
| } |
| |
| res = parse_octal_mode_args(args, argv, data); |
| if(res >= 0) |
| /* Got an octal mode argument */ |
| return res; |
| else /* not an octal mode argument */ |
| return parse_sym_mode_args(action, args, argv, data); |
| } |
| |
| |
| static int mode_execute(struct mode_data *mode_data, int st_mode) |
| { |
| int mode = 0; |
| |
| for (;mode_data; mode_data = mode_data->next) { |
| if (mode_data->mode < 0) { |
| /* 'u', 'g' or 'o' */ |
| switch(-mode_data->mode) { |
| case 'u': |
| mode = (st_mode >> 6) & 07; |
| break; |
| case 'g': |
| mode = (st_mode >> 3) & 07; |
| break; |
| case 'o': |
| mode = st_mode & 07; |
| break; |
| } |
| mode = ((mode << 6) | (mode << 3) | mode) & |
| mode_data->mask; |
| } else if (mode_data->X && |
| ((st_mode & S_IFMT) == S_IFDIR || |
| (st_mode & 0111))) |
| /* X permission, only takes effect if inode is a |
| * directory or x is set for some owner */ |
| mode = mode_data->mode | (0111 & mode_data->mask); |
| else |
| mode = mode_data->mode; |
| |
| switch(mode_data->operation) { |
| case ACTION_MODE_OCT: |
| st_mode = (st_mode & S_IFMT) | mode; |
| break; |
| case ACTION_MODE_SET: |
| st_mode = (st_mode & ~mode_data->mask) | mode; |
| break; |
| case ACTION_MODE_ADD: |
| st_mode |= mode; |
| break; |
| case ACTION_MODE_REM: |
| st_mode &= ~mode; |
| } |
| } |
| |
| return st_mode; |
| } |
| |
| |
| static void mode_action(struct action *action, struct dir_ent *dir_ent) |
| { |
| dir_ent->inode->buf.st_mode = mode_execute(action->data, |
| dir_ent->inode->buf.st_mode); |
| } |
| |
| |
| /* |
| * Empty specific action code |
| */ |
| int empty_actions() |
| { |
| return empty_count; |
| } |
| |
| |
| static int parse_empty_args(struct action_entry *action, int args, |
| char **argv, void **data) |
| { |
| struct empty_data *empty_data; |
| int val; |
| |
| if (args >= 2) { |
| SYNTAX_ERROR("Empty action expects zero or one argument\n"); |
| return 0; |
| } |
| |
| if (args == 0 || strcmp(argv[0], "all") == 0) |
| val = EMPTY_ALL; |
| else if (strcmp(argv[0], "source") == 0) |
| val = EMPTY_SOURCE; |
| else if (strcmp(argv[0], "excluded") == 0) |
| val = EMPTY_EXCLUDED; |
| else { |
| SYNTAX_ERROR("Empty action expects zero arguments, or one" |
| "argument containing \"all\", \"source\", or \"excluded\"" |
| "\n"); |
| return 0; |
| } |
| |
| empty_data = malloc(sizeof(*empty_data)); |
| if (empty_data == NULL) |
| MEM_ERROR(); |
| |
| empty_data->val = val; |
| *data = empty_data; |
| |
| return 1; |
| } |
| |
| |
| int eval_empty_actions(struct dir_info *root, struct dir_ent *dir_ent) |
| { |
| int i, match = 0; |
| struct action_data action_data; |
| struct empty_data *data; |
| struct dir_info *dir = dir_ent->dir; |
| |
| /* |
| * Empty action only works on empty directories |
| */ |
| if (dir->count != 0) |
| return 0; |
| |
| action_data.name = dir_ent->name; |
| action_data.pathname = strdup(pathname(dir_ent)); |
| action_data.subpath = strdup(subpathname(dir_ent)); |
| action_data.buf = &dir_ent->inode->buf; |
| action_data.depth = dir_ent->our_dir->depth; |
| action_data.dir_ent = dir_ent; |
| action_data.root = root; |
| |
| for (i = 0; i < empty_count && !match; i++) { |
| data = empty_spec[i].data; |
| |
| /* |
| * determine the cause of the empty directory and evaluate |
| * the empty action specified. Three empty actions: |
| * - EMPTY_SOURCE: empty action triggers only if the directory |
| * was originally empty, i.e directories that are empty |
| * only due to excluding are ignored. |
| * - EMPTY_EXCLUDED: empty action triggers only if the directory |
| * is empty because of excluding, i.e. directories that |
| * were originally empty are ignored. |
| * - EMPTY_ALL (the default): empty action triggers if the |
| * directory is empty, irrespective of the reason, i.e. |
| * the directory could have been originally empty or could |
| * be empty due to excluding. |
| */ |
| if ((data->val == EMPTY_EXCLUDED && !dir->excluded) || |
| (data->val == EMPTY_SOURCE && dir->excluded)) |
| continue; |
| |
| match = eval_expr_top(&empty_spec[i], &action_data); |
| } |
| |
| free(action_data.pathname); |
| free(action_data.subpath); |
| |
| return match; |
| } |
| |
| |
| /* |
| * Move specific action code |
| */ |
| static struct move_ent *move_list = NULL; |
| |
| |
| int move_actions() |
| { |
| return move_count; |
| } |
| |
| |
| static char *move_pathname(struct move_ent *move) |
| { |
| struct dir_info *dest; |
| char *name, *pathname; |
| int res; |
| |
| dest = (move->ops & ACTION_MOVE_MOVE) ? |
| move->dest : move->dir_ent->our_dir; |
| name = (move->ops & ACTION_MOVE_RENAME) ? |
| move->name : move->dir_ent->name; |
| |
| if(dest->subpath[0] != '\0') |
| res = asprintf(&pathname, "%s/%s", dest->subpath, name); |
| else |
| res = asprintf(&pathname, "/%s", name); |
| |
| if(res == -1) |
| BAD_ERROR("asprintf failed in move_pathname\n"); |
| |
| return pathname; |
| } |
| |
| |
| static char *get_comp(char **pathname) |
| { |
| char *path = *pathname, *start; |
| |
| while(*path == '/') |
| path ++; |
| |
| if(*path == '\0') |
| return NULL; |
| |
| start = path; |
| while(*path != '/' && *path != '\0') |
| path ++; |
| |
| *pathname = path; |
| return strndup(start, path - start); |
| } |
| |
| |
| static struct dir_ent *lookup_comp(char *comp, struct dir_info *dest) |
| { |
| struct dir_ent *dir_ent; |
| |
| for(dir_ent = dest->list; dir_ent; dir_ent = dir_ent->next) |
| if(strcmp(comp, dir_ent->name) == 0) |
| break; |
| |
| return dir_ent; |
| } |
| |
| |
| void eval_move(struct action_data *action_data, struct move_ent *move, |
| struct dir_info *root, struct dir_ent *dir_ent, char *pathname) |
| { |
| struct dir_info *dest, *source = dir_ent->our_dir; |
| struct dir_ent *comp_ent; |
| char *comp, *path = pathname; |
| |
| /* |
| * Walk pathname to get the destination directory |
| * |
| * Like the mv command, if the last component exists and it |
| * is a directory, then move the file into that directory, |
| * otherwise, move the file into parent directory of the last |
| * component and rename to the last component. |
| */ |
| if (pathname[0] == '/') |
| /* absolute pathname, walk from root directory */ |
| dest = root; |
| else |
| /* relative pathname, walk from current directory */ |
| dest = source; |
| |
| for(comp = get_comp(&pathname); comp; free(comp), |
| comp = get_comp(&pathname)) { |
| |
| if (strcmp(comp, ".") == 0) |
| continue; |
| |
| if (strcmp(comp, "..") == 0) { |
| /* if we're in the root directory then ignore */ |
| if(dest->depth > 1) |
| dest = dest->dir_ent->our_dir; |
| continue; |
| } |
| |
| /* |
| * Look up comp in current directory, if it exists and it is a |
| * directory continue walking the pathname, otherwise exit, |
| * we've walked as far as we can go, normally this is because |
| * we've arrived at the leaf component which we are going to |
| * rename source to |
| */ |
| comp_ent = lookup_comp(comp, dest); |
| if (comp_ent == NULL || (comp_ent->inode->buf.st_mode & S_IFMT) |
| != S_IFDIR) |
| break; |
| |
| dest = comp_ent->dir; |
| } |
| |
| if(comp) { |
| /* Leaf component? If so we're renaming to this */ |
| char *remainder = get_comp(&pathname); |
| free(remainder); |
| |
| if(remainder) { |
| /* |
| * trying to move source to a subdirectory of |
| * comp, but comp either doesn't exist, or it isn't |
| * a directory, which is impossible |
| */ |
| if (comp_ent == NULL) |
| ERROR("Move action: cannot move %s to %s, no " |
| "such directory %s\n", |
| action_data->subpath, path, comp); |
| else |
| ERROR("Move action: cannot move %s to %s, %s " |
| "is not a directory\n", |
| action_data->subpath, path, comp); |
| free(comp); |
| return; |
| } |
| |
| /* |
| * Multiple move actions triggering on one file can be merged |
| * if one is a RENAME and the other is a MOVE. Multiple RENAMEs |
| * can only merge if they're doing the same thing |
| */ |
| if(move->ops & ACTION_MOVE_RENAME) { |
| if(strcmp(comp, move->name) != 0) { |
| char *conf_path = move_pathname(move); |
| ERROR("Move action: Cannot move %s to %s, " |
| "conflicting move, already moving " |
| "to %s via another move action!\n", |
| action_data->subpath, path, conf_path); |
| free(conf_path); |
| free(comp); |
| return; |
| } |
| free(comp); |
| } else { |
| move->name = comp; |
| move->ops |= ACTION_MOVE_RENAME; |
| } |
| } |
| |
| if(dest != source) { |
| /* |
| * Multiple move actions triggering on one file can be merged |
| * if one is a RENAME and the other is a MOVE. Multiple MOVEs |
| * can only merge if they're doing the same thing |
| */ |
| if(move->ops & ACTION_MOVE_MOVE) { |
| if(dest != move->dest) { |
| char *conf_path = move_pathname(move); |
| ERROR("Move action: Cannot move %s to %s, " |
| "conflicting move, already moving " |
| "to %s via another move action!\n", |
| action_data->subpath, path, conf_path); |
| free(conf_path); |
| return; |
| } |
| } else { |
| move->dest = dest; |
| move->ops |= ACTION_MOVE_MOVE; |
| } |
| } |
| } |
| |
| |
| static int subdirectory(struct dir_info *source, struct dir_info *dest) |
| { |
| if(source == NULL) |
| return 0; |
| |
| return strlen(source->subpath) <= strlen(dest->subpath) && |
| (dest->subpath[strlen(source->subpath)] == '/' || |
| dest->subpath[strlen(source->subpath)] == '\0') && |
| strncmp(source->subpath, dest->subpath, |
| strlen(source->subpath)) == 0; |
| } |
| |
| |
| void eval_move_actions(struct dir_info *root, struct dir_ent *dir_ent) |
| { |
| int i; |
| struct action_data action_data; |
| struct move_ent *move = NULL; |
| |
| action_data.name = dir_ent->name; |
| action_data.pathname = strdup(pathname(dir_ent)); |
| action_data.subpath = strdup(subpathname(dir_ent)); |
| action_data.buf = &dir_ent->inode->buf; |
| action_data.depth = dir_ent->our_dir->depth; |
| action_data.dir_ent = dir_ent; |
| action_data.root = root; |
| |
| /* |
| * Evaluate each move action against the current file. For any |
| * move actions that match don't actually perform the move now, but, |
| * store it, and execute all the stored move actions together once the |
| * directory scan is complete. This is done to ensure each separate |
| * move action does not nondeterministically interfere with other move |
| * actions. Each move action is considered to act independently, and |
| * each move action sees the directory tree in the same state. |
| */ |
| for (i = 0; i < move_count; i++) { |
| struct action *action = &move_spec[i]; |
| int match = eval_expr_top(action, &action_data); |
| |
| if(match) { |
| if(move == NULL) { |
| move = malloc(sizeof(*move)); |
| if(move == NULL) |
| MEM_ERROR(); |
| |
| move->ops = 0; |
| move->dir_ent = dir_ent; |
| } |
| eval_move(&action_data, move, root, dir_ent, |
| action->argv[0]); |
| } |
| } |
| |
| if(move) { |
| struct dir_ent *comp_ent; |
| struct dir_info *dest; |
| char *name; |
| |
| /* |
| * Move contains the result of all triggered move actions. |
| * Check the destination doesn't already exist |
| */ |
| if(move->ops == 0) { |
| free(move); |
| goto finish; |
| } |
| |
| dest = (move->ops & ACTION_MOVE_MOVE) ? |
| move->dest : dir_ent->our_dir; |
| name = (move->ops & ACTION_MOVE_RENAME) ? |
| move->name : dir_ent->name; |
| comp_ent = lookup_comp(name, dest); |
| if(comp_ent) { |
| char *conf_path = move_pathname(move); |
| ERROR("Move action: Cannot move %s to %s, " |
| "destination already exists\n", |
| action_data.subpath, conf_path); |
| free(conf_path); |
| free(move); |
| goto finish; |
| } |
| |
| /* |
| * If we're moving a directory, check we're not moving it to a |
| * subdirectory of itself |
| */ |
| if(subdirectory(dir_ent->dir, dest)) { |
| char *conf_path = move_pathname(move); |
| ERROR("Move action: Cannot move %s to %s, this is a " |
| "subdirectory of itself\n", |
| action_data.subpath, conf_path); |
| free(conf_path); |
| free(move); |
| goto finish; |
| } |
| move->next = move_list; |
| move_list = move; |
| } |
| |
| finish: |
| free(action_data.pathname); |
| free(action_data.subpath); |
| } |
| |
| |
| static void move_dir(struct dir_ent *dir_ent) |
| { |
| struct dir_info *dir = dir_ent->dir; |
| struct dir_ent *comp_ent; |
| |
| /* update our directory's subpath name */ |
| free(dir->subpath); |
| dir->subpath = strdup(subpathname(dir_ent)); |
| |
| /* recursively update the subpaths of any sub-directories */ |
| for(comp_ent = dir->list; comp_ent; comp_ent = comp_ent->next) |
| if(comp_ent->dir) |
| move_dir(comp_ent); |
| } |
| |
| |
| static void move_file(struct move_ent *move_ent) |
| { |
| struct dir_ent *dir_ent = move_ent->dir_ent; |
| |
| if(move_ent->ops & ACTION_MOVE_MOVE) { |
| struct dir_ent *comp_ent, *prev = NULL; |
| struct dir_info *source = dir_ent->our_dir, |
| *dest = move_ent->dest; |
| char *filename = pathname(dir_ent); |
| |
| /* |
| * If we're moving a directory, check we're not moving it to a |
| * subdirectory of itself |
| */ |
| if(subdirectory(dir_ent->dir, dest)) { |
| char *conf_path = move_pathname(move_ent); |
| ERROR("Move action: Cannot move %s to %s, this is a " |
| "subdirectory of itself\n", |
| subpathname(dir_ent), conf_path); |
| free(conf_path); |
| return; |
| } |
| |
| /* Remove the file from source directory */ |
| for(comp_ent = source->list; comp_ent != dir_ent; |
| prev = comp_ent, comp_ent = comp_ent->next); |
| |
| if(prev) |
| prev->next = comp_ent->next; |
| else |
| source->list = comp_ent->next; |
| |
| source->count --; |
| if((comp_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR) |
| source->directory_count --; |
| |
| /* Add the file to dest directory */ |
| comp_ent->next = dest->list; |
| dest->list = comp_ent; |
| comp_ent->our_dir = dest; |
| |
| dest->count ++; |
| if((comp_ent->inode->buf.st_mode & S_IFMT) == S_IFDIR) |
| dest->directory_count ++; |
| |
| /* |
| * We've moved the file, and so we can't now use the |
| * parent directory's pathname to calculate the pathname |
| */ |
| if(dir_ent->nonstandard_pathname == NULL) { |
| dir_ent->nonstandard_pathname = strdup(filename); |
| if(dir_ent->source_name) { |
| free(dir_ent->source_name); |
| dir_ent->source_name = NULL; |
| } |
| } |
| } |
| |
| if(move_ent->ops & ACTION_MOVE_RENAME) { |
| /* |
| * If we're using name in conjunction with the parent |
| * directory's pathname to calculate the pathname, we need |
| * to use source_name to override. Otherwise it's already being |
| * over-ridden |
| */ |
| if(dir_ent->nonstandard_pathname == NULL && |
| dir_ent->source_name == NULL) |
| dir_ent->source_name = dir_ent->name; |
| else |
| free(dir_ent->name); |
| |
| dir_ent->name = move_ent->name; |
| } |
| |
| if(dir_ent->dir) |
| /* |
| * dir_ent is a directory, and we have to recursively fix-up |
| * its subpath, and the subpaths of all of its sub-directories |
| */ |
| move_dir(dir_ent); |
| } |
| |
| |
| void do_move_actions() |
| { |
| while(move_list) { |
| struct move_ent *temp = move_list; |
| struct dir_info *dest = (move_list->ops & ACTION_MOVE_MOVE) ? |
| move_list->dest : move_list->dir_ent->our_dir; |
| char *name = (move_list->ops & ACTION_MOVE_RENAME) ? |
| move_list->name : move_list->dir_ent->name; |
| struct dir_ent *comp_ent = lookup_comp(name, dest); |
| if(comp_ent) { |
| char *conf_path = move_pathname(move_list); |
| ERROR("Move action: Cannot move %s to %s, " |
| "destination already exists\n", |
| subpathname(move_list->dir_ent), conf_path); |
| free(conf_path); |
| } else |
| move_file(move_list); |
| |
| move_list = move_list->next; |
| free(temp); |
| } |
| } |
| |
| |
| /* |
| * Prune specific action code |
| */ |
| int prune_actions() |
| { |
| return prune_count; |
| } |
| |
| |
| int eval_prune_actions(struct dir_info *root, struct dir_ent *dir_ent) |
| { |
| int i, match = 0; |
| struct action_data action_data; |
| |
| action_data.name = dir_ent->name; |
| action_data.pathname = strdup(pathname(dir_ent)); |
| action_data.subpath = strdup(subpathname(dir_ent)); |
| action_data.buf = &dir_ent->inode->buf; |
| action_data.depth = dir_ent->our_dir->depth; |
| action_data.dir_ent = dir_ent; |
| action_data.root = root; |
| |
| for (i = 0; i < prune_count && !match; i++) |
| match = eval_expr_top(&prune_spec[i], &action_data); |
| |
| free(action_data.pathname); |
| free(action_data.subpath); |
| |
| return match; |
| } |
| |
| |
| /* |
| * Noop specific action code |
| */ |
| static void noop_action(struct action *action, struct dir_ent *dir_ent) |
| { |
| } |
| |
| |
| /* |
| * General test evaluation code |
| */ |
| |
| /* |
| * A number can be of the form [range]number[size] |
| * [range] is either: |
| * '<' or '-', match on less than number |
| * '>' or '+', match on greater than number |
| * '' (nothing), match on exactly number |
| * [size] is either: |
| * '' (nothing), number |
| * 'k' or 'K', number * 2^10 |
| * 'm' or 'M', number * 2^20 |
| * 'g' or 'G', number * 2^30 |
| */ |
| static int parse_number(char *start, long long *size, int *range, char **error) |
| { |
| char *end; |
| long long number; |
| |
| if (*start == '>' || *start == '+') { |
| *range = NUM_GREATER; |
| start ++; |
| } else if (*start == '<' || *start == '-') { |
| *range = NUM_LESS; |
| start ++; |
| } else |
| *range = NUM_EQ; |
| |
| errno = 0; /* To enable failure after call to be determined */ |
| number = strtoll(start, &end, 10); |
| |
| if((errno == ERANGE && (number == LLONG_MAX || number == LLONG_MIN)) |
| || (errno != 0 && number == 0)) { |
| /* long long underflow or overflow in conversion, or other |
| * conversion error. |
| * Note: we don't check for LLONG_MIN and LLONG_MAX only |
| * because strtoll can validly return that if the |
| * user used these values |
| */ |
| *error = "Long long underflow, overflow or other conversion " |
| "error"; |
| return 0; |
| } |
| |
| if (end == start) { |
| /* Couldn't read any number */ |
| *error = "Number expected"; |
| return 0; |
| } |
| |
| switch (end[0]) { |
| case 'g': |
| case 'G': |
| number *= 1024; |
| case 'm': |
| case 'M': |
| number *= 1024; |
| case 'k': |
| case 'K': |
| number *= 1024; |
| |
| if (end[1] != '\0') { |
| *error = "Trailing junk after size specifier"; |
| return 0; |
| } |
| |
| break; |
| case '\0': |
| break; |
| default: |
| *error = "Trailing junk after number"; |
| return 0; |
| } |
| |
| *size = number; |
| |
| return 1; |
| } |
| |
| |
| static int parse_number_arg(struct test_entry *test, struct atom *atom) |
| { |
| struct test_number_arg *number; |
| long long size; |
| int range; |
| char *error; |
| int res = parse_number(atom->argv[0], &size, &range, &error); |
| |
| if (res == 0) { |
| TEST_SYNTAX_ERROR(test, 0, "%s\n", error); |
| return 0; |
| } |
| |
| number = malloc(sizeof(*number)); |
| if (number == NULL) |
| MEM_ERROR(); |
| |
| number->range = range; |
| number->size = size; |
| |
| atom->data = number; |
| |
| return 1; |
| } |
| |
| |
| static int parse_range_args(struct test_entry *test, struct atom *atom) |
| { |
| struct test_range_args *range; |
| long long start, end; |
| int type; |
| int res; |
| char *error; |
| |
| res = parse_number(atom->argv[0], &start, &type, &error); |
| if (res == 0) { |
| TEST_SYNTAX_ERROR(test, 0, "%s\n", error); |
| return 0; |
| } |
| |
| if (type != NUM_EQ) { |
| TEST_SYNTAX_ERROR(test, 0, "Range specifier (<, >, -, +) not " |
| "expected\n"); |
| return 0; |
| } |
| |
| res = parse_number(atom->argv[1], &end, &type, &error); |
| if (res == 0) { |
| TEST_SYNTAX_ERROR(test, 1, "%s\n", error); |
| return 0; |
| } |
| |
| if (type != NUM_EQ) { |
| TEST_SYNTAX_ERROR(test, 1, "Range specifier (<, >, -, +) not " |
| "expected\n"); |
| return 0; |
| } |
| |
| range = malloc(sizeof(*range)); |
| if (range == NULL) |
| MEM_ERROR(); |
| |
| range->start = start; |
| range->end = end; |
| |
| atom->data = range; |
| |
| return 1; |
| } |
| |
| |
| /* |
| * Generic test code macro |
| */ |
| #define TEST_FN(NAME, MATCH, CODE) \ |
| static int NAME##_fn(struct atom *atom, struct action_data *action_data) \ |
| { \ |
| /* test operates on MATCH file types only */ \ |
| if (!file_type_match(action_data->buf->st_mode, MATCH)) \ |
| return 0; \ |
| \ |
| CODE \ |
| } |
| |
| /* |
| * Generic test code macro testing VAR for size (eq, less than, greater than) |
| */ |
| #define TEST_VAR_FN(NAME, MATCH, VAR) TEST_FN(NAME, MATCH, \ |
| { \ |
| int match = 0; \ |
| struct test_number_arg *number = atom->data; \ |
| \ |
| switch (number->range) { \ |
| case NUM_EQ: \ |
| match = VAR == number->size; \ |
| break; \ |
| case NUM_LESS: \ |
| match = VAR < number->size; \ |
| break; \ |
| case NUM_GREATER: \ |
| match = VAR > number->size; \ |
| break; \ |
| } \ |
| \ |
| return match; \ |
| }) |
| |
| |
| /* |
| * Generic test code macro testing VAR for range [x, y] (value between x and y |
| * inclusive). |
| */ |
| #define TEST_VAR_RANGE_FN(NAME, MATCH, VAR) TEST_FN(NAME##_range, MATCH, \ |
| { \ |
| struct test_range_args *range = atom->data; \ |
| \ |
| return range->start <= VAR && VAR <= range->end; \ |
| }) |
| |
| |
| /* |
| * Name, Pathname and Subpathname test specific code |
| */ |
| |
| /* |
| * Add a leading "/" if subpathname and pathname lacks it |
| */ |
| static int check_pathname(struct test_entry *test, struct atom *atom) |
| { |
| int res; |
| char *name; |
| |
| if(atom->argv[0][0] != '/') { |
| res = asprintf(&name, "/%s", atom->argv[0]); |
| if(res == -1) |
| BAD_ERROR("asprintf failed in check_pathname\n"); |
| |
| free(atom->argv[0]); |
| atom->argv[0] = name; |
| } |
| |
| return 1; |
| } |
| |
| |
| TEST_FN(name, ACTION_ALL_LNK, \ |
| return fnmatch(atom->argv[0], action_data->name, |
| FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 0;) |
| |
| TEST_FN(pathname, ACTION_ALL_LNK, \ |
| return fnmatch(atom->argv[0], action_data->subpath, |
| FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 0;) |
| |
| |
| static int count_components(char *path) |
| { |
| int count; |
| |
| for (count = 0; *path != '\0'; count ++) { |
| while (*path == '/') |
| path ++; |
| |
| while (*path != '\0' && *path != '/') |
| path ++; |
| } |
| |
| return count; |
| } |
| |
| |
| static char *get_start(char *s, int n) |
| { |
| int count; |
| char *path = s; |
| |
| for (count = 0; *path != '\0' && count < n; count ++) { |
| while (*path == '/') |
| path ++; |
| |
| while (*path != '\0' && *path != '/') |
| path ++; |
| } |
| |
| if (count == n) |
| *path = '\0'; |
| |
| return s; |
| } |
| |
| |
| static int subpathname_fn(struct atom *atom, struct action_data *action_data) |
| { |
| char *path = strdup(action_data->subpath); |
| int is_match = fnmatch(atom->argv[0], get_start(path, |
| count_components(atom->argv[0])), |
| FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 0; |
| free(path); |
| return is_match; |
| } |
| |
| /* |
| * Inode attribute test operations using generic |
| * TEST_VAR_FN(test name, file scope, attribute name) macro. |
| * This is for tests that do not need to be specially handled in any way. |
| * They just take a variable and compare it against a number. |
| */ |
| TEST_VAR_FN(filesize, ACTION_REG, action_data->buf->st_size) |
| |
| TEST_VAR_FN(dirsize, ACTION_DIR, action_data->buf->st_size) |
| |
| TEST_VAR_FN(size, ACTION_ALL_LNK, action_data->buf->st_size) |
| |
| TEST_VAR_FN(inode, ACTION_ALL_LNK, action_data->buf->st_ino) |
| |
| TEST_VAR_FN(nlink, ACTION_ALL_LNK, action_data->buf->st_nlink) |
| |
| TEST_VAR_FN(fileblocks, ACTION_REG, action_data->buf->st_blocks) |
| |
| TEST_VAR_FN(dirblocks, ACTION_DIR, action_data->buf->st_blocks) |
| |
| TEST_VAR_FN(blocks, ACTION_ALL_LNK, action_data->buf->st_blocks) |
| |
| TEST_VAR_FN(dircount, ACTION_DIR, action_data->dir_ent->dir->count) |
| |
| TEST_VAR_FN(depth, ACTION_ALL_LNK, action_data->depth) |
| |
| TEST_VAR_RANGE_FN(filesize, ACTION_REG, action_data->buf->st_size) |
| |
| TEST_VAR_RANGE_FN(dirsize, ACTION_DIR, action_data->buf->st_size) |
| |
| TEST_VAR_RANGE_FN(size, ACTION_ALL_LNK, action_data->buf->st_size) |
| |
| TEST_VAR_RANGE_FN(inode, ACTION_ALL_LNK, action_data->buf->st_ino) |
| |
| TEST_VAR_RANGE_FN(nlink, ACTION_ALL_LNK, action_data->buf->st_nlink) |
| |
| TEST_VAR_RANGE_FN(fileblocks, ACTION_REG, action_data->buf->st_blocks) |
| |
| TEST_VAR_RANGE_FN(dirblocks, ACTION_DIR, action_data->buf->st_blocks) |
| |
| TEST_VAR_RANGE_FN(blocks, ACTION_ALL_LNK, action_data->buf->st_blocks) |
| |
| TEST_VAR_RANGE_FN(gid, ACTION_ALL_LNK, action_data->buf->st_gid) |
| |
| TEST_VAR_RANGE_FN(uid, ACTION_ALL_LNK, action_data->buf->st_uid) |
| |
| TEST_VAR_RANGE_FN(depth, ACTION_ALL_LNK, action_data->depth) |
| |
| TEST_VAR_RANGE_FN(dircount, ACTION_DIR, action_data->dir_ent->dir->count) |
| |
| /* |
| * uid specific test code |
| */ |
| TEST_VAR_FN(uid, ACTION_ALL_LNK, action_data->buf->st_uid) |
| |
| static int parse_uid_arg(struct test_entry *test, struct atom *atom) |
| { |
| struct test_number_arg *number; |
| long long size; |
| int range; |
| char *error; |
| |
| if(parse_number(atom->argv[0], &size, &range, &error)) { |
| /* managed to fully parse argument as a number */ |
| if(size < 0 || size > (((long long) 1 << 32) - 1)) { |
| TEST_SYNTAX_ERROR(test, 1, "Numeric uid out of " |
| "range\n"); |
| return 0; |
| } |
| } else { |
| /* couldn't parse (fully) as a number, is it a user name? */ |
| struct passwd *uid = getpwnam(atom->argv[0]); |
| if(uid) { |
| size = uid->pw_uid; |
| range = NUM_EQ; |
| } else { |
| TEST_SYNTAX_ERROR(test, 1, "Invalid uid or unknown " |
| "user\n"); |
| return 0; |
| } |
| } |
| |
| number = malloc(sizeof(*number)); |
| if(number == NULL) |
| MEM_ERROR(); |
| |
| number->range = range; |
| number->size= size; |
| |
| atom->data = number; |
| |
| return 1; |
| } |
| |
| |
| /* |
| * gid specific test code |
| */ |
| TEST_VAR_FN(gid, ACTION_ALL_LNK, action_data->buf->st_gid) |
| |
| static int parse_gid_arg(struct test_entry *test, struct atom *atom) |
| { |
| struct test_number_arg *number; |
| long long size; |
| int range; |
| char *error; |
| |
| if(parse_number(atom->argv[0], &size, &range, &error)) { |
| /* managed to fully parse argument as a number */ |
| if(size < 0 || size > (((long long) 1 << 32) - 1)) { |
| TEST_SYNTAX_ERROR(test, 1, "Numeric gid out of " |
| "range\n"); |
| return 0; |
| } |
| } else { |
| /* couldn't parse (fully) as a number, is it a group name? */ |
| struct group *gid = getgrnam(atom->argv[0]); |
| if(gid) { |
| size = gid->gr_gid; |
| range = NUM_EQ; |
| } else { |
| TEST_SYNTAX_ERROR(test, 1, "Invalid gid or unknown " |
| "group\n"); |
| return 0; |
| } |
| } |
| |
| number = malloc(sizeof(*number)); |
| if(number == NULL) |
| MEM_ERROR(); |
| |
| number->range = range; |
| number->size= size; |
| |
| atom->data = number; |
| |
| return 1; |
| } |
| |
| |
| /* |
| * Type test specific code |
| */ |
| struct type_entry type_table[] = { |
| { S_IFSOCK, 's' }, |
| { S_IFLNK, 'l' }, |
| { S_IFREG, 'f' }, |
| { S_IFBLK, 'b' }, |
| { S_IFDIR, 'd' }, |
| { S_IFCHR, 'c' }, |
| { S_IFIFO, 'p' }, |
| { 0, 0 }, |
| }; |
| |
| |
| static int parse_type_arg(struct test_entry *test, struct atom *atom) |
| { |
| int i; |
| |
| if (strlen(atom->argv[0]) != 1) |
| goto failed; |
| |
| for(i = 0; type_table[i].type != 0; i++) |
| if (type_table[i].type == atom->argv[0][0]) |
| break; |
| |
| atom->data = &type_table[i]; |
| |
| if(type_table[i].type != 0) |
| return 1; |
| |
| failed: |
| TEST_SYNTAX_ERROR(test, 0, "Unexpected file type, expected 'f', 'd', " |
| "'c', 'b', 'l', 's' or 'p'\n"); |
| return 0; |
| } |
| |
| |
| static int type_fn(struct atom *atom, struct action_data *action_data) |
| { |
| struct type_entry *type = atom->data; |
| |
| return (action_data->buf->st_mode & S_IFMT) == type->value; |
| } |
| |
| |
| /* |
| * True test specific code |
| */ |
| static int true_fn(struct atom *atom, struct action_data *action_data) |
| { |
| return 1; |
| } |
| |
| |
| /* |
| * False test specific code |
| */ |
| static int false_fn(struct atom *atom, struct action_data *action_data) |
| { |
| return 0; |
| } |
| |
| |
| /* |
| * File test specific code |
| */ |
| static int parse_file_arg(struct test_entry *test, struct atom *atom) |
| { |
| int res; |
| regex_t *preg = malloc(sizeof(regex_t)); |
| |
| if (preg == NULL) |
| MEM_ERROR(); |
| |
| res = regcomp(preg, atom->argv[0], REG_EXTENDED); |
| if (res) { |
| char str[1024]; /* overflow safe */ |
| |
| regerror(res, preg, str, 1024); |
| free(preg); |
| TEST_SYNTAX_ERROR(test, 0, "invalid regex \"%s\" because " |
| "\"%s\"\n", atom->argv[0], str); |
| return 0; |
| } |
| |
| atom->data = preg; |
| |
| return 1; |
| } |
| |
| |
| static int file_fn(struct atom *atom, struct action_data *action_data) |
| { |
| int child, res, size = 0, status; |
| int pipefd[2]; |
| char *buffer = NULL; |
| regex_t *preg = atom->data; |
| |
| res = pipe(pipefd); |
| if (res == -1) |
| BAD_ERROR("file_fn pipe failed\n"); |
| |
| child = fork(); |
| if (child == -1) |
| BAD_ERROR("file_fn fork_failed\n"); |
| |
| if (child == 0) { |
| /* |
| * Child process |
| * Connect stdout to pipefd[1] and execute file command |
| */ |
| close(STDOUT_FILENO); |
| res = dup(pipefd[1]); |
| if (res == -1) |
| exit(EXIT_FAILURE); |
| |
| execlp("file", "file", "-b", action_data->pathname, |
| (char *) NULL); |
| exit(EXIT_FAILURE); |
| } |
| |
| /* |
| * Parent process. Read stdout from file command |
| */ |
| close(pipefd[1]); |
| |
| do { |
| buffer = realloc(buffer, size + 512); |
| if (buffer == NULL) |
| MEM_ERROR(); |
| |
| res = read_bytes(pipefd[0], buffer + size, 512); |
| |
| if (res == -1) |
| BAD_ERROR("file_fn pipe read error\n"); |
| |
| size += 512; |
| |
| } while (res == 512); |
| |
| size = size + res - 512; |
| |
| buffer[size] = '\0'; |
| |
| res = waitpid(child, &status, 0); |
| |
| if (res == -1) |
| BAD_ERROR("file_fn waitpid failed\n"); |
| |
| if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) |
| BAD_ERROR("file_fn file returned error\n"); |
| |
| close(pipefd[0]); |
| |
| res = regexec(preg, buffer, (size_t) 0, NULL, 0); |
| |
| free(buffer); |
| |
| return res == 0; |
| } |
| |
| |
| /* |
| * Exec test specific code |
| */ |
| static int exec_fn(struct atom *atom, struct action_data *action_data) |
| { |
| int child, i, res, status; |
| |
| child = fork(); |
| if (child == -1) |
| BAD_ERROR("exec_fn fork_failed\n"); |
| |
| if (child == 0) { |
| /* |
| * Child process |
| * redirect stdin, stdout & stderr to /dev/null and |
| * execute atom->argv[0] |
| */ |
| int fd = open("/dev/null", O_RDWR); |
| if(fd == -1) |
| exit(EXIT_FAILURE); |
| |
| close(STDIN_FILENO); |
| close(STDOUT_FILENO); |
| close(STDERR_FILENO); |
| for(i = 0; i < 3; i++) { |
| res = dup(fd); |
| if (res == -1) |
| exit(EXIT_FAILURE); |
| } |
| close(fd); |
| |
| /* |
| * Create environment variables |
| * NAME: name of file |
| * PATHNAME: pathname of file relative to squashfs root |
| * SOURCE_PATHNAME: the pathname of the file in the source |
| * directory |
| */ |
| res = setenv("NAME", action_data->name, 1); |
| if(res == -1) |
| exit(EXIT_FAILURE); |
| |
| res = setenv("PATHNAME", action_data->subpath, 1); |
| if(res == -1) |
| exit(EXIT_FAILURE); |
| |
| res = setenv("SOURCE_PATHNAME", action_data->pathname, 1); |
| if(res == -1) |
| exit(EXIT_FAILURE); |
| |
| execl("/bin/sh", "sh", "-c", atom->argv[0], (char *) NULL); |
| exit(EXIT_FAILURE); |
| } |
| |
| /* |
| * Parent process. |
| */ |
| |
| res = waitpid(child, &status, 0); |
| |
| if (res == -1) |
| BAD_ERROR("exec_fn waitpid failed\n"); |
| |
| return WIFEXITED(status) ? WEXITSTATUS(status) == 0 : 0; |
| } |
| |
| |
| /* |
| * Symbolic link specific test code |
| */ |
| |
| /* |
| * Walk the supplied pathname and return the directory entry corresponding |
| * to the pathname. If any symlinks are encountered whilst walking the |
| * pathname, then recursively walk these, to obtain the fully |
| * dereferenced canonicalised directory entry. |
| * |
| * If follow_path fails to walk a pathname either because a component |
| * doesn't exist, it is a non directory component when a directory |
| * component is expected, a symlink with an absolute path is encountered, |
| * or a symlink is encountered which cannot be recursively walked due to |
| * the above failures, then return NULL. |
| */ |
| static struct dir_ent *follow_path(struct dir_info *dir, char *pathname) |
| { |
| char *comp, *path = pathname; |
| struct dir_ent *dir_ent = NULL; |
| |
| /* We cannot follow absolute paths */ |
| if(pathname[0] == '/') |
| return NULL; |
| |
| for(comp = get_comp(&path); comp; free(comp), comp = get_comp(&path)) { |
| if(strcmp(comp, ".") == 0) |
| continue; |
| |
| if(strcmp(comp, "..") == 0) { |
| /* Move to parent if we're not in the root directory */ |
| if(dir->depth > 1) { |
| dir = dir->dir_ent->our_dir; |
| dir_ent = NULL; /* lazily eval at loop exit */ |
| continue; |
| } else |
| /* Failed to walk pathname */ |
| return NULL; |
| } |
| |
| /* Lookup comp in current directory */ |
| dir_ent = lookup_comp(comp, dir); |
| if(dir_ent == NULL) |
| /* Doesn't exist, failed to walk pathname */ |
| return NULL; |
| |
| if((dir_ent->inode->buf.st_mode & S_IFMT) == S_IFLNK) { |
| /* Symbolic link, try to walk it */ |
| dir_ent = follow_path(dir, dir_ent->inode->symlink); |
| if(dir_ent == NULL) |
| /* Failed to follow symlink */ |
| return NULL; |
| } |
| |
| if((dir_ent->inode->buf.st_mode & S_IFMT) != S_IFDIR) |
| /* Cannot walk further */ |
| break; |
| |
| dir = dir_ent->dir; |
| } |
| |
| /* We will have exited the loop either because we've processed |
| * all the components, which means we've successfully walked the |
| * pathname, or because we've hit a non-directory, in which case |
| * it's success if this is the leaf component */ |
| if(comp) { |
| free(comp); |
| comp = get_comp(&path); |
| free(comp); |
| if(comp != NULL) |
| /* Not a leaf component */ |
| return NULL; |
| } else { |
| /* Fully walked pathname, dir_ent contains correct value unless |
| * we've walked to the parent ("..") in which case we need |
| * to resolve it here */ |
| if(!dir_ent) |
| dir_ent = dir->dir_ent; |
| } |
| |
| return dir_ent; |
| } |
| |
| |
| static int exists_fn(struct atom *atom, struct action_data *action_data) |
| { |
| /* |
| * Test if a symlink exists within the output filesystem, that is, |
| * the symlink has a relative path, and the relative path refers |
| * to an entry within the output filesystem. |
| * |
| * This test function evaluates the path for symlinks - that is it |
| * follows any symlinks in the path (and any symlinks that it contains |
| * etc.), to discover the fully dereferenced canonicalised relative |
| * path. |
| * |
| * If any symlinks within the path do not exist or are absolute |
| * then the symlink is considered to not exist, as it cannot be |
| * fully dereferenced. |
| * |
| * exists operates on symlinks only, other files by definition |
| * exist |
| */ |
| if (!file_type_match(action_data->buf->st_mode, ACTION_LNK)) |
| return 1; |
| |
| /* dereference the symlink, and return TRUE if it exists */ |
| return follow_path(action_data->dir_ent->our_dir, |
| action_data->dir_ent->inode->symlink) ? 1 : 0; |
| } |
| |
| |
| static int absolute_fn(struct atom *atom, struct action_data *action_data) |
| { |
| /* |
| * Test if a symlink has an absolute path, which by definition |
| * means the symbolic link may be broken (even if the absolute path |
| * does point into the filesystem being squashed, because the resultant |
| * filesystem can be mounted/unsquashed anywhere, it is unlikely the |
| * absolute path will still point to the right place). If you know that |
| * an absolute symlink will point to the right place then you don't need |
| * to use this function, and/or these symlinks can be excluded by |
| * use of other test operators. |
| * |
| * absolute operates on symlinks only, other files by definition |
| * don't have problems |
| */ |
| if (!file_type_match(action_data->buf->st_mode, ACTION_LNK)) |
| return 0; |
| |
| return action_data->dir_ent->inode->symlink[0] == '/'; |
| } |
| |
| |
| static int parse_expr_argX(struct test_entry *test, struct atom *atom, |
| int argno) |
| { |
| /* Call parse_expr to parse argument, which should be an expression */ |
| |
| /* save the current parser state */ |
| char *save_cur_ptr = cur_ptr; |
| char *save_source = source; |
| |
| cur_ptr = source = atom->argv[argno]; |
| atom->data = parse_expr(0); |
| |
| cur_ptr = save_cur_ptr; |
| source = save_source; |
| |
| if(atom->data == NULL) { |
| /* parse_expr(0) will have reported the exact syntax error, |
| * but, because we recursively evaluated the expression, it |
| * will have been reported without the context of the stat |
| * test(). So here additionally report our failure to parse |
| * the expression in the stat() test to give context */ |
| TEST_SYNTAX_ERROR(test, 0, "Failed to parse expression\n"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| |
| static int parse_expr_arg0(struct test_entry *test, struct atom *atom) |
| { |
| return parse_expr_argX(test, atom, 0); |
| } |
| |
| |
| static int parse_expr_arg1(struct test_entry *test, struct atom *atom) |
| { |
| return parse_expr_argX(test, atom, 1); |
| } |
| |
| |
| static int stat_fn(struct atom *atom, struct action_data *action_data) |
| { |
| struct stat buf; |
| struct action_data eval_action; |
| int match, res; |
| |
| /* evaluate the expression using the context of the inode |
| * pointed to by the symlink. This allows the inode attributes |
| * of the file pointed to by the symlink to be evaluated, rather |
| * than the symlink itself. |
| * |
| * Note, stat() deliberately does not evaluate the pathname, name or |
| * depth of the symlink, these are left with the symlink values. |
| * This allows stat() to be used on any symlink, rather than |
| * just symlinks which are contained (if the symlink is *not* |
| * contained then pathname, name and depth are meaningless as they |
| * are relative to the filesystem being squashed). */ |
| |
| /* if this isn't a symlink then stat will just return the current |
| * information, i.e. stat(expr) == expr. This is harmless and |
| * is better than returning TRUE or FALSE in a non symlink case */ |
| res = stat(action_data->pathname, &buf); |
| if(res == -1) { |
| if(expr_log_cmnd(LOG_ENABLED)) { |
| expr_log(atom->test->name); |
| expr_log("("); |
| expr_log_match(0); |
| expr_log(")"); |
| } |
| return 0; |
| } |
| |
| /* fill in the inode values of the file pointed to by the |
| * symlink, but, leave everything else the same */ |
| memcpy(&eval_action, action_data, sizeof(struct action_data)); |
| eval_action.buf = &buf; |
| |
| if(expr_log_cmnd(LOG_ENABLED)) { |
| expr_log(atom->test->name); |
| expr_log("("); |
| match = eval_expr_log(atom->data, &eval_action); |
| expr_log(")"); |
| } else |
| match = eval_expr(atom->data, &eval_action); |
| |
| return match; |
| } |
| |
| |
| static int readlink_fn(struct atom *atom, struct action_data *action_data) |
| { |
| int match = 0; |
| struct dir_ent *dir_ent; |
| struct action_data eval_action; |
| |
| /* Dereference the symlink and evaluate the expression in the |
| * context of the file pointed to by the symlink. |
| * All attributes are updated to refer to the file that is pointed to. |
| * Thus the inode attributes, pathname, name and depth all refer to |
| * the dereferenced file, and not the symlink. |
| * |
| * If the symlink cannot be dereferenced because it doesn't exist in |
| * the output filesystem, or due to some other failure to |
| * walk the pathname (see follow_path above), then FALSE is returned. |
| * |
| * If you wish to evaluate the inode attributes of symlinks which |
| * exist in the source filestem (but not in the output filesystem then |
| * use stat instead (see above). |
| * |
| * readlink operates on symlinks only */ |
| if (!file_type_match(action_data->buf->st_mode, ACTION_LNK)) |
| goto finish; |
| |
| /* dereference the symlink, and get the directory entry it points to */ |
| dir_ent = follow_path(action_data->dir_ent->our_dir, |
| action_data->dir_ent->inode->symlink); |
| if(dir_ent == NULL) |
| goto finish; |
| |
| eval_action.name = dir_ent->name; |
| eval_action.pathname = strdup(pathname(dir_ent)); |
| eval_action.subpath = strdup(subpathname(dir_ent)); |
| eval_action.buf = &dir_ent->inode->buf; |
| eval_action.depth = dir_ent->our_dir->depth; |
| eval_action.dir_ent = dir_ent; |
| eval_action.root = action_data->root; |
| |
| if(expr_log_cmnd(LOG_ENABLED)) { |
| expr_log(atom->test->name); |
| expr_log("("); |
| match = eval_expr_log(atom->data, &eval_action); |
| expr_log(")"); |
| } else |
| match = eval_expr(atom->data, &eval_action); |
| |
| free(eval_action.pathname); |
| free(eval_action.subpath); |
| |
| return match; |
| |
| finish: |
| if(expr_log_cmnd(LOG_ENABLED)) { |
| expr_log(atom->test->name); |
| expr_log("("); |
| expr_log_match(0); |
| expr_log(")"); |
| } |
| |
| return 0; |
| } |
| |
| |
| static int eval_fn(struct atom *atom, struct action_data *action_data) |
| { |
| int match; |
| char *path = atom->argv[0]; |
| struct dir_ent *dir_ent = action_data->dir_ent; |
| struct stat *buf = action_data->buf; |
| struct action_data eval_action; |
| |
| /* Follow path (arg1) and evaluate the expression (arg2) |
| * in the context of the file discovered. All attributes are updated |
| * to refer to the file that is pointed to. |
| * |
| * This test operation allows you to add additional context to the |
| * evaluation of the file being scanned, such as "if current file is |
| * XXX and the parent is YYY, then ..." Often times you need or |
| * want to test a combination of file status |
| * |
| * If the file referenced by the path does not exist in |
| * the output filesystem, or some other failure is experienced in |
| * walking the path (see follow_path above), then FALSE is returned. |
| * |
| * If you wish to evaluate the inode attributes of files which |
| * exist in the source filestem (but not in the output filesystem then |
| * use stat instead (see above). */ |
| |
| /* try to follow path, and get the directory entry it points to */ |
| if(path[0] == '/') { |
| /* absolute, walk from root - first skip the leading / */ |
| while(path[0] == '/') |
| path ++; |
| if(path[0] == '\0') |
| dir_ent = action_data->root->dir_ent; |
| else |
| dir_ent = follow_path(action_data->root, path); |
| } else { |
| /* relative, if first component is ".." walk from parent, |
| * otherwise walk from dir_ent. |
| * Note: this has to be handled here because follow_path |
| * will quite correctly refuse to execute ".." on anything |
| * which isn't a directory */ |
| if(strncmp(path, "..", 2) == 0 && (path[2] == '\0' || |
| path[2] == '/')) { |
| /* walk from parent */ |
| path += 2; |
| while(path[0] == '/') |
| path ++; |
| if(path[0] == '\0') |
| dir_ent = dir_ent->our_dir->dir_ent; |
| else |
| dir_ent = follow_path(dir_ent->our_dir, path); |
| } else if(!file_type_match(buf->st_mode, ACTION_DIR)) |
| dir_ent = NULL; |
| else |
| dir_ent = follow_path(dir_ent->dir, path); |
| } |
| |
| if(dir_ent == NULL) { |
| if(expr_log_cmnd(LOG_ENABLED)) { |
| expr_log(atom->test->name); |
| expr_log("("); |
| expr_log(atom->argv[0]); |
| expr_log(","); |
| expr_log_match(0); |
| expr_log(")"); |
| } |
| |
| return 0; |
| } |
| |
| eval_action.name = dir_ent->name; |
| eval_action.pathname = strdup(pathname(dir_ent)); |
| eval_action.subpath = strdup(subpathname(dir_ent)); |
| eval_action.buf = &dir_ent->inode->buf; |
| eval_action.depth = dir_ent->our_dir->depth; |
| eval_action.dir_ent = dir_ent; |
| eval_action.root = action_data->root; |
| |
| if(expr_log_cmnd(LOG_ENABLED)) { |
| expr_log(atom->test->name); |
| expr_log("("); |
| expr_log(eval_action.subpath); |
| expr_log(","); |
| match = eval_expr_log(atom->data, &eval_action); |
| expr_log(")"); |
| } else |
| match = eval_expr(atom->data, &eval_action); |
| |
| free(eval_action.pathname); |
| free(eval_action.subpath); |
| |
| return match; |
| } |
| |
| |
| /* |
| * Perm specific test code |
| */ |
| static int parse_perm_args(struct test_entry *test, struct atom *atom) |
| { |
| int res = 1, mode, op, i; |
| char *arg; |
| struct mode_data *head = NULL, *cur = NULL; |
| struct perm_data *perm_data; |
| |
| if(atom->args == 0) { |
| TEST_SYNTAX_ERROR(test, 0, "One or more arguments expected\n"); |
| return 0; |
| } |
| |
| switch(atom->argv[0][0]) { |
| case '-': |
| op = PERM_ALL; |
| arg = atom->argv[0] + 1; |
| break; |
| case '/': |
| op = PERM_ANY; |
| arg = atom->argv[0] + 1; |
| break; |
| default: |
| op = PERM_EXACT; |
| arg = atom->argv[0]; |
| break; |
| } |
| |
| /* try to parse as an octal number */ |
| res = parse_octal_mode_args(atom->args, atom->argv, (void **) &head); |
| if(res == -1) { |
| /* parse as sym mode argument */ |
| for(i = 0; i < atom->args && res; i++, arg = atom->argv[i]) |
| res = parse_sym_mode_arg(arg, &head, &cur); |
| } |
| |
| if (res == 0) |
| goto finish; |
| |
| /* |
| * Evaluate the symbolic mode against a permission of 0000 octal |
| */ |
| mode = mode_execute(head, 0); |
| |
| perm_data = malloc(sizeof(struct perm_data)); |
| if (perm_data == NULL) |
| MEM_ERROR(); |
| |
| perm_data->op = op; |
| perm_data->mode = mode; |
| |
| atom->data = perm_data; |
| |
| finish: |
| while(head) { |
| struct mode_data *tmp = head; |
| head = head->next; |
| free(tmp); |
| } |
| |
| return res; |
| } |
| |
| |
| static int perm_fn(struct atom *atom, struct action_data *action_data) |
| { |
| struct perm_data *perm_data = atom->data; |
| struct stat *buf = action_data->buf; |
| |
| switch(perm_data->op) { |
| case PERM_EXACT: |
| return (buf->st_mode & ~S_IFMT) == perm_data->mode; |
| case PERM_ALL: |
| return (buf->st_mode & perm_data->mode) == perm_data->mode; |
| case PERM_ANY: |
| default: |
| /* |
| * if no permission bits are set in perm_data->mode match |
| * on any file, this is to be consistent with find, which |
| * does this to be consistent with the behaviour of |
| * -perm -000 |
| */ |
| return perm_data->mode == 0 || (buf->st_mode & perm_data->mode); |
| } |
| } |
| |
| |
| #ifdef SQUASHFS_TRACE |
| static void dump_parse_tree(struct expr *expr) |
| { |
| int i; |
| |
| if(expr->type == ATOM_TYPE) { |
| printf("%s", expr->atom.test->name); |
| if(expr->atom.args) { |
| printf("("); |
| for(i = 0; i < expr->atom.args; i++) { |
| printf("%s", expr->atom.argv[i]); |
| if (i + 1 < expr->atom.args) |
| printf(","); |
| } |
| printf(")"); |
| } |
| } else if (expr->type == UNARY_TYPE) { |
| printf("%s", token_table[expr->unary_op.op].string); |
| dump_parse_tree(expr->unary_op.expr); |
| } else { |
| printf("("); |
| dump_parse_tree(expr->expr_op.lhs); |
| printf("%s", token_table[expr->expr_op.op].string); |
| dump_parse_tree(expr->expr_op.rhs); |
| printf(")"); |
| } |
| } |
| |
| |
| void dump_action_list(struct action *spec_list, int spec_count) |
| { |
| int i; |
| |
| for (i = 0; i < spec_count; i++) { |
| printf("%s", spec_list[i].action->name); |
| if (spec_list[i].args) { |
| int n; |
| |
| printf("("); |
|