aboutsummaryrefslogtreecommitdiff
path: root/bin/gensquashfs/src
diff options
context:
space:
mode:
authorDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>2023-01-31 11:21:30 +0100
committerDavid Oberhollenzer <david.oberhollenzer@sigma-star.at>2023-01-31 13:51:49 +0100
commitcdccc69c62579b0c13b35fad0728079652b8f3c9 (patch)
tree9fa54c710f73c5e08a9c8466e7a712eb63ee07ac /bin/gensquashfs/src
parent2182129c8f359c4fa1390eaba7a65b595ccd4182 (diff)
Move library source into src sub-directory
Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Diffstat (limited to 'bin/gensquashfs/src')
-rw-r--r--bin/gensquashfs/src/dirscan_xattr.c220
-rw-r--r--bin/gensquashfs/src/filemap_xattr.c254
-rw-r--r--bin/gensquashfs/src/fstree_from_dir.c493
-rw-r--r--bin/gensquashfs/src/fstree_from_file.c591
-rw-r--r--bin/gensquashfs/src/mkfs.c215
-rw-r--r--bin/gensquashfs/src/mkfs.h137
-rw-r--r--bin/gensquashfs/src/options.c383
-rw-r--r--bin/gensquashfs/src/selinux.c78
-rw-r--r--bin/gensquashfs/src/sort_by_file.c368
9 files changed, 2739 insertions, 0 deletions
diff --git a/bin/gensquashfs/src/dirscan_xattr.c b/bin/gensquashfs/src/dirscan_xattr.c
new file mode 100644
index 0000000..7d4e552
--- /dev/null
+++ b/bin/gensquashfs/src/dirscan_xattr.c
@@ -0,0 +1,220 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * dirscan_xattr.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "mkfs.h"
+
+#ifdef HAVE_SYS_XATTR_H
+static char *get_full_path(const char *prefix, tree_node_t *node)
+{
+ char *path = NULL, *new = NULL;
+ size_t path_len, prefix_len;
+ int ret;
+
+ path = fstree_get_path(node);
+ if (path == NULL)
+ goto fail;
+
+ ret = canonicalize_name(path);
+ assert(ret == 0);
+
+ path_len = strlen(path);
+ prefix_len = strlen(prefix);
+
+ while (prefix_len > 0 && prefix[prefix_len - 1] == '/')
+ --prefix_len;
+
+ if (prefix_len > 0) {
+ new = realloc(path, path_len + prefix_len + 2);
+ if (new == NULL)
+ goto fail;
+
+ path = new;
+
+ memmove(path + prefix_len + 1, path, path_len + 1);
+ memcpy(path, prefix, prefix_len);
+ path[prefix_len] = '/';
+ }
+
+ return path;
+fail:
+ perror("getting full path for xattr scan");
+ free(path);
+ return NULL;
+}
+
+static int xattr_from_path(sqfs_xattr_writer_t *xwr, const char *path)
+{
+ char *key, *value = NULL, *buffer = NULL;
+ ssize_t buflen, vallen, keylen;
+ int ret;
+
+ buflen = llistxattr(path, NULL, 0);
+ if (buflen < 0) {
+ fprintf(stderr, "llistxattr %s: %s", path, strerror(errno));
+ return -1;
+ }
+
+ if (buflen == 0)
+ return 0;
+
+ buffer = malloc(buflen);
+ if (buffer == NULL) {
+ perror("xattr name buffer");
+ return -1;
+ }
+
+ buflen = llistxattr(path, buffer, buflen);
+ if (buflen == -1) {
+ fprintf(stderr, "llistxattr %s: %s", path, strerror(errno));
+ goto fail;
+ }
+
+ key = buffer;
+ while (buflen > 0) {
+ vallen = lgetxattr(path, key, NULL, 0);
+ if (vallen == -1) {
+ fprintf(stderr, "lgetxattr %s: %s",
+ path, strerror(errno));
+ goto fail;
+ }
+
+ if (vallen > 0) {
+ value = calloc(1, vallen);
+ if (value == NULL) {
+ perror("allocating xattr value buffer");
+ goto fail;
+ }
+
+ vallen = lgetxattr(path, key, value, vallen);
+ if (vallen == -1) {
+ fprintf(stderr, "lgetxattr %s: %s\n",
+ path, strerror(errno));
+ goto fail;
+ }
+
+ ret = sqfs_xattr_writer_add(xwr, key, value, vallen);
+ if (ret) {
+ sqfs_perror(path,
+ "storing xattr key-value pairs",
+ ret);
+ goto fail;
+ }
+
+ free(value);
+ value = NULL;
+ }
+
+ keylen = strlen(key) + 1;
+ buflen -= keylen;
+ key += keylen;
+ }
+
+ free(buffer);
+ return 0;
+fail:
+ free(value);
+ free(buffer);
+ return -1;
+}
+#endif
+
+static int xattr_xcan_dfs(const char *path_prefix, void *selinux_handle,
+ sqfs_xattr_writer_t *xwr, bool scan_xattr, void *xattr_map,
+ tree_node_t *node)
+{
+ char *path = NULL;
+ int ret;
+
+ ret = sqfs_xattr_writer_begin(xwr, 0);
+ if (ret) {
+ sqfs_perror(node->name, "recoding xattr key-value pairs\n",
+ ret);
+ return -1;
+ }
+
+#ifdef HAVE_SYS_XATTR_H
+ if (scan_xattr) {
+ path = get_full_path(path_prefix, node);
+ if (path == NULL)
+ return -1;
+
+ ret = xattr_from_path(xwr, path);
+ free(path);
+ path = NULL;
+ if (ret) {
+ ret = -1;
+ goto out;
+ }
+ }
+#else
+ (void)path_prefix;
+#endif
+
+ if (selinux_handle != NULL || xattr_map != NULL) {
+ path = fstree_get_path(node);
+
+ if (path == NULL) {
+ perror("reconstructing absolute path");
+ ret = -1;
+ goto out;
+ }
+ }
+
+ if (xattr_map != NULL) {
+ ret = xattr_apply_map_file(path, xattr_map, xwr);
+
+ if (ret) {
+ ret = -1;
+ goto out;
+ }
+ }
+
+ if (selinux_handle != NULL) {
+ ret = selinux_relable_node(selinux_handle, xwr, node, path);
+
+ if (ret) {
+ ret = -1;
+ goto out;
+ }
+ }
+
+ if (sqfs_xattr_writer_end(xwr, &node->xattr_idx)) {
+ sqfs_perror(node->name, "completing xattr key-value pairs",
+ ret);
+ ret = -1;
+ goto out;
+ }
+
+ if (S_ISDIR(node->mode)) {
+ node = node->data.dir.children;
+
+ while (node != NULL) {
+ if (xattr_xcan_dfs(path_prefix, selinux_handle, xwr,
+ scan_xattr, xattr_map, node)) {
+ ret = -1;
+ goto out;
+ }
+
+ node = node->next;
+ }
+ }
+
+out:
+ free(path);
+ return ret;
+}
+
+int xattrs_from_dir(fstree_t *fs, const char *path, void *selinux_handle,
+ void *xattr_map, sqfs_xattr_writer_t *xwr, bool scan_xattr)
+{
+ if (xwr == NULL)
+ return 0;
+
+ if (selinux_handle == NULL && !scan_xattr && xattr_map == NULL)
+ return 0;
+
+ return xattr_xcan_dfs(path, selinux_handle, xwr, scan_xattr, xattr_map, fs->root);
+}
diff --git a/bin/gensquashfs/src/filemap_xattr.c b/bin/gensquashfs/src/filemap_xattr.c
new file mode 100644
index 0000000..dd76b50
--- /dev/null
+++ b/bin/gensquashfs/src/filemap_xattr.c
@@ -0,0 +1,254 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * filemap_xattr.c
+ *
+ * Copyright (C) 2022 Enno Boland <mail@eboland.de>
+ */
+#include "fstree.h"
+#include "mkfs.h"
+#include <stdio.h>
+
+#define NEW_FILE_START "# file: "
+
+static void print_error(const char *filename, size_t line_num, const char *err)
+{
+ fprintf(stderr, "%s: " PRI_SZ ": %s\n", filename, line_num, err);
+}
+
+// Taken from attr-2.5.1/tools/setfattr.c
+static sqfs_u8 *decode(const char *filename, size_t line_num,
+ const char *value, size_t *size)
+{
+ sqfs_u8 *decoded = NULL;
+
+ if (*size == 0) {
+ decoded = (sqfs_u8 *)strdup("");
+ if (decoded == NULL)
+ goto fail_alloc;
+ return decoded;
+ }
+
+ if (value[0] == '0' && (value[1] == 'x' || value[1] == 'X')) {
+ *size = ((*size) - 2) / 2;
+
+ decoded = calloc(1, (*size) + 1);
+ if (decoded == NULL)
+ goto fail_alloc;
+
+ if (hex_decode(value + 2, (*size) * 2, decoded, *size))
+ goto fail_encode;
+ } else if (value[0] == '0' && (value[1] == 's' || value[1] == 'S')) {
+ size_t input_len = *size - 2;
+
+ *size = (input_len / 4) * 3;
+
+ decoded = calloc(1, (*size) + 1);
+ if (decoded == NULL)
+ goto fail_alloc;
+
+ if (base64_decode(value + 2, input_len, decoded, size))
+ goto fail_encode;
+ } else {
+ const char *v = value, *end = value + *size;
+ sqfs_u8 *d;
+
+ if (end > v + 1 && *v == '"' && *(end - 1) == '"') {
+ v++;
+ end--;
+ }
+
+ decoded = calloc(1, (*size) + 1);
+ if (decoded == NULL)
+ goto fail_alloc;
+
+ d = decoded;
+
+ while (v < end) {
+ if (v[0] == '\\') {
+ if (v[1] == '\\' || v[1] == '"') {
+ *d++ = *++v;
+ v++;
+ } else if (v[1] >= '0' && v[1] <= '7') {
+ int c = 0;
+ v++;
+ c = (*v++ - '0');
+ if (*v >= '0' && *v <= '7')
+ c = (c << 3) + (*v++ - '0');
+ if (*v >= '0' && *v <= '7')
+ c = (c << 3) + (*v++ - '0');
+ *d++ = c;
+ } else
+ *d++ = *v++;
+ } else
+ *d++ = *v++;
+ }
+ *size = d - decoded;
+ }
+ return decoded;
+fail_alloc:
+ fprintf(stderr, "out of memory\n");
+ return NULL;
+fail_encode:
+ print_error(filename, line_num, "bad input encoding");
+ free(decoded);
+ return NULL;
+}
+
+static int parse_file_name(const char *filename, size_t line_num,
+ char *line, struct XattrMap *map)
+{
+ struct XattrMapPattern *current_file;
+ char *file_name = strdup(line + strlen(NEW_FILE_START));
+
+ if (file_name == NULL)
+ goto fail_alloc;
+
+ current_file = calloc(1, sizeof(struct XattrMapPattern));
+ if (current_file == NULL)
+ goto fail_alloc;
+
+ current_file->next = map->patterns;
+ map->patterns = current_file;
+
+ if (canonicalize_name(file_name)) {
+ print_error(filename, line_num, "invalid absolute path");
+ free(current_file);
+ free(file_name);
+ return -1;
+ }
+
+ current_file->path = file_name;
+ return 0;
+fail_alloc:
+ fprintf(stderr, "out of memory\n");
+ free(file_name);
+ return -1;
+}
+
+static int parse_xattr(const char *filename, size_t line_num, char *key_start,
+ char *value_start, struct XattrMap *map)
+{
+ size_t len;
+ struct XattrMapPattern *current_pattern = map->patterns;
+ struct XattrMapEntry *current_entry;
+
+ if (current_pattern == NULL) {
+ print_error(filename, line_num, "no file specified yet");
+ return -1;
+ }
+
+ current_entry = calloc(1, sizeof(struct XattrMapEntry));
+ if (current_entry == NULL) {
+ return -1;
+ }
+ current_entry->next = current_pattern->entries;
+ current_pattern->entries = current_entry;
+
+ current_entry->key = strdup(key_start);
+ len = strlen(value_start);
+ current_entry->value = decode(filename, line_num, value_start, &len);
+ current_entry->value_len = len;
+
+ return 0;
+}
+
+void *
+xattr_open_map_file(const char *path) {
+ struct XattrMap *map;
+ size_t line_num = 1;
+ char *p = NULL;
+ istream_t *file = istream_open_file(path);
+ if (file == NULL) {
+ return NULL;
+ }
+
+ map = calloc(1, sizeof(struct XattrMap));
+ if (map == NULL)
+ goto fail_close;
+
+ for (;;) {
+ char *line = NULL;
+ int ret = istream_get_line(file, &line, &line_num,
+ ISTREAM_LINE_LTRIM |
+ ISTREAM_LINE_RTRIM |
+ ISTREAM_LINE_SKIP_EMPTY);
+ if (ret < 0)
+ goto fail;
+ if (ret > 0)
+ break;
+
+ if (strncmp(NEW_FILE_START, line, strlen(NEW_FILE_START)) == 0) {
+ ret = parse_file_name(path, line_num, line, map);
+ } else if ((p = strchr(line, '='))) {
+ *(p++) = '\0';
+ ret = parse_xattr(path, line_num, line, p, map);
+ } else if (line[0] != '#') {
+ print_error(path, line_num, "not a key-value pair");
+ ret = -1;
+ }
+
+ ++line_num;
+ free(line);
+ if (ret < 0)
+ goto fail;
+ }
+
+ sqfs_drop(file);
+ return map;
+fail:
+ xattr_close_map_file(map);
+fail_close:
+ sqfs_drop(file);
+ return NULL;
+}
+
+void
+xattr_close_map_file(void *xattr_map) {
+ struct XattrMap *map = xattr_map;
+ while (map->patterns != NULL) {
+ struct XattrMapPattern *file = map->patterns;
+ map->patterns = file->next;
+ while (file->entries != NULL) {
+ struct XattrMapEntry *entry = file->entries;
+ file->entries = entry->next;
+ free(entry->key);
+ free(entry->value);
+ free(entry);
+ }
+ free(file->path);
+ free(file);
+ }
+ free(xattr_map);
+}
+
+int
+xattr_apply_map_file(char *path, void *map, sqfs_xattr_writer_t *xwr) {
+ struct XattrMap *xattr_map = map;
+ int ret = 0;
+ const struct XattrMapPattern *pat;
+ const struct XattrMapEntry *entry;
+
+ for (pat = xattr_map->patterns; pat != NULL; pat = pat->next) {
+ char *patstr = pat->path;
+ const char *stripped = path;
+
+ if (patstr[0] != '/' && stripped[0] == '/') {
+ stripped++;
+ }
+
+ if (strcmp(patstr, stripped) == 0) {
+ printf("Applying xattrs for %s", path);
+ for (entry = pat->entries; entry != NULL; entry = entry->next) {
+ printf(" %s = \n", entry->key);
+ fwrite(entry->value, entry->value_len, 1, stdout);
+ puts("\n");
+ ret = sqfs_xattr_writer_add(
+ xwr, entry->key, entry->value, entry->value_len);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+ }
+ }
+ return ret;
+}
diff --git a/bin/gensquashfs/src/fstree_from_dir.c b/bin/gensquashfs/src/fstree_from_dir.c
new file mode 100644
index 0000000..5b3f003
--- /dev/null
+++ b/bin/gensquashfs/src/fstree_from_dir.c
@@ -0,0 +1,493 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * fstree_from_dir.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+#include "mkfs.h"
+
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#if defined(_WIN32) || defined(__WINDOWS__)
+#define UNIX_EPOCH_ON_W32 11644473600UL
+#define W32_TICS_PER_SEC 10000000UL
+
+static sqfs_u32 w32time_to_sqfs_time(const FILETIME *ft)
+{
+ sqfs_u64 w32ts;
+
+ w32ts = ft->dwHighDateTime;
+ w32ts <<= 32UL;
+ w32ts |= ft->dwLowDateTime;
+
+ w32ts /= W32_TICS_PER_SEC;
+
+ if (w32ts <= UNIX_EPOCH_ON_W32)
+ return 0;
+
+ w32ts -= UNIX_EPOCH_ON_W32;
+
+ return (w32ts < 0x0FFFFFFFFUL) ? w32ts : 0xFFFFFFFF;
+}
+
+static int add_node(fstree_t *fs, tree_node_t *root,
+ scan_node_callback cb, void *user,
+ unsigned int flags,
+ const LPWIN32_FIND_DATAW entry)
+{
+ tree_node_t *n;
+ DWORD length;
+
+ if (entry->cFileName[0] == '.') {
+ if (entry->cFileName[1] == '\0')
+ return 0;
+
+ if (entry->cFileName[1] == '.' && entry->cFileName[2] == '\0')
+ return 0;
+ }
+
+ length = WideCharToMultiByte(CP_UTF8, 0, entry->cFileName,
+ -1, NULL, 0, NULL, NULL);
+ if (length <= 0) {
+ w32_perror("converting path to UTF-8");
+ return -1;
+ }
+
+ n = calloc(1, sizeof(*n) + length + 1);
+ if (n == NULL) {
+ fprintf(stderr, "creating tree node: out-of-memory\n");
+ return -1;
+ }
+
+ n->name = (char *)n->payload;
+ WideCharToMultiByte(CP_UTF8, 0, entry->cFileName, -1,
+ n->name, length + 1, NULL, NULL);
+
+ if (entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ if (flags & DIR_SCAN_NO_DIR) {
+ free(n);
+ return 0;
+ }
+
+ n->mode = S_IFDIR | 0755;
+ } else {
+ if (flags & DIR_SCAN_NO_FILE) {
+ free(n);
+ return 0;
+ }
+
+ n->mode = S_IFREG | 0644;
+ }
+
+ if (cb != NULL) {
+ int ret = cb(user, fs, n);
+
+ if (ret != 0) {
+ free(n);
+ return ret < 0 ? ret : 0;
+ }
+ }
+
+ if (flags & DIR_SCAN_KEEP_TIME) {
+ n->mod_time = w32time_to_sqfs_time(&(entry->ftLastWriteTime));
+ } else {
+ n->mod_time = fs->defaults.st_mtime;
+ }
+
+ fstree_insert_sorted(root, n);
+ return 0;
+}
+
+static int scan_dir(fstree_t *fs, tree_node_t *root,
+ const char *path, const WCHAR *wpath,
+ scan_node_callback cb, void *user,
+ unsigned int flags)
+{
+ WIN32_FIND_DATAW entry;
+ HANDLE dirhnd;
+
+ dirhnd = FindFirstFileW(wpath, &entry);
+
+ if (dirhnd == INVALID_HANDLE_VALUE)
+ goto fail_perror;
+
+ do {
+ if (add_node(fs, root, cb, user, flags, &entry))
+ goto fail;
+ } while (FindNextFileW(dirhnd, &entry));
+
+ if (GetLastError() != ERROR_NO_MORE_FILES)
+ goto fail_perror;
+
+ FindClose(dirhnd);
+ return 0;
+fail_perror:
+ w32_perror(path);
+fail:
+ if (dirhnd != INVALID_HANDLE_VALUE)
+ FindClose(dirhnd);
+ return -1;
+}
+
+int fstree_from_dir(fstree_t *fs, tree_node_t *root,
+ const char *path, scan_node_callback cb,
+ void *user, unsigned int flags)
+{
+ WCHAR *wpath = NULL, *new = NULL;
+ size_t len, newlen;
+ tree_node_t *n;
+
+ /* path -> to_wchar(path) + L"\*" */
+ wpath = path_to_windows(path);
+ if (wpath == NULL) {
+ fprintf(stderr, "%s: allocation failure.\n", path);
+ return -1;
+ }
+
+ for (len = 0; wpath[len] != '\0'; ++len)
+ ;
+
+ newlen = len + 1;
+
+ if (len > 0 && wpath[len - 1] != '\\')
+ newlen += 1;
+
+ new = realloc(wpath, sizeof(wpath[0]) * (newlen + 1));
+ if (new == NULL) {
+ fprintf(stderr, "%s: allocation failure.\n", path);
+ goto fail;
+ }
+
+ wpath = new;
+
+ if (len > 0 && wpath[len - 1] != '\\')
+ wpath[len++] = '\\';
+
+ wpath[len++] = '*';
+ wpath[len++] = '\0';
+
+ /* scan directory contents */
+ if (scan_dir(fs, root, path, wpath, cb, user, flags))
+ goto fail;
+
+ free(wpath);
+ wpath = NULL;
+
+ /* recursion step */
+ if (flags & DIR_SCAN_NO_RECURSION)
+ return 0;
+
+ for (n = root->data.dir.children; n != NULL; n = n->next) {
+ if (!S_ISDIR(n->mode))
+ continue;
+
+ if (fstree_from_subdir(fs, n, path, n->name, cb, user, flags))
+ return -1;
+ }
+
+ return 0;
+fail:
+ free(wpath);
+ return -1;
+}
+
+int fstree_from_subdir(fstree_t *fs, tree_node_t *root,
+ const char *path, const char *subdir,
+ scan_node_callback cb, void *user,
+ unsigned int flags)
+{
+ size_t len, plen, slen;
+ WCHAR *wpath = NULL;
+ char *temp = NULL;
+ tree_node_t *n;
+
+ plen = strlen(path);
+ slen = subdir == NULL ? 0 : strlen(subdir);
+
+ if (slen == 0)
+ return fstree_from_dir(fs, root, path, cb, user, flags);
+
+ len = plen + 1 + slen + 2;
+
+ temp = calloc(1, len + 1);
+ if (temp == NULL) {
+ fprintf(stderr, "%s/%s: allocation failure.\n", path, subdir);
+ return -1;
+ }
+
+ memcpy(temp, path, plen);
+ temp[plen] = '/';
+ memcpy(temp + plen + 1, subdir, slen);
+ temp[plen + 1 + slen ] = '/';
+ temp[plen + 1 + slen + 1] = '*';
+ temp[plen + 1 + slen + 2] = '\0';
+
+ wpath = path_to_windows(temp);
+ if (wpath == NULL) {
+ fprintf(stderr, "%s: allocation failure.\n", temp);
+ goto fail;
+ }
+
+ if (scan_dir(fs, root, temp, wpath, cb, user, flags))
+ goto fail;
+
+ free(wpath);
+ wpath = NULL;
+
+ if (flags & DIR_SCAN_NO_RECURSION) {
+ free(temp);
+ return 0;
+ }
+
+ temp[plen + 1 + slen] = '\0';
+
+ for (n = root->data.dir.children; n != NULL; n = n->next) {
+ if (!S_ISDIR(n->mode))
+ continue;
+
+ if (fstree_from_subdir(fs, n, temp, n->name, cb, user, flags))
+ goto fail;
+ }
+
+ free(temp);
+ return 0;
+fail:
+ free(temp);
+ free(wpath);
+ return -1;
+
+}
+#else
+static void discard_node(tree_node_t *root, tree_node_t *n)
+{
+ tree_node_t *it;
+
+ if (n == root->data.dir.children) {
+ root->data.dir.children = n->next;
+ } else {
+ it = root->data.dir.children;
+
+ while (it != NULL && it->next != n)
+ it = it->next;
+
+ if (it != NULL)
+ it->next = n->next;
+ }
+
+ free(n);
+}
+
+static int populate_dir(int dir_fd, fstree_t *fs, tree_node_t *root,
+ dev_t devstart, scan_node_callback cb,
+ void *user, unsigned int flags)
+{
+ char *extra = NULL;
+ struct dirent *ent;
+ int ret, childfd;
+ struct stat sb;
+ tree_node_t *n;
+ DIR *dir;
+
+ dir = fdopendir(dir_fd);
+ if (dir == NULL) {
+ perror("fdopendir");
+ close(dir_fd);
+ return -1;
+ }
+
+ /* XXX: fdopendir can dup and close dir_fd internally
+ and still be compliant with the spec. */
+ dir_fd = dirfd(dir);
+
+ for (;;) {
+ errno = 0;
+ ent = readdir(dir);
+
+ if (ent == NULL) {
+ if (errno) {
+ perror("readdir");
+ goto fail;
+ }
+ break;
+ }
+
+ if (!strcmp(ent->d_name, "..") || !strcmp(ent->d_name, "."))
+ continue;
+
+ if (fstatat(dir_fd, ent->d_name, &sb, AT_SYMLINK_NOFOLLOW)) {
+ perror(ent->d_name);
+ goto fail;
+ }
+
+ switch (sb.st_mode & S_IFMT) {
+ case S_IFSOCK:
+ if (flags & DIR_SCAN_NO_SOCK)
+ continue;
+ break;
+ case S_IFLNK:
+ if (flags & DIR_SCAN_NO_SLINK)
+ continue;
+ break;
+ case S_IFREG:
+ if (flags & DIR_SCAN_NO_FILE)
+ continue;
+ break;
+ case S_IFBLK:
+ if (flags & DIR_SCAN_NO_BLK)
+ continue;
+ break;
+ case S_IFCHR:
+ if (flags & DIR_SCAN_NO_CHR)
+ continue;
+ break;
+ case S_IFIFO:
+ if (flags & DIR_SCAN_NO_FIFO)
+ continue;
+ break;
+ default:
+ break;
+ }
+
+ if ((flags & DIR_SCAN_ONE_FILESYSTEM) && sb.st_dev != devstart)
+ continue;
+
+ if (S_ISLNK(sb.st_mode)) {
+ size_t size;
+
+ if ((sizeof(sb.st_size) > sizeof(size_t)) &&
+ sb.st_size > SIZE_MAX) {
+ errno = EOVERFLOW;
+ goto fail_rdlink;
+ }
+
+ if (SZ_ADD_OV((size_t)sb.st_size, 1, &size)) {
+ errno = EOVERFLOW;
+ goto fail_rdlink;
+ }
+
+ extra = calloc(1, size);
+ if (extra == NULL)
+ goto fail_rdlink;
+
+ if (readlinkat(dir_fd, ent->d_name,
+ extra, (size_t)sb.st_size) < 0) {
+ goto fail_rdlink;
+ }
+
+ extra[sb.st_size] = '\0';
+ }
+
+ if (!(flags & DIR_SCAN_KEEP_TIME))
+ sb.st_mtime = fs->defaults.st_mtime;
+
+ if (S_ISDIR(sb.st_mode) && (flags & DIR_SCAN_NO_DIR)) {
+ n = fstree_get_node_by_path(fs, root, ent->d_name,
+ false, false);
+ if (n == NULL)
+ continue;
+
+ ret = 0;
+ } else {
+ n = fstree_mknode(root, ent->d_name,
+ strlen(ent->d_name), extra, &sb);
+ if (n == NULL) {
+ perror("creating tree node");
+ goto fail;
+ }
+
+ ret = (cb == NULL) ? 0 : cb(user, fs, n);
+ }
+
+ free(extra);
+ extra = NULL;
+
+ if (ret < 0)
+ goto fail;
+
+ if (ret > 0) {
+ discard_node(root, n);
+ continue;
+ }
+
+ if (S_ISDIR(n->mode) && !(flags & DIR_SCAN_NO_RECURSION)) {
+ childfd = openat(dir_fd, n->name, O_DIRECTORY |
+ O_RDONLY | O_CLOEXEC);
+ if (childfd < 0) {
+ perror(n->name);
+ goto fail;
+ }
+
+ if (populate_dir(childfd, fs, n, devstart,
+ cb, user, flags)) {
+ goto fail;
+ }
+ }
+ }
+
+ closedir(dir);
+ return 0;
+fail_rdlink:
+ perror("readlink");
+fail:
+ closedir(dir);
+ free(extra);
+ return -1;
+}
+
+int fstree_from_subdir(fstree_t *fs, tree_node_t *root,
+ const char *path, const char *subdir,
+ scan_node_callback cb, void *user,
+ unsigned int flags)
+{
+ struct stat sb;
+ int fd, subfd;
+
+ if (!S_ISDIR(root->mode)) {
+ fprintf(stderr,
+ "scanning %s/%s into %s: target is not a directory\n",
+ path, subdir == NULL ? "" : subdir, root->name);
+ return -1;
+ }
+
+ fd = open(path, O_DIRECTORY | O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ perror(path);
+ return -1;
+ }
+
+ if (subdir != NULL) {
+ subfd = openat(fd, subdir, O_DIRECTORY | O_RDONLY | O_CLOEXEC);
+
+ if (subfd < 0) {
+ fprintf(stderr, "%s/%s: %s\n", path, subdir,
+ strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+ fd = subfd;
+ }
+
+ if (fstat(fd, &sb)) {
+ fprintf(stderr, "%s/%s: %s\n", path,
+ subdir == NULL ? "" : subdir,
+ strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ return populate_dir(fd, fs, root, sb.st_dev, cb, user, flags);
+}
+
+int fstree_from_dir(fstree_t *fs, tree_node_t *root,
+ const char *path, scan_node_callback cb,
+ void *user, unsigned int flags)
+{
+ return fstree_from_subdir(fs, root, path, NULL, cb, user, flags);
+}
+#endif
diff --git a/bin/gensquashfs/src/fstree_from_file.c b/bin/gensquashfs/src/fstree_from_file.c
new file mode 100644
index 0000000..e26d4b1
--- /dev/null
+++ b/bin/gensquashfs/src/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_drop(fp);
+ return ret;
+}
diff --git a/bin/gensquashfs/src/mkfs.c b/bin/gensquashfs/src/mkfs.c
new file mode 100644
index 0000000..c773dd7
--- /dev/null
+++ b/bin/gensquashfs/src/mkfs.c
@@ -0,0 +1,215 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * mkfs.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "mkfs.h"
+
+static int pack_files(sqfs_block_processor_t *data, fstree_t *fs,
+ options_t *opt)
+{
+ sqfs_u64 filesize;
+ sqfs_file_t *file;
+ tree_node_t *node;
+ const char *path;
+ char *node_path;
+ file_info_t *fi;
+ int flags;
+ int ret;
+
+ if (opt->packdir != NULL && chdir(opt->packdir) != 0) {
+ perror(opt->packdir);
+ return -1;
+ }
+
+ for (fi = fs->files; fi != NULL; fi = fi->next) {
+ if (fi->input_file == NULL) {
+ node = container_of(fi, tree_node_t, data.file);
+
+ node_path = fstree_get_path(node);
+ if (node_path == NULL) {
+ perror("reconstructing file path");
+ return -1;
+ }
+
+ ret = canonicalize_name(node_path);
+ assert(ret == 0);
+
+ path = node_path;
+ } else {
+ node_path = NULL;
+ path = fi->input_file;
+ }
+
+ if (!opt->cfg.quiet)
+ printf("packing %s\n", path);
+
+ file = sqfs_open_file(path, SQFS_FILE_OPEN_READ_ONLY);
+ if (file == NULL) {
+ perror(path);
+ free(node_path);
+ return -1;
+ }
+
+ flags = fi->flags;
+ filesize = file->get_size(file);
+
+ if (opt->no_tail_packing && filesize > opt->cfg.block_size)
+ flags |= SQFS_BLK_DONT_FRAGMENT;
+
+ ret = write_data_from_file(path, data, &fi->inode, file, flags);
+ sqfs_drop(file);
+ free(node_path);
+
+ if (ret)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int relabel_tree_dfs(const char *filename, sqfs_xattr_writer_t *xwr,
+ tree_node_t *n, void *selinux_handle)
+{
+ char *path = fstree_get_path(n);
+ int ret;
+
+ if (path == NULL) {
+ perror("getting absolute node path for SELinux relabeling");
+ return -1;
+ }
+
+ ret = sqfs_xattr_writer_begin(xwr, 0);
+ if (ret) {
+ sqfs_perror(filename, "recording xattr key-value pairs", ret);
+ return -1;
+ }
+
+ if (selinux_relable_node(selinux_handle, xwr, n, path)) {
+ free(path);
+ return -1;
+ }
+
+ ret = sqfs_xattr_writer_end(xwr, &n->xattr_idx);
+ if (ret) {
+ sqfs_perror(filename, "flushing completed key-value pairs",
+ ret);
+ return -1;
+ }
+
+ free(path);
+
+ if (S_ISDIR(n->mode)) {
+ for (n = n->data.dir.children; n != NULL; n = n->next) {
+ if (relabel_tree_dfs(filename, xwr, n, selinux_handle))
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int read_fstree(fstree_t *fs, options_t *opt, sqfs_xattr_writer_t *xwr,
+ void *selinux_handle)
+{
+ int ret;
+
+ ret = fstree_from_file(fs, opt->infile, opt->packdir);
+
+ if (ret == 0 && selinux_handle != NULL)
+ ret = relabel_tree_dfs(opt->cfg.filename, xwr,
+ fs->root, selinux_handle);
+
+ return ret;
+}
+
+static void override_owner_dfs(const options_t *opt, tree_node_t *n)
+{
+ if (opt->force_uid)
+ n->uid = opt->force_uid_value;
+
+ if (opt->force_gid)
+ n->gid = opt->force_gid_value;
+
+ if (S_ISDIR(n->mode)) {
+ for (n = n->data.dir.children; n != NULL; n = n->next)
+ override_owner_dfs(opt, n);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int status = EXIT_FAILURE;
+ istream_t *sortfile = NULL;
+ void *sehnd = NULL;
+ void *xattrmap = NULL;
+ sqfs_writer_t sqfs;
+ options_t opt;
+
+ process_command_line(&opt, argc, argv);
+
+ if (sqfs_writer_init(&sqfs, &opt.cfg))
+ return EXIT_FAILURE;
+
+ if (opt.selinux != NULL) {
+ sehnd = selinux_open_context_file(opt.selinux);
+ if (sehnd == NULL)
+ goto out;
+ }
+ if (opt.xattr_file != NULL) {
+ xattrmap = xattr_open_map_file(opt.xattr_file);
+ if (xattrmap == NULL)
+ goto out;
+ }
+
+ if (opt.sortfile != NULL) {
+ sortfile = istream_open_file(opt.sortfile);
+ if (sortfile == NULL)
+ goto out;
+ }
+
+ if (opt.infile == NULL) {
+ if (fstree_from_dir(&sqfs.fs, sqfs.fs.root, opt.packdir,
+ NULL, NULL, opt.dirscan_flags)) {
+ goto out;
+ }
+ } else {
+ if (read_fstree(&sqfs.fs, &opt, sqfs.xwr, sehnd))
+ goto out;
+ }
+
+ if (opt.force_uid || opt.force_gid)
+ override_owner_dfs(&opt, sqfs.fs.root);
+
+ if (fstree_post_process(&sqfs.fs))
+ goto out;
+
+ if (opt.infile == NULL) {
+ if (xattrs_from_dir(&sqfs.fs, opt.packdir, sehnd, xattrmap,
+ sqfs.xwr, opt.scan_xattr)) {
+ goto out;
+ }
+ }
+
+ if (sortfile != NULL) {
+ if (fstree_sort_files(&sqfs.fs, sortfile))
+ goto out;
+ }
+
+ if (pack_files(sqfs.data, &sqfs.fs, &opt))
+ goto out;
+
+ if (sqfs_writer_finish(&sqfs, &opt.cfg))
+ goto out;
+
+ status = EXIT_SUCCESS;
+out:
+ sqfs_writer_cleanup(&sqfs, status);
+ if (sehnd != NULL)
+ selinux_close_context_file(sehnd);
+ if (sortfile != NULL)
+ sqfs_drop(sortfile);
+ free(opt.packdir);
+ return status;
+}
diff --git a/bin/gensquashfs/src/mkfs.h b/bin/gensquashfs/src/mkfs.h
new file mode 100644
index 0000000..53fb018
--- /dev/null
+++ b/bin/gensquashfs/src/mkfs.h
@@ -0,0 +1,137 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * mkfs.h
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ * Copyright (C) 2022 Enno Boland <mail@eboland.de>
+ */
+#ifndef MKFS_H
+#define MKFS_H
+
+#include "config.h"
+
+#include "common.h"
+#include "fstree.h"
+#include "util/util.h"
+#include "io/file.h"
+
+#ifdef HAVE_SYS_XATTR_H
+#include <sys/xattr.h>
+
+#if defined(__APPLE__) && defined(__MACH__)
+#define llistxattr(path, list, size) \
+ listxattr(path, list, size, XATTR_NOFOLLOW)
+
+#define lgetxattr(path, name, value, size) \
+ getxattr(path, name, value, size, 0, XATTR_NOFOLLOW)
+#endif
+#endif
+
+#ifdef WITH_SELINUX
+#include <selinux/selinux.h>
+#include <selinux/label.h>
+#endif
+
+#include <getopt.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+
+typedef struct {
+ sqfs_writer_cfg_t cfg;
+ unsigned int dirscan_flags;
+ const char *infile;
+ const char *selinux;
+ const char *xattr_file;
+ const char *sortfile;
+ bool no_tail_packing;
+
+ /* copied from command line or constructed from infile argument
+ if not specified. Must be free'd. */
+ char *packdir;
+
+ unsigned int force_uid_value;
+ unsigned int force_gid_value;
+ bool force_uid;
+ bool force_gid;
+
+ bool scan_xattr;
+} options_t;
+
+struct XattrMapEntry {
+ char *key;
+ sqfs_u8 *value;
+ size_t value_len;
+ struct XattrMapEntry *next;
+};
+
+struct XattrMapPattern {
+ char *path;
+ struct XattrMapEntry *entries;
+ struct XattrMapPattern *next;
+};
+
+struct XattrMap {
+ struct XattrMapPattern *patterns;
+};
+
+void process_command_line(options_t *opt, int argc, char **argv);
+
+int xattrs_from_dir(fstree_t *fs, const char *path, void *selinux_handle,
+ void *xattr_map, sqfs_xattr_writer_t *xwr, bool scan_xattr);
+
+void *xattr_open_map_file(const char *path);
+
+int
+xattr_apply_map_file(char *path, void *map, sqfs_xattr_writer_t *xwr);
+
+void xattr_close_map_file(void *xattr_map);
+
+void *selinux_open_context_file(const char *filename);
+
+int selinux_relable_node(void *sehnd, sqfs_xattr_writer_t *xwr,
+ tree_node_t *node, const char *path);
+
+void selinux_close_context_file(void *sehnd);
+
+/*
+ Parses the file format accepted by gensquashfs and produce a file system
+ tree from it. File input paths are interpreted as relative to the current
+ working directory.
+
+ On failure, an error report with filename and line number is written
+ to stderr.
+
+ Returns 0 on success.
+ */
+int fstree_from_file(fstree_t *fs, const char *filename,
+ const char *basepath);
+
+int fstree_from_file_stream(fstree_t *fs, istream_t *file,
+ const char *basepath);
+
+/*
+ Recursively scan a directory to build a file system tree.
+
+ Returns 0 on success, prints to stderr on failure.
+ */
+int fstree_from_dir(fstree_t *fs, tree_node_t *root,
+ const char *path, scan_node_callback cb, void *user,
+ unsigned int flags);
+
+/*
+ Same as fstree_from_dir, but scans a sub-directory inside the specified path.
+
+ Returns 0 on success, prints to stderr on failure.
+ */
+int fstree_from_subdir(fstree_t *fs, tree_node_t *root,
+ const char *path, const char *subdir,
+ scan_node_callback cb, void *user, unsigned int flags);
+
+int fstree_sort_files(fstree_t *fs, istream_t *sortfile);
+
+#endif /* MKFS_H */
diff --git a/bin/gensquashfs/src/options.c b/bin/gensquashfs/src/options.c
new file mode 100644
index 0000000..f263bce
--- /dev/null
+++ b/bin/gensquashfs/src/options.c
@@ -0,0 +1,383 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * options.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "mkfs.h"
+
+enum {
+ ALL_ROOT_OPTION = 1,
+};
+
+static struct option long_opts[] = {
+ { "all-root", no_argument, NULL, ALL_ROOT_OPTION },
+ { "set-uid", required_argument, NULL, 'u' },
+ { "set-gid", required_argument, NULL, 'g' },
+ { "compressor", required_argument, NULL, 'c' },
+ { "block-size", required_argument, NULL, 'b' },
+ { "dev-block-size", required_argument, NULL, 'B' },
+ { "defaults", required_argument, NULL, 'd' },
+ { "comp-extra", required_argument, NULL, 'X' },
+ { "pack-file", required_argument, NULL, 'F' },
+ { "pack-dir", required_argument, NULL, 'D' },
+ { "num-jobs", required_argument, NULL, 'j' },
+ { "queue-backlog", required_argument, NULL, 'Q' },
+ { "keep-time", no_argument, NULL, 'k' },
+#ifdef HAVE_SYS_XATTR_H
+ { "keep-xattr", no_argument, NULL, 'x' },
+#endif
+ { "one-file-system", no_argument, NULL, 'o' },
+ { "exportable", no_argument, NULL, 'e' },
+ { "no-tail-packing", no_argument, NULL, 'T' },
+ { "force", no_argument, NULL, 'f' },
+ { "quiet", no_argument, NULL, 'q' },
+#ifdef WITH_SELINUX
+ { "selinux", required_argument, NULL, 's' },
+#endif
+ { "xattr-file", required_argument, NULL, 'A' },
+ { "sort-file", required_argument, NULL, 'S' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 },
+};
+
+static const char *short_opts = "F:D:X:c:b:B:d:u:g:j:Q:S:A:kxoefqThV"
+#ifdef WITH_SELINUX
+"s:"
+#endif
+#ifdef HAVE_SYS_XATTR_H
+"x"
+#endif
+;
+
+static const char *help_string =
+"Usage: gensquashfs [OPTIONS...] <squashfs-file>\n"
+"\n"
+"Possible options:\n"
+"\n";
+
+static const char *pack_options =
+" --pack-file, -F <file> Use a `gen_init_cpio` style description file.\n"
+" The file format is specified below.\n"
+" If --pack-dir is used, input file paths are\n"
+" relative to the pack directory, otherwise\n"
+" they are relative to the directory the pack\n"
+" file is in.\n"
+" --pack-dir, -D <directory> If --pack-file is used, this is the root path\n"
+" relative to which to read files. If no pack\n"
+" file is specified, pack the contents of the\n"
+" given directory. The directory becomes the\n"
+" file system root.\n"
+"\n"
+" --compressor, -c <name> Select the compressor to use.\n"
+" A list of available compressors is below.\n"
+" --comp-extra, -X <options> A comma separated list of extra options for\n"
+" the selected compressor. Specify 'help' to\n"
+" get a list of available options.\n"
+" --num-jobs, -j <count> Number of compressor jobs to create.\n"
+" --queue-backlog, -Q <count> Maximum number of data blocks in the thread\n"
+" worker queue before the packer starts waiting\n"
+" for the block processors to catch up.\n"
+" Defaults to 10 times the number of jobs.\n"
+" --block-size, -b <size> Block size to use for Squashfs image.\n"
+" Defaults to %u.\n"
+" --dev-block-size, -B <size> Device block size to padd the image to.\n"
+" Defaults to %u.\n"
+" --defaults, -d <options> A comma separated list of default values for\n"
+" implicitly created directories.\n"
+"\n"
+" Possible options:\n"
+" uid=<value> 0 if not set.\n"
+" gid=<value> 0 if not set.\n"
+" mode=<value> 0755 if not set.\n"
+" mtime=<value> 0 if not set.\n"
+"\n"
+" --set-uid, -u <number> Force the owners user ID for ALL inodes to\n"
+" this value, no matter what the pack file or\n"
+" directory entries actually specify.\n"
+" --set-gid, -g <number> Force the owners group ID for ALL inodes to\n"
+" this value, no matter what the pack file or\n"
+" directory entries actually specify.\n"
+" --all-root A short hand for `--set-uid 0 --set-gid 0`.\n"
+"\n";
+
+const char *extra_options =
+" --sort-file, -S <file> Specify a \"sort file\" that can be used to\n"
+" micro manage the order of files during packing\n"
+" and behaviour (compression, fragmentation, ..)\n"
+"\n"
+#ifdef WITH_SELINUX
+" --selinux, -s <file> Specify an SELinux label file to get context\n"
+" attributes from.\n"
+#endif
+" --xattr-file, -A <file> Specify an Xattr file to get extended attributes\n"
+" for loading xattrs\n"
+" --keep-time, -k When using --pack-dir only, use the timestamps\n"
+" from the input files instead of setting\n"
+" defaults on all input paths.\n"
+" --keep-xattr, -x When using --pack-dir only, read and pack the\n"
+" extended attributes from the input files.\n"
+" --one-file-system, -o When using --pack-dir only, stay in local file\n"
+" system and do not cross mount points.\n"
+" --exportable, -e Generate an export table for NFS support.\n"
+" --no-tail-packing, -T Do not perform tail end packing on files that\n"
+" are larger than block size.\n"
+" --force, -f Overwrite the output file if it exists.\n"
+" --quiet, -q Do not print out progress reports.\n"
+" --help, -h Print help text and exit.\n"
+" --version, -V Print version information and exit.\n"
+"\n";
+
+const char *pack_details =
+"Example of a pack file:\n"
+"\n"
+" # A simple squashfs image\n"
+" dir /dev 0755 0 0\n"
+" nod /dev/console 0600 0 0 c 5 1\n"
+" dir /root 0700 0 0\n"
+" \n"
+" # `slink` for symlink, `link` for hard links\n"
+" slink /lib 0777 0 0 /usr/lib\n"
+" link /init 0777 0 0 /sbin/init\n"
+" \n"
+" # Add a file. Input is relative to listing or pack dir.\n"
+" file /sbin/init 0755 0 0 ../init/sbin/init\n"
+" \n"
+" # Read bin/bash, relative to listing or pack dir.\n"
+" # Implicitly create /bin.\n"
+" file /bin/bash 0755 0 0\n"
+" \n"
+" # file name with a space in it.\n"
+" file \"/opt/my app/\\\"special\\\"/data\" 0600 0 0\n"
+" \n"
+" # collect the contents of ./lib and put it under /usr/lib\n"
+" glob /usr/lib 0755 0 0 -type d ./lib\n"
+" glob /usr/lib 0755 0 0 -type f -name \"*.so.*\" ./lib\n"
+" glob /usr/lib 0777 0 0 -type l -name \"*.so.*\" ./lib\n"
+"\n\n";
+
+const char *sort_details =
+"When using a sort file, the specified paths are within the SquashFS image.\n"
+"Files with lower priority are packed first, default priority is 0.\n"
+"The sorting is stable, files with the same priority do not change place\n"
+"relative to each other.\n"
+"\n"
+"Example:\n"
+" # Specify a packing order with file globbing\n"
+" -8000 [glob] bin/*\n"
+" -5000 [glob] lib/*\n"
+"\n"
+" # glob_no_path means * is allowed to match /\n"
+" -1000 [glob_no_path] share/*\n"
+"\n"
+" # Our boot loader needs this\n"
+" -100000 [dont_compress,dont_fragment,nosparse] boot/vmlinuz\n"
+"\n"
+" # For demonstration, a quoted filename and no flags\n"
+" 1337 \"usr/share/my \\\"special\\\" file \"\n"
+"\n\n";
+
+static const char *xattr_details =
+"The format of xattr files tries to immitate the format generated\n"
+"by `getfattr --dump`.\n"
+"\n"
+"Example:\n"
+" # file: dev/\n"
+" security.selinux=\"system_u:object_r:device_t:s0\"\n"
+" user.beverage_preference=0xCAFECAFEDECAFBAD\n"
+"\n"
+" # file: dev/rfkill\n"
+" security.selinux=\"system_u:object_r:wireless_device_t:s0\"\n"
+" system.posix_acl_access=0sSGVsbG8gdGhlcmUgOi0pCg==\n"
+"\n\n";
+
+void process_command_line(options_t *opt, int argc, char **argv)
+{
+ bool have_compressor;
+ int i, ret;
+
+ memset(opt, 0, sizeof(*opt));
+ sqfs_writer_cfg_init(&opt->cfg);
+
+ for (;;) {
+ i = getopt_long(argc, argv, short_opts, long_opts, NULL);
+ if (i == -1)
+ break;
+
+ switch (i) {
+ case ALL_ROOT_OPTION:
+ opt->force_uid_value = 0;
+ opt->force_gid_value = 0;
+ opt->force_uid = true;
+ opt->force_gid = true;
+ break;
+ case 'u':
+ opt->force_uid_value = strtol(optarg, NULL, 0);
+ opt->force_uid = true;
+ break;
+ case 'g':
+ opt->force_gid_value = strtol(optarg, NULL, 0);
+ opt->force_gid = true;
+ break;
+ case 'T':
+ opt->no_tail_packing = true;
+ break;
+ case 'c':
+ have_compressor = true;
+ ret = sqfs_compressor_id_from_name(optarg);
+
+ if (ret < 0) {
+ have_compressor = false;
+#ifdef WITH_LZO
+ if (opt->cfg.comp_id == SQFS_COMP_LZO)
+ have_compressor = true;
+#endif
+ }
+
+ if (!have_compressor) {
+ fprintf(stderr, "Unsupported compressor '%s'\n",
+ optarg);
+ exit(EXIT_FAILURE);
+ }
+
+ opt->cfg.comp_id = ret;
+ break;
+ case 'b':
+ if (parse_size("Block size", &opt->cfg.block_size,
+ optarg, 0)) {
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'j':
+ opt->cfg.num_jobs = strtol(optarg, NULL, 0);
+ break;
+ case 'Q':
+ opt->cfg.max_backlog = strtol(optarg, NULL, 0);
+ break;
+ case 'B':
+ if (parse_size("Device block size",
+ &opt->cfg.devblksize, optarg, 0)) {
+ exit(EXIT_FAILURE);
+ }
+ if (opt->cfg.devblksize < 1024) {
+ fputs("Device block size must be at "
+ "least 1024\n", stderr);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'd':
+ opt->cfg.fs_defaults = optarg;
+ break;
+ case 'k':
+ opt->dirscan_flags |= DIR_SCAN_KEEP_TIME;
+ break;
+#ifdef HAVE_SYS_XATTR_H
+ case 'x':
+ opt->scan_xattr = true;
+ break;
+#endif
+ case 'o':
+ opt->dirscan_flags |= DIR_SCAN_ONE_FILESYSTEM;
+ break;
+ case 'e':
+ opt->cfg.exportable = true;
+ break;
+ case 'f':
+ opt->cfg.outmode |= SQFS_FILE_OPEN_OVERWRITE;
+ break;
+ case 'q':
+ opt->cfg.quiet = true;
+ break;
+ case 'X':
+ opt->cfg.comp_extra = optarg;
+ break;
+ case 'F':
+ opt->infile = optarg;
+ break;
+ case 'D':
+ free(opt->packdir);
+ opt->packdir = strdup(optarg);
+ if (opt->packdir == NULL) {
+ perror(optarg);
+ exit(EXIT_FAILURE);
+ }
+ break;
+#ifdef WITH_SELINUX
+ case 's':
+ opt->selinux = optarg;
+ break;
+#endif
+ case 'A':
+ opt->xattr_file = optarg;
+ break;
+ case 'S':
+ opt->sortfile = optarg;
+ break;
+ case 'h':
+ fputs(help_string, stdout);
+ printf(pack_options, SQFS_DEFAULT_BLOCK_SIZE,
+ SQFS_DEVBLK_SIZE);
+ fputs(extra_options, stdout);
+ fputs(pack_details, stdout);
+ fputs(sort_details, stdout);
+ fputs(xattr_details, stdout);
+ compressor_print_available();
+ exit(EXIT_SUCCESS);
+ case 'V':
+ print_version("gensquashfs");
+ exit(EXIT_SUCCESS);
+ default:
+ goto fail_arg;
+ }
+ }
+
+ if (opt->cfg.num_jobs < 1)
+ opt->cfg.num_jobs = 1;
+
+ if (opt->cfg.max_backlog < 1)
+ opt->cfg.max_backlog = 10 * opt->cfg.num_jobs;
+
+ if (opt->cfg.comp_extra != NULL &&
+ strcmp(opt->cfg.comp_extra, "help") == 0) {
+ compressor_print_help(opt->cfg.comp_id);
+ exit(EXIT_SUCCESS);
+ }
+
+ if (opt->infile == NULL && opt->packdir == NULL) {
+ fputs("No input file or directory specified.\n", stderr);
+ goto fail_arg;
+ }
+
+ if (optind >= argc) {
+ fputs("No output file specified.\n", stderr);
+ goto fail_arg;
+ }
+
+ opt->cfg.filename = argv[optind++];
+
+ if (optind < argc) {
+ fputs("Unknown extra arguments specified.\n", stderr);
+ goto fail_arg;
+ }
+
+ /* construct packdir if not specified */
+ if (opt->packdir == NULL && opt->infile != NULL) {
+ const char *split = strrchr(opt->infile, '/');
+
+ if (split != NULL) {
+ opt->packdir = strndup(opt->infile,
+ split - opt->infile);
+
+ if (opt->packdir == NULL) {
+ perror("constructing input directory path");
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+ return;
+fail_arg:
+ fputs("Try `gensquashfs --help' for more information.\n", stderr);
+ free(opt->packdir);
+ exit(EXIT_FAILURE);
+}
diff --git a/bin/gensquashfs/src/selinux.c b/bin/gensquashfs/src/selinux.c
new file mode 100644
index 0000000..678723b
--- /dev/null
+++ b/bin/gensquashfs/src/selinux.c
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * selinux.c
+ *
+ * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "mkfs.h"
+
+#define XATTR_NAME_SELINUX "security.selinux"
+#define XATTR_VALUE_SELINUX "system_u:object_r:unlabeled_t:s0"
+
+#ifdef WITH_SELINUX
+int selinux_relable_node(void *sehnd, sqfs_xattr_writer_t *xwr,
+ tree_node_t *node, const char *path)
+{
+ char *context = NULL;
+ int ret;
+
+ if (selabel_lookup(sehnd, &context, path, node->mode) < 0) {
+ context = strdup(XATTR_VALUE_SELINUX);
+ if (context == NULL)
+ goto fail;
+ }
+
+ ret = sqfs_xattr_writer_add(xwr, XATTR_NAME_SELINUX,
+ context, strlen(context));
+ free(context);
+
+ if (ret)
+ sqfs_perror(node->name, "storing SELinux xattr", ret);
+
+ return ret;
+fail:
+ perror("relabeling files");
+ return -1;
+}
+
+void *selinux_open_context_file(const char *filename)
+{
+ struct selabel_handle *sehnd;
+ struct selinux_opt seopts[] = {
+ { SELABEL_OPT_PATH, filename },
+ };
+
+ sehnd = selabel_open(SELABEL_CTX_FILE, seopts, 1);
+ if (sehnd == NULL)
+ perror(filename);
+
+ return sehnd;
+}
+
+void selinux_close_context_file(void *sehnd)
+{
+ selabel_close(sehnd);
+}
+#else
+int selinux_relable_node(void *sehnd, sqfs_xattr_writer_t *xwr,
+ tree_node_t *node, const char *path)
+{
+ (void)sehnd; (void)xwr; (void)node; (void)path;
+ fputs("Built without SELinux support, cannot add SELinux labels\n",
+ stderr);
+ return -1;
+}
+
+void *selinux_open_context_file(const char *filename)
+{
+ (void)filename;
+ fputs("Built without SELinux support, cannot open contexts file\n",
+ stderr);
+ return NULL;
+}
+
+void selinux_close_context_file(void *sehnd)
+{
+ (void)sehnd;
+}
+#endif
diff --git a/bin/gensquashfs/src/sort_by_file.c b/bin/gensquashfs/src/sort_by_file.c
new file mode 100644
index 0000000..a555718
--- /dev/null
+++ b/bin/gensquashfs/src/sort_by_file.c
@@ -0,0 +1,368 @@
+/* SPDX-License-Identifier: GPL-3.0-or-later */
+/*
+ * sort_by_file.c
+ *
+ * Copyright (C) 2021 David Oberhollenzer <goliath@infraroot.at>
+ */
+#include "config.h"
+
+#include "util/util.h"
+#include "fstree.h"
+#include "mkfs.h"
+
+#include "sqfs/block.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+static int decode_priority(const char *filename, size_t line_no,
+ char *line, sqfs_s64 *priority)
+{
+ bool negative = false;
+ size_t i = 0;
+
+ if (line[0] == '-') {
+ negative = true;
+ i = 1;
+ }
+
+ if (!isdigit(line[i]))
+ goto fail_number;
+
+ *priority = 0;
+
+ for (; isdigit(line[i]); ++i) {
+ sqfs_s64 x = line[i] - '0';
+
+ if ((*priority) >= ((0x7FFFFFFFFFFFFFFFL - x) / 10L))
+ goto fail_ov;
+
+ (*priority) = (*priority) * 10 + x;
+ }
+
+ if (!isspace(line[i]))
+ goto fail_filename;
+
+ while (isspace(line[i]))
+ ++i;
+
+ if (line[i] == '\0')
+ goto fail_filename;
+
+ if (negative)
+ (*priority) = -(*priority);
+
+ memmove(line, line + i, strlen(line + i) + 1);
+ return 0;
+fail_number:
+ fprintf(stderr, "%s: " PRI_SZ ": Line must start with "
+ "numeric sort priority.\n",
+ filename, line_no);
+ return -1;
+fail_ov:
+ fprintf(stderr, "%s: " PRI_SZ ": Numeric overflow in sort priority.\n",
+ filename, line_no);
+ return -1;
+fail_filename:
+ fprintf(stderr, "%s: " PRI_SZ ": Expacted `<space> <filename>` "
+ "after sort priority.\n",
+ filename, line_no);
+ return -1;
+}
+
+static int decode_filename(const char *filename, size_t line_no, char *buffer)
+{
+ char *src, *dst;
+
+ if (buffer[0] == '"') {
+ src = buffer + 1;
+ dst = buffer;
+
+ for (;;) {
+ if (src[0] == '\0')
+ goto fail_match;
+
+ if (src[0] == '"') {
+ ++src;
+ break;
+ }
+
+ if (src[0] == '\\') {
+ switch (src[1]) {
+ case '\\':
+ *(dst++) = '\\';
+ src += 2;
+ break;
+ case '"':
+ *(dst++) = '"';
+ src += 2;
+ break;
+ default:
+ goto fail_escape;
+ }
+ } else {
+ *(dst++) = *(src++);
+ }
+ }
+
+ if (*src != '\0')
+ return -1;
+ }
+
+ if (canonicalize_name(buffer))
+ goto fail_canon;
+ return 0;
+fail_canon:
+ fprintf(stderr, "%s: " PRI_SZ ": Malformed filename.\n",
+ filename, line_no);
+ return -1;
+fail_escape:
+ fprintf(stderr, "%s: " PRI_SZ ": Unknown escape sequence `\\%c` "
+ "in filename.\n", filename, line_no, src[1]);
+ return -1;
+fail_match:
+ fprintf(stderr, "%s: " PRI_SZ ": Unmatched '\"' in filename.\n",
+ filename, line_no);
+ return -1;
+}
+
+static int decode_flags(const char *filename, size_t line_no, bool *do_glob,
+ bool *path_glob, int *flags, char *line)
+{
+ char *start = line;
+
+ *do_glob = false;
+ *path_glob = false;
+ *flags = 0;
+
+ if (*(line++) != '[')
+ return 0;
+
+ for (;;) {
+ while (isspace(*line))
+ ++line;
+
+ if (*line == ']') {
+ ++line;
+ break;
+ }
+
+ if (strncmp(line, "glob_no_path", 12) == 0) {
+ line += 12;
+ *do_glob = true;
+ *path_glob = false;
+ } else if (strncmp(line, "glob", 4) == 0) {
+ line += 4;
+ *do_glob = true;
+ *path_glob = true;
+ } else if (strncmp(line, "dont_fragment", 13) == 0) {
+ line += 13;
+ (*flags) |= SQFS_BLK_DONT_FRAGMENT;
+ } else if (strncmp(line, "align", 5) == 0) {
+ line += 5;
+ (*flags) |= SQFS_BLK_ALIGN;
+ } else if (strncmp(line, "dont_compress", 13) == 0) {
+ line += 13;
+ (*flags) |= SQFS_BLK_DONT_COMPRESS;
+ } else if (strncmp(line, "dont_deduplicate", 16) == 0) {
+ line += 16;
+ (*flags) |= SQFS_BLK_DONT_DEDUPLICATE;
+ } else if (strncmp(line, "nosparse", 8) == 0) {
+ line += 8;
+ (*flags) |= SQFS_BLK_IGNORE_SPARSE;
+ } else {
+ goto fail_flag;
+ }
+
+ while (isspace(*line))
+ ++line;
+
+ if (*line == ']') {
+ ++line;
+ break;
+ }
+
+ if (*(line++) != ',')
+ goto fail_sep;
+ }
+
+ if (!isspace(*line))
+ goto fail_fname;
+
+ while (isspace(*line))
+ ++line;
+
+ memmove(start, line, strlen(line) + 1);
+ return 0;
+fail_fname:
+ fprintf(stderr, "%s: " PRI_SZ ": Expected `<space> <filename>` "
+ "after flag list.\n", filename, line_no);
+ return -1;
+fail_sep:
+ fprintf(stderr, "%s: " PRI_SZ ": Unexpected '%c' after flag.\n",
+ filename, line_no, *line);
+ return -1;
+fail_flag:
+ fprintf(stderr, "%s: " PRI_SZ ": Unknown flag `%.3s...`.\n",
+ filename, line_no, line);
+ return -1;
+}
+
+static void sort_file_list(fstree_t *fs)
+{
+ file_info_t *out = NULL, *out_last = NULL;
+
+ while (fs->files != NULL) {
+ sqfs_s64 lowest = fs->files->priority;
+ file_info_t *it, *prev;
+
+ for (it = fs->files; it != NULL; it = it->next) {
+ if (it->priority < lowest)
+ lowest = it->priority;
+ }
+
+ it = fs->files;
+ prev = NULL;
+
+ while (it != NULL) {
+ if (it->priority != lowest) {
+ prev = it;
+ it = it->next;
+ continue;
+ }
+
+ if (prev == NULL) {
+ fs->files = it->next;
+ } else {
+ prev->next = it->next;
+ }
+
+ if (out == NULL) {
+ out = it;
+ } else {
+ out_last->next = it;
+ }
+
+ out_last = it;
+ it = it->next;
+ out_last->next = NULL;
+ }
+ }
+
+ fs->files = out;
+}
+
+int fstree_sort_files(fstree_t *fs, istream_t *sortfile)
+{
+ const char *filename;
+ size_t line_num = 1;
+ file_info_t *it;
+
+ for (it = fs->files; it != NULL; it = it->next) {
+ it->priority = 0;
+ it->flags = 0;
+ it->already_matched = false;
+ }
+
+ filename = istream_get_filename(sortfile);
+
+ for (;;) {
+ bool do_glob, path_glob, have_match;
+ char *line = NULL;
+ sqfs_s64 priority;
+ int ret, flags;
+
+ ret = istream_get_line(sortfile, &line, &line_num,
+ ISTREAM_LINE_LTRIM |
+ ISTREAM_LINE_RTRIM |
+ ISTREAM_LINE_SKIP_EMPTY);
+ if (ret != 0) {
+ free(line);
+ if (ret < 0)
+ return -1;
+ break;
+ }
+
+ if (line[0] == '#') {
+ free(line);
+ continue;
+ }
+
+ if (decode_priority(filename, line_num, line, &priority)) {
+ free(line);
+ return -1;
+ }
+
+ if (decode_flags(filename, line_num, &do_glob, &path_glob,
+ &flags, line)) {
+ free(line);
+ return -1;
+ }
+
+ if (decode_filename(filename, line_num, line)) {
+ free(line);
+ return -1;
+ }
+
+ have_match = false;
+
+ for (it = fs->files; it != NULL; it = it->next) {
+ tree_node_t *node;
+ char *path;
+
+ if (it->already_matched)
+ continue;
+
+ node = container_of(it, tree_node_t, data.file);
+ path = fstree_get_path(node);
+ if (path == NULL) {
+ fprintf(stderr, "%s: " PRI_SZ ": out-of-memory\n",
+ filename, line_num);
+ free(line);
+ return -1;
+ }
+
+ if (canonicalize_name(path)) {
+ fprintf(stderr,
+ "%s: " PRI_SZ ": [BUG] error "
+ "reconstructing node path\n",
+ filename, line_num);
+ free(line);
+ free(path);
+ return -1;
+ }
+
+ if (do_glob) {
+ ret = fnmatch(line, path,
+ path_glob ? FNM_PATHNAME : 0);
+
+ } else {
+ ret = strcmp(path, line);
+ }
+
+ free(path);
+
+ if (ret == 0) {
+ have_match = true;
+ it->flags = flags;
+ it->priority = priority;
+ it->already_matched = true;
+
+ if (!do_glob)
+ break;
+ }
+ }
+
+ if (!have_match) {
+ fprintf(stderr, "WARNING: %s: " PRI_SZ ": no match "
+ "for '%s'.\n",
+ filename, line_num, line);
+ }
+
+ free(line);
+ }
+
+ sort_file_list(fs);
+ return 0;
+}