/* * Create a squashfs filesystem. This is a highly compressed read only * filesystem. * * Copyright (c) 2011, 2012, 2013, 2014 * Phillip Lougher * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "squashfs_fs.h" #include "mksquashfs.h" #include "action.h" #include "error.h" #include "fnmatch_compat.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) { return fnmatch(atom->argv[0], get_start(strdupa(action_data->subpath), count_components(atom->argv[0])), FNM_PATHNAME|FNM_PERIOD|FNM_EXTMATCH) == 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) /* * 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("("); 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_gid_arg, 1, 0}, { "uid", 1, uid_fn, parse_uid_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}, { "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 }, { "noop", NOOP_ACTION, 0, ACTION_ALL, NULL, noop_action }, { "", 0, -1, 0, NULL, NULL} };