diff options
Diffstat (limited to 'squashfs-tools/action.c')
-rw-r--r-- | squashfs-tools/action.c | 3574 |
1 files changed, 3574 insertions, 0 deletions
diff --git a/squashfs-tools/action.c b/squashfs-tools/action.c new file mode 100644 index 0000000..182487b --- /dev/null +++ b/squashfs-tools/action.c @@ -0,0 +1,3574 @@ +/* + * Create a squashfs filesystem. This is a highly compressed read only + * filesystem. + * + * Copyright (c) 2011, 2012, 2013, 2014, 2021, 2022 + * 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 <pwd.h> +#include <grp.h> +#include <sys/wait.h> +#include <regex.h> +#include <limits.h> +#include <errno.h> +#include <ctype.h> + +#include "squashfs_fs.h" +#include "mksquashfs.h" +#include "action.h" +#include "mksquashfs_error.h" +#include "fnmatch_compat.h" +#include "xattr.h" + +#define TRUE 1 +#define FALSE 0 +#define MAX_LINE 16384 + +/* + * 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 *xattr_exc_spec = NULL; +static struct action *xattr_inc_spec = NULL; +static struct action *xattr_add_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 xattr_exc_count = 0; +static int xattr_inc_count = 0; +static int xattr_add_count = 0; +static int other_count = 0; +static struct action_entry *parsing_action; + +static struct file_buffer *def_fragment = NULL; +static struct file_buffer *tail_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 *); + +/* + * Read a file, passing each line to parse_line() for + * parsing. + * + * Lines can be split across multiple lines using "\". + * + * Blank lines and comment lines indicated by # are supported. + */ +static int read_file(char *filename, char *type, int (parse_line)(char *)) +{ + FILE *fd; + char *def, *err, *line = NULL; + int res, size = 0; + + fd = fopen(filename, "r"); + if(fd == NULL) { + ERROR("Could not open %s device file \"%s\" because %s\n", + type, filename, strerror(errno)); + return FALSE; + } + + while(1) { + int total = 0; + + while(1) { + int len; + + if(total + (MAX_LINE + 1) > size) { + line = realloc(line, size += (MAX_LINE + 1)); + if(line == NULL) + MEM_ERROR(); + } + + err = fgets(line + total, MAX_LINE + 1, fd); + if(err == NULL) + break; + + len = strlen(line + total); + total += len; + + if(len == MAX_LINE && line[total - 1] != '\n') { + /* line too large */ + ERROR("Line too long when reading " + "%s file \"%s\", larger than " + "%d bytes\n", type, filename, MAX_LINE); + goto failed; + } + + /* + * Remove '\n' terminator if it exists (the last line + * in the file may not be '\n' terminated) + */ + if(len && line[total - 1] == '\n') { + line[-- total] = '\0'; + len --; + } + + /* + * If no line continuation then jump out to + * process line. Note, we have to be careful to + * check for "\\" (backslashed backslash) and to + * ensure we don't look at the previous line + */ + if(len == 0 || line[total - 1] != '\\' || (len >= 2 && + strcmp(line + total - 2, "\\\\") == 0)) + break; + else + total --; + } + + if(err == NULL) { + if(ferror(fd)) { + ERROR("Reading %s file \"%s\" failed " + "because %s\n", type, filename, + strerror(errno)); + goto failed; + } + + /* + * At EOF, normally we'll be finished, but, have to + * check for special case where we had "\" line + * continuation and then hit EOF immediately afterwards + */ + if(total == 0) + break; + else + line[total] = '\0'; + } + + /* Skip any leading whitespace */ + for(def = line; isspace(*def); def ++); + + /* if line is now empty after skipping characters, skip it */ + if(*def == '\0') + continue; + + /* if comment line, skip */ + if(*def == '#') + continue; + + res = parse_line(def); + if(res == FALSE) + goto failed; + } + + fclose(fd); + free(line); + return TRUE; + +failed: + fclose(fd); + free(line); + return FALSE; +} +/* + * 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; + int offset = str_ptr - str; + + size = (cur_size + 1 + STR_SIZE) & ~(STR_SIZE - 1); + + tmp = realloc(str, size); + if(tmp == NULL) + MEM_ERROR(); + + str_ptr = tmp + offset; + 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; + case XATTR_EXC_ACTION: + spec_count = xattr_exc_count ++; + spec_list = &xattr_exc_spec; + break; + case XATTR_INC_ACTION: + spec_count = xattr_inc_count ++; + spec_list = &xattr_inc_spec; + break; + case XATTR_ADD_ACTION: + spec_count = xattr_add_count ++; + spec_list = &xattr_add_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 + +static 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; +} + + +static char *expr_log_cmnd(int cmnd) +{ + return _expr_log(NULL, cmnd); +} + + +static char *expr_log(char *string) +{ + return _expr_log(string, LOG_PRINT); +} + + +static 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(")"); + } +} + + +static 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. + */ +static int parse_action_true(char *s) +{ + return parse_action(s, ACTION_LOG_TRUE); +} + + +static int parse_action_false(char *s) +{ + return parse_action(s, ACTION_LOG_FALSE); +} + + +static int parse_action_verbose(char *s) +{ + return parse_action(s, ACTION_LOG_VERBOSE); +} + + +static 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 any_actions() +{ + return fragment_count + exclude_count + empty_count + + move_count + prune_count + other_count; +} + + +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 tail) +{ + 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); + + if(tail) + return &tail_fragment; + else + 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 == &def_fragment) + return &tail_fragment; + + if (fragment_count == 0) + return NULL; + + if (fragment == &tail_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, unsigned 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; +} + + +/* + * Xattr include/exclude specific action code + */ +static int parse_xattr_args(struct action_entry *action, int args, + char **argv, void **data) +{ + struct xattr_data *xattr_data; + int error; + + xattr_data = malloc(sizeof(*xattr_data)); + if (xattr_data == NULL) + MEM_ERROR(); + + error = regcomp(&xattr_data->preg, argv[0], REG_EXTENDED|REG_NOSUB); + if(error) { + char str[1024]; /* overflow safe */ + + regerror(error, &xattr_data->preg, str, 1024); + SYNTAX_ERROR("invalid regex %s because %s\n", argv[0], str); + free(xattr_data); + return 0; + } + + *data = xattr_data; + + return 1; +} + + +static struct xattr_data *eval_xattr_actions (struct action *spec, + int count, struct dir_info *root, struct dir_ent *dir_ent) +{ + int i; + struct action_data action_data; + struct xattr_data *head = NULL; + + if(count == 0) + return 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; + + for (i = 0; i < count; i++) { + struct xattr_data *data = spec[i].data; + int match = eval_expr_top(&spec[i], &action_data); + + if(match) { + data->next = head; + head = data; + } + } + + free(action_data.pathname); + free(action_data.subpath); + + return head; +} + + +int xattr_exc_actions() +{ + return xattr_exc_count; +} + + +struct xattr_data *eval_xattr_exc_actions (struct dir_info *root, + struct dir_ent *dir_ent) +{ + return eval_xattr_actions(xattr_exc_spec, xattr_exc_count, root, dir_ent); +} + + +int match_xattr_exc_actions(struct xattr_data *head, char *name) +{ + struct xattr_data *cur; + + for(cur = head; cur != NULL; cur = cur->next) { + int match = regexec(&cur->preg, name, (size_t) 0, NULL, 0); + + if(match == 0) + return 1; + } + + return 0; +} + + +int xattr_inc_actions() +{ + return xattr_inc_count; +} + + +struct xattr_data *eval_xattr_inc_actions (struct dir_info *root, + struct dir_ent *dir_ent) +{ + return eval_xattr_actions(xattr_inc_spec, xattr_inc_count, root, dir_ent); +} + + +int match_xattr_inc_actions(struct xattr_data *head, char *name) +{ + if(head == NULL) + return 0; + else + return !match_xattr_exc_actions(head, name); +} + + +/* + * Xattr add specific action code + */ +static int parse_xattr_add_args(struct action_entry *action, int args, + char **argv, void **data) +{ + struct xattr_add *xattr = xattr_parse(argv[0], "", "action xattr add"); + + if(xattr == NULL) + return 0; + + *data = xattr; + + return 1; +} + + +struct xattr_add *eval_xattr_add_actions(struct dir_info *root, + struct dir_ent *dir_ent, int *items) +{ + int i, count = 0; + struct action_data action_data; + struct xattr_add *head = NULL; + + if(xattr_add_count == 0) { + *items = 0; + return 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; + + for (i = 0; i < xattr_add_count; i++) { + struct xattr_add *data = xattr_add_spec[i].data; + int match = eval_expr_top(&xattr_add_spec[i], &action_data); + + if(match) { + data->next = head; + head = data; + count ++; + } + } + + free(action_data.pathname); + free(action_data.subpath); + + *items = count; + return head; +} + + +int xattr_add_actions() +{ + return xattr_add_count; +} + + +/* + * 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_EXTMATCH) == 0;) + +TEST_FN(pathname, ACTION_ALL_LNK, \ + return fnmatch(atom->argv[0], action_data->subpath, + FNM_PATHNAME|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 *data) +{ + char *s = get_start(strdup(data->subpath), count_components(atom->argv[0])); + int res = fnmatch(atom->argv[0], s, FNM_PATHNAME|FNM_EXTMATCH); + + free(s); + + return res == 0; +} + +/* + * 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) + +TEST_VAR_FN(uid, ACTION_ALL_LNK, action_data->buf->st_uid) + +TEST_VAR_FN(gid, ACTION_ALL_LNK, action_data->buf->st_gid) + +/* + * user specific test code + */ +TEST_VAR_FN(user, ACTION_ALL_LNK, action_data->buf->st_uid) + +static int parse_user_arg(struct test_entry *test, struct atom *atom) +{ + struct test_number_arg *number; + long long size; + struct passwd *uid = getpwnam(atom->argv[0]); + + if(uid) + size = uid->pw_uid; + else { + TEST_SYNTAX_ERROR(test, 1, "Unknown user\n"); + return 0; + } + + number = malloc(sizeof(*number)); + if(number == NULL) + MEM_ERROR(); + + number->range = NUM_EQ; + number->size = size; + + atom->data = number; + + return 1; +} + + +/* + * group specific test code + */ +TEST_VAR_FN(group, ACTION_ALL_LNK, action_data->buf->st_gid) + +static int parse_group_arg(struct test_entry *test, struct atom *atom) +{ + struct test_number_arg *number; + long long size; + struct group *gid = getgrnam(atom->argv[0]); + + if(gid) + size = gid->gr_gid; + else { + TEST_SYNTAX_ERROR(test, 1, "Unknown group\n"); + return 0; + } + + number = malloc(sizeof(*number)); + if(number == NULL) + MEM_ERROR(); + + number->range = NUM_EQ; + number->size= size; + + atom->data = number; + + return 1; +} + + +/* + * Type test specific code + */ +static 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("("); + for (n = 0; n < spec_list[i].args; n++) { + printf("%s", spec_list[i].argv[n]); + if (n + 1 < spec_list[i].args) + printf(","); + } + printf(")"); + } + printf("="); + dump_parse_tree(spec_list[i].expr); + printf("\n"); + } +} + + +void dump_actions() +{ + dump_action_list(exclude_spec, exclude_count); + dump_action_list(fragment_spec, fragment_count); + dump_action_list(other_spec, other_count); + dump_action_list(move_spec, move_count); + dump_action_list(empty_spec, empty_count); +} +#else +void dump_actions() +{ +} +#endif + + +static struct test_entry test_table[] = { + { "name", 1, name_fn, NULL, 1}, + { "pathname", 1, pathname_fn, check_pathname, 1, 0}, + { "subpathname", 1, subpathname_fn, check_pathname, 1, 0}, + { "filesize", 1, filesize_fn, parse_number_arg, 1, 0}, + { "dirsize", 1, dirsize_fn, parse_number_arg, 1, 0}, + { "size", 1, size_fn, parse_number_arg, 1, 0}, + { "inode", 1, inode_fn, parse_number_arg, 1, 0}, + { "nlink", 1, nlink_fn, parse_number_arg, 1, 0}, + { "fileblocks", 1, fileblocks_fn, parse_number_arg, 1, 0}, + { "dirblocks", 1, dirblocks_fn, parse_number_arg, 1, 0}, + { "blocks", 1, blocks_fn, parse_number_arg, 1, 0}, + { "gid", 1, gid_fn, parse_number_arg, 1, 0}, + { "group", 1, group_fn, parse_group_arg, 1, 0}, + { "uid", 1, uid_fn, parse_number_arg, 1, 0}, + { "user", 1, user_fn, parse_user_arg, 1, 0}, + { "depth", 1, depth_fn, parse_number_arg, 1, 0}, + { "dircount", 1, dircount_fn, parse_number_arg, 0, 0}, + { "filesize_range", 2, filesize_range_fn, parse_range_args, 1, 0}, + { "dirsize_range", 2, dirsize_range_fn, parse_range_args, 1, 0}, + { "size_range", 2, size_range_fn, parse_range_args, 1, 0}, + { "inode_range", 2, inode_range_fn, parse_range_args, 1, 0}, + { "nlink_range", 2, nlink_range_fn, parse_range_args, 1, 0}, + { "fileblocks_range", 2, fileblocks_range_fn, parse_range_args, 1, 0}, + { "dirblocks_range", 2, dirblocks_range_fn, parse_range_args, 1, 0}, + { "blocks_range", 2, blocks_range_fn, parse_range_args, 1, 0}, + { "gid_range", 2, gid_range_fn, parse_range_args, 1, 0}, + { "uid_range", 2, uid_range_fn, parse_range_args, 1, 0}, + { "depth_range", 2, depth_range_fn, parse_range_args, 1, 0}, + { "dircount_range", 2, dircount_range_fn, parse_range_args, 0, 0}, + { "type", 1, type_fn, parse_type_arg, 1, 0}, + { "true", 0, true_fn, NULL, 1, 0}, + { "false", 0, false_fn, NULL, 1, 0}, + { "file", 1, file_fn, parse_file_arg, 1, 0}, + { "exec", 1, exec_fn, NULL, 1, 0}, + { "exists", 0, exists_fn, NULL, 0, 0}, + { "absolute", 0, absolute_fn, NULL, 0, 0}, + { "stat", 1, stat_fn, parse_expr_arg0, 1, 1}, + { "readlink", 1, readlink_fn, parse_expr_arg0, 0, 1}, + { "eval", 2, eval_fn, parse_expr_arg1, 0, 1}, + { "perm", -2, perm_fn, parse_perm_args, 1, 0}, + { "", -1 } +}; + + +static struct action_entry action_table[] = { + { "fragment", FRAGMENT_ACTION, 1, ACTION_REG, NULL, NULL}, + { "exclude", EXCLUDE_ACTION, 0, ACTION_ALL_LNK, NULL, NULL}, + { "fragments", FRAGMENTS_ACTION, 0, ACTION_REG, NULL, frag_action}, + { "no-fragments", NO_FRAGMENTS_ACTION, 0, ACTION_REG, NULL, no_frag_action}, + { "always-use-fragments", ALWAYS_FRAGS_ACTION, 0, ACTION_REG, NULL, always_frag_action}, + { "dont-always-use-fragments", NO_ALWAYS_FRAGS_ACTION, 0, ACTION_REG, NULL, no_always_frag_action}, + { "tailend", ALWAYS_FRAGS_ACTION, 0, ACTION_REG, NULL, always_frag_action}, + { "no-tailend", NO_ALWAYS_FRAGS_ACTION, 0, ACTION_REG, NULL, no_always_frag_action}, + { "compressed", COMPRESSED_ACTION, 0, ACTION_REG, NULL, comp_action}, + { "uncompressed", UNCOMPRESSED_ACTION, 0, ACTION_REG, NULL, uncomp_action}, + { "uid", UID_ACTION, 1, ACTION_ALL_LNK, parse_uid_args, uid_action}, + { "gid", GID_ACTION, 1, ACTION_ALL_LNK, parse_gid_args, gid_action}, + { "guid", GUID_ACTION, 2, ACTION_ALL_LNK, parse_guid_args, guid_action}, + { "mode", MODE_ACTION, -2, ACTION_ALL, parse_mode_args, mode_action }, + { "empty", EMPTY_ACTION, -2, ACTION_DIR, parse_empty_args, NULL}, + { "move", MOVE_ACTION, 1, ACTION_ALL_LNK, NULL, NULL}, + { "prune", PRUNE_ACTION, 0, ACTION_ALL_LNK, NULL, NULL}, + { "chmod", MODE_ACTION, -2, ACTION_ALL, parse_mode_args, mode_action }, + { "xattrs-exclude", XATTR_EXC_ACTION, 1, ACTION_ALL, parse_xattr_args, NULL}, + { "xattrs-include", XATTR_INC_ACTION, 1, ACTION_ALL, parse_xattr_args, NULL}, + { "xattrs-add", XATTR_ADD_ACTION, 1, ACTION_ALL, parse_xattr_add_args, NULL}, + { "noop", NOOP_ACTION, 0, ACTION_ALL, NULL, noop_action }, + { "", 0, -1, 0, NULL, NULL} +}; |