diff options
Diffstat (limited to 'bin/gensquashfs/fstree_from_file.c')
-rw-r--r-- | bin/gensquashfs/fstree_from_file.c | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/bin/gensquashfs/fstree_from_file.c b/bin/gensquashfs/fstree_from_file.c new file mode 100644 index 0000000..feacbbc --- /dev/null +++ b/bin/gensquashfs/fstree_from_file.c @@ -0,0 +1,591 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * fstree_from_file.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "config.h" + +#include "util/util.h" +#include "io/file.h" +#include "compat.h" +#include "mkfs.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> + +struct glob_context { + const char *filename; + size_t line_num; + + struct stat *basic; + unsigned int glob_flags; + + char *name_pattern; +}; + +enum { + GLOB_MODE_FROM_SRC = 0x01, + GLOB_UID_FROM_SRC = 0x02, + GLOB_GID_FROM_SRC = 0x04, + GLOB_FLAG_PATH = 0x08, +}; + +static const struct { + const char *name; + unsigned int clear_flag; + unsigned int set_flag; +} glob_scan_flags[] = { + { "-type b", DIR_SCAN_NO_BLK, 0 }, + { "-type c", DIR_SCAN_NO_CHR, 0 }, + { "-type d", DIR_SCAN_NO_DIR, 0 }, + { "-type p", DIR_SCAN_NO_FIFO, 0 }, + { "-type f", DIR_SCAN_NO_FILE, 0 }, + { "-type l", DIR_SCAN_NO_SLINK, 0 }, + { "-type s", DIR_SCAN_NO_SOCK, 0 }, + { "-xdev", 0, DIR_SCAN_ONE_FILESYSTEM }, + { "-mount", 0, DIR_SCAN_ONE_FILESYSTEM }, + { "-keeptime", 0, DIR_SCAN_KEEP_TIME }, + { "-nonrecursive", 0, DIR_SCAN_NO_RECURSION }, +}; + +static int add_generic(fstree_t *fs, const char *filename, size_t line_num, + const char *path, struct stat *sb, + const char *basepath, unsigned int glob_flags, + const char *extra) +{ + (void)basepath; + (void)glob_flags; + + if (fstree_add_generic(fs, path, sb, extra) == NULL) { + fprintf(stderr, "%s: " PRI_SZ ": %s: %s\n", + filename, line_num, path, strerror(errno)); + return -1; + } + + return 0; +} + +static int add_device(fstree_t *fs, const char *filename, size_t line_num, + const char *path, struct stat *sb, const char *basepath, + unsigned int glob_flags, const char *extra) +{ + unsigned int maj, min; + char c; + + if (sscanf(extra, "%c %u %u", &c, &maj, &min) != 3) { + fprintf(stderr, "%s: " PRI_SZ ": " + "expected '<c|b> major minor'\n", + filename, line_num); + return -1; + } + + if (c == 'c' || c == 'C') { + sb->st_mode |= S_IFCHR; + } else if (c == 'b' || c == 'B') { + sb->st_mode |= S_IFBLK; + } else { + fprintf(stderr, "%s: " PRI_SZ ": unknown device type '%c'\n", + filename, line_num, c); + return -1; + } + + sb->st_rdev = makedev(maj, min); + return add_generic(fs, filename, line_num, path, sb, basepath, + glob_flags, NULL); +} + +static int add_file(fstree_t *fs, const char *filename, size_t line_num, + const char *path, struct stat *basic, const char *basepath, + unsigned int glob_flags, const char *extra) +{ + if (extra == NULL || *extra == '\0') + extra = path; + + return add_generic(fs, filename, line_num, path, basic, + basepath, glob_flags, extra); +} + +static int add_hard_link(fstree_t *fs, const char *filename, size_t line_num, + const char *path, struct stat *basic, + const char *basepath, unsigned int glob_flags, + const char *extra) +{ + (void)basepath; + (void)glob_flags; + (void)basic; + + if (fstree_add_hard_link(fs, path, extra) == NULL) { + fprintf(stderr, "%s: " PRI_SZ ": %s\n", + filename, line_num, strerror(errno)); + return -1; + } + return 0; +} + +static int glob_node_callback(void *user, fstree_t *fs, tree_node_t *node) +{ + struct glob_context *ctx = user; + char *path; + int ret; + (void)fs; + + if (!(ctx->glob_flags & GLOB_MODE_FROM_SRC)) { + node->mode &= ~(07777); + node->mode |= ctx->basic->st_mode & 07777; + } + + if (!(ctx->glob_flags & GLOB_UID_FROM_SRC)) + node->uid = ctx->basic->st_uid; + + if (!(ctx->glob_flags & GLOB_GID_FROM_SRC)) + node->gid = ctx->basic->st_gid; + + if (ctx->name_pattern != NULL) { + if (ctx->glob_flags & GLOB_FLAG_PATH) { + path = fstree_get_path(node); + if (path == NULL) { + fprintf(stderr, "%s: " PRI_SZ ": %s\n", + ctx->filename, ctx->line_num, + strerror(errno)); + return -1; + } + + ret = canonicalize_name(path); + assert(ret == 0); + + ret = fnmatch(ctx->name_pattern, path, FNM_PATHNAME); + free(path); + } else { + ret = fnmatch(ctx->name_pattern, node->name, 0); + } + + if (ret != 0) + return 1; + } + + return 0; +} + +static size_t name_string_length(const char *str) +{ + size_t len = 0; + int start; + + if (*str == '"' || *str == '\'') { + start = *str; + ++len; + + while (str[len] != '\0' && str[len] != start) + ++len; + + if (str[len] == start) + ++len; + } else { + while (str[len] != '\0' && !isspace(str[len])) + ++len; + } + + return len; +} + +static void quote_remove(char *str) +{ + char *dst = str; + int start = *(str++); + + if (start != '\'' && start != '"') + return; + + while (*str != start && *str != '\0') + *(dst++) = *(str++); + + *(dst++) = '\0'; +} + +static int glob_files(fstree_t *fs, const char *filename, size_t line_num, + const char *path, struct stat *basic, + const char *basepath, unsigned int glob_flags, + const char *extra) +{ + unsigned int scan_flags = 0, all_flags; + struct glob_context ctx; + bool first_clear_flag; + size_t i, count, len; + tree_node_t *root; + int ret; + + memset(&ctx, 0, sizeof(ctx)); + ctx.filename = filename; + ctx.line_num = line_num; + ctx.basic = basic; + ctx.glob_flags = glob_flags; + + /* fetch the actual target node */ + root = fstree_get_node_by_path(fs, fs->root, path, true, false); + if (root == NULL) { + fprintf(stderr, "%s: " PRI_SZ ": %s: %s\n", + filename, line_num, path, strerror(errno)); + return -1; + } + + /* process options */ + first_clear_flag = true; + + all_flags = DIR_SCAN_NO_BLK | DIR_SCAN_NO_CHR | DIR_SCAN_NO_DIR | + DIR_SCAN_NO_FIFO | DIR_SCAN_NO_FILE | DIR_SCAN_NO_SLINK | + DIR_SCAN_NO_SOCK; + + while (extra != NULL && *extra != '\0') { + count = sizeof(glob_scan_flags) / sizeof(glob_scan_flags[0]); + + for (i = 0; i < count; ++i) { + len = strlen(glob_scan_flags[i].name); + if (strncmp(extra, glob_scan_flags[i].name, len) != 0) + continue; + + if (isspace(extra[len])) { + extra += len; + while (isspace(*extra)) + ++extra; + break; + } + } + + if (i < count) { + if (glob_scan_flags[i].clear_flag != 0 && + first_clear_flag) { + scan_flags |= all_flags; + first_clear_flag = false; + } + + scan_flags &= ~(glob_scan_flags[i].clear_flag); + scan_flags |= glob_scan_flags[i].set_flag; + continue; + } + + if (strncmp(extra, "-name", 5) == 0 && isspace(extra[5])) { + for (extra += 5; isspace(*extra); ++extra) + ; + + len = name_string_length(extra); + + free(ctx.name_pattern); + ctx.name_pattern = strndup(extra, len); + extra += len; + + while (isspace(*extra)) + ++extra; + + quote_remove(ctx.name_pattern); + continue; + } + + if (strncmp(extra, "-path", 5) == 0 && isspace(extra[5])) { + for (extra += 5; isspace(*extra); ++extra) + ; + + len = name_string_length(extra); + + free(ctx.name_pattern); + ctx.name_pattern = strndup(extra, len); + extra += len; + + while (isspace(*extra)) + ++extra; + + quote_remove(ctx.name_pattern); + ctx.glob_flags |= GLOB_FLAG_PATH; + continue; + } + + if (extra[0] == '-') { + if (extra[1] == '-' && isspace(extra[2])) { + extra += 2; + while (isspace(*extra)) + ++extra; + break; + } + + fprintf(stderr, "%s: " PRI_SZ ": unknown option.\n", + filename, line_num); + free(ctx.name_pattern); + return -1; + } else { + break; + } + } + + if (extra != NULL && *extra == '\0') + extra = NULL; + + /* do the scan */ + if (basepath == NULL) { + if (extra == NULL) { + ret = fstree_from_dir(fs, root, ".", glob_node_callback, + &ctx, scan_flags); + } else { + ret = fstree_from_dir(fs, root, extra, + glob_node_callback, + &ctx, scan_flags); + } + } else { + ret = fstree_from_subdir(fs, root, basepath, extra, + glob_node_callback, &ctx, + scan_flags); + } + + free(ctx.name_pattern); + return ret; +} + +static const struct callback_t { + const char *keyword; + unsigned int mode; + bool need_extra; + bool is_glob; + bool allow_root; + int (*callback)(fstree_t *fs, const char *filename, size_t line_num, + const char *path, struct stat *sb, + const char *basepath, unsigned int glob_flags, + const char *extra); +} file_list_hooks[] = { + { "dir", S_IFDIR, false, false, true, add_generic }, + { "slink", S_IFLNK, true, false, false, add_generic }, + { "link", 0, true, false, false, add_hard_link }, + { "nod", 0, true, false, false, add_device }, + { "pipe", S_IFIFO, false, false, false, add_generic }, + { "sock", S_IFSOCK, false, false, false, add_generic }, + { "file", S_IFREG, false, false, false, add_file }, + { "glob", 0, false, true, true, glob_files }, +}; + +#define NUM_HOOKS (sizeof(file_list_hooks) / sizeof(file_list_hooks[0])) + +static char *skip_space(char *str) +{ + if (!isspace(*str)) + return NULL; + while (isspace(*str)) + ++str; + return str; +} + +static char *read_u32(char *str, sqfs_u32 *out, sqfs_u32 base) +{ + *out = 0; + + if (!isdigit(*str)) + return NULL; + + while (isdigit(*str)) { + sqfs_u32 x = *(str++) - '0'; + + if (x >= base || (*out) > (0xFFFFFFFF - x) / base) + return NULL; + + (*out) = (*out) * base + x; + } + + return str; +} + +static char *read_str(char *str, char **out) +{ + *out = str; + + if (*str == '"') { + char *ptr = str++; + + while (*str != '\0' && *str != '"') { + if (str[0] == '\\' && + (str[1] == '"' || str[1] == '\\')) { + *(ptr++) = str[1]; + str += 2; + } else { + *(ptr++) = *(str++); + } + } + + if (str[0] != '"' || !isspace(str[1])) + return NULL; + + *ptr = '\0'; + ++str; + } else { + while (*str != '\0' && !isspace(*str)) + ++str; + + if (!isspace(*str)) + return NULL; + + *(str++) = '\0'; + } + + while (isspace(*str)) + ++str; + + return str; +} + +static int handle_line(fstree_t *fs, const char *filename, + size_t line_num, char *line, + const char *basepath) +{ + const char *extra = NULL, *msg = NULL; + const struct callback_t *cb = NULL; + unsigned int glob_flags = 0; + sqfs_u32 uid, gid, mode; + struct stat sb; + char *path; + + for (size_t i = 0; i < NUM_HOOKS; ++i) { + size_t len = strlen(file_list_hooks[i].keyword); + if (strncmp(file_list_hooks[i].keyword, line, len) != 0) + continue; + + if (isspace(line[len])) { + cb = file_list_hooks + i; + line = skip_space(line + len); + break; + } + } + + if (cb == NULL) + goto fail_kw; + + if ((line = read_str(line, &path)) == NULL) + goto fail_ent; + + if (canonicalize_name(path)) + goto fail_ent; + + if (*path == '\0' && !cb->allow_root) + goto fail_root; + + if (cb->is_glob && *line == '*') { + ++line; + mode = 0; + glob_flags |= GLOB_MODE_FROM_SRC; + } else { + if ((line = read_u32(line, &mode, 8)) == NULL || mode > 07777) + goto fail_mode; + } + + if ((line = skip_space(line)) == NULL) + goto fail_ent; + + if (cb->is_glob && *line == '*') { + ++line; + uid = 0; + glob_flags |= GLOB_UID_FROM_SRC; + } else { + if ((line = read_u32(line, &uid, 10)) == NULL) + goto fail_uid_gid; + } + + if ((line = skip_space(line)) == NULL) + goto fail_ent; + + if (cb->is_glob && *line == '*') { + ++line; + gid = 0; + glob_flags |= GLOB_GID_FROM_SRC; + } else { + if ((line = read_u32(line, &gid, 10)) == NULL) + goto fail_uid_gid; + } + + if ((line = skip_space(line)) != NULL && *line != '\0') + extra = line; + + if (cb->need_extra && extra == NULL) + goto fail_no_extra; + + /* forward to callback */ + memset(&sb, 0, sizeof(sb)); + sb.st_mtime = fs->defaults.st_mtime; + sb.st_mode = mode | cb->mode; + sb.st_uid = uid; + sb.st_gid = gid; + + return cb->callback(fs, filename, line_num, path, + &sb, basepath, glob_flags, extra); +fail_root: + fprintf(stderr, "%s: " PRI_SZ ": cannot use / as argument for %s.\n", + filename, line_num, cb->keyword); + return -1; +fail_no_extra: + fprintf(stderr, "%s: " PRI_SZ ": missing argument for %s.\n", + filename, line_num, cb->keyword); + return -1; +fail_uid_gid: + msg = "uid & gid must be decimal numbers < 2^32"; + goto out_desc; +fail_mode: + msg = "mode must be an octal number <= 07777"; + goto out_desc; +fail_kw: + msg = "unknown entry type"; + goto out_desc; +fail_ent: + msg = "error in entry description"; + goto out_desc; +out_desc: + fprintf(stderr, "%s: " PRI_SZ ": %s.\n", filename, line_num, msg); + fputs("expected: <type> <path> <mode> <uid> <gid> [<extra>]\n", + stderr); + return -1; +} + +int fstree_from_file_stream(fstree_t *fs, istream_t *fp, const char *basepath) +{ + const char *filename; + size_t line_num = 1; + char *line; + int ret; + + filename = istream_get_filename(fp); + + for (;;) { + ret = istream_get_line(fp, &line, &line_num, + ISTREAM_LINE_LTRIM | ISTREAM_LINE_SKIP_EMPTY); + if (ret < 0) + return -1; + if (ret > 0) + break; + + if (line[0] != '#') { + if (handle_line(fs, filename, line_num, + line, basepath)) { + goto fail_line; + } + } + + free(line); + ++line_num; + } + + return 0; +fail_line: + free(line); + return -1; +} + +int fstree_from_file(fstree_t *fs, const char *filename, const char *basepath) +{ + istream_t *fp; + int ret; + + fp = istream_open_file(filename); + if (fp == NULL) + return -1; + + ret = fstree_from_file_stream(fs, fp, basepath); + + sqfs_destroy(fp); + return ret; +} |