aboutsummaryrefslogtreecommitdiff
path: root/bin/gensquashfs/fstree_from_file.c
diff options
context:
space:
mode:
authorDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>2022-11-22 14:45:32 +0100
committerDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>2022-11-22 14:45:32 +0100
commit168ef9be32ad754d7bcb38ed70787237fc12630d (patch)
tree39d567fa667b2f170783329a07481769dd538d43 /bin/gensquashfs/fstree_from_file.c
parent61a0dd71c4b69c21ee4aacdc0459dba58504a24a (diff)
Move gensquashfs specific code from libfstree to gensquashfs
The "from dir" and from "from file" code, as well as the "sort file" code is specific to gensquashfs, so move them there and the test cases as well. The medium term idea is to reduce libfstree to a stub, merge it into the generic writer and ultimately hoist that into libsquashfs. Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Diffstat (limited to 'bin/gensquashfs/fstree_from_file.c')
-rw-r--r--bin/gensquashfs/fstree_from_file.c591
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;
+}