From cdccc69c62579b0c13b35fad0728079652b8f3c9 Mon Sep 17 00:00:00 2001 From: David Oberhollenzer <david.oberhollenzer@sigma-star.at> Date: Tue, 31 Jan 2023 11:21:30 +0100 Subject: Move library source into src sub-directory Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at> --- bin/gensquashfs/Makemodule.am | 13 +- bin/gensquashfs/dirscan_xattr.c | 220 ------------ bin/gensquashfs/filemap_xattr.c | 254 -------------- bin/gensquashfs/fstree_from_dir.c | 493 --------------------------- bin/gensquashfs/fstree_from_file.c | 591 --------------------------------- bin/gensquashfs/mkfs.c | 215 ------------ bin/gensquashfs/mkfs.h | 137 -------- bin/gensquashfs/options.c | 383 --------------------- bin/gensquashfs/selinux.c | 78 ----- bin/gensquashfs/sort_by_file.c | 368 -------------------- bin/gensquashfs/src/dirscan_xattr.c | 220 ++++++++++++ bin/gensquashfs/src/filemap_xattr.c | 254 ++++++++++++++ bin/gensquashfs/src/fstree_from_dir.c | 493 +++++++++++++++++++++++++++ bin/gensquashfs/src/fstree_from_file.c | 591 +++++++++++++++++++++++++++++++++ bin/gensquashfs/src/mkfs.c | 215 ++++++++++++ bin/gensquashfs/src/mkfs.h | 137 ++++++++ bin/gensquashfs/src/options.c | 383 +++++++++++++++++++++ bin/gensquashfs/src/selinux.c | 78 +++++ bin/gensquashfs/src/sort_by_file.c | 368 ++++++++++++++++++++ bin/rdsquashfs/Makemodule.am | 10 +- bin/rdsquashfs/describe.c | 139 -------- bin/rdsquashfs/dump_xattrs.c | 120 ------- bin/rdsquashfs/fill_files.c | 186 ----------- bin/rdsquashfs/list_files.c | 158 --------- bin/rdsquashfs/options.c | 226 ------------- bin/rdsquashfs/rdsquashfs.c | 275 --------------- bin/rdsquashfs/rdsquashfs.h | 82 ----- bin/rdsquashfs/restore_fstree.c | 336 ------------------- bin/rdsquashfs/src/describe.c | 139 ++++++++ bin/rdsquashfs/src/dump_xattrs.c | 120 +++++++ bin/rdsquashfs/src/fill_files.c | 186 +++++++++++ bin/rdsquashfs/src/list_files.c | 158 +++++++++ bin/rdsquashfs/src/options.c | 226 +++++++++++++ bin/rdsquashfs/src/rdsquashfs.c | 275 +++++++++++++++ bin/rdsquashfs/src/rdsquashfs.h | 82 +++++ bin/rdsquashfs/src/restore_fstree.c | 336 +++++++++++++++++++ bin/rdsquashfs/src/stat.c | 187 +++++++++++ bin/rdsquashfs/stat.c | 187 ----------- bin/sqfs2tar/Makemodule.am | 6 +- bin/sqfs2tar/options.c | 212 ------------ bin/sqfs2tar/sqfs2tar.c | 274 --------------- bin/sqfs2tar/sqfs2tar.h | 56 ---- bin/sqfs2tar/src/options.c | 212 ++++++++++++ bin/sqfs2tar/src/sqfs2tar.c | 274 +++++++++++++++ bin/sqfs2tar/src/sqfs2tar.h | 56 ++++ bin/sqfs2tar/src/write_tree.c | 209 ++++++++++++ bin/sqfs2tar/src/xattr.c | 91 +++++ bin/sqfs2tar/write_tree.c | 209 ------------ bin/sqfs2tar/xattr.c | 91 ----- bin/sqfsdiff/Makemodule.am | 10 +- bin/sqfsdiff/compare_dir.c | 94 ------ bin/sqfsdiff/compare_files.c | 72 ---- bin/sqfsdiff/extract.c | 58 ---- bin/sqfsdiff/node_compare.c | 206 ------------ bin/sqfsdiff/options.c | 131 -------- bin/sqfsdiff/sqfsdiff.c | 167 ---------- bin/sqfsdiff/sqfsdiff.h | 73 ---- bin/sqfsdiff/src/compare_dir.c | 94 ++++++ bin/sqfsdiff/src/compare_files.c | 72 ++++ bin/sqfsdiff/src/extract.c | 58 ++++ bin/sqfsdiff/src/node_compare.c | 206 ++++++++++++ bin/sqfsdiff/src/options.c | 131 ++++++++ bin/sqfsdiff/src/sqfsdiff.c | 167 ++++++++++ bin/sqfsdiff/src/sqfsdiff.h | 73 ++++ bin/sqfsdiff/src/super.c | 125 +++++++ bin/sqfsdiff/src/util.c | 27 ++ bin/sqfsdiff/super.c | 125 ------- bin/sqfsdiff/util.c | 27 -- bin/tar2sqfs/Makemodule.am | 4 +- bin/tar2sqfs/options.c | 257 -------------- bin/tar2sqfs/process_tarball.c | 346 ------------------- bin/tar2sqfs/src/options.c | 257 ++++++++++++++ bin/tar2sqfs/src/process_tarball.c | 346 +++++++++++++++++++ bin/tar2sqfs/src/tar2sqfs.c | 104 ++++++ bin/tar2sqfs/src/tar2sqfs.h | 39 +++ bin/tar2sqfs/tar2sqfs.c | 104 ------ bin/tar2sqfs/tar2sqfs.h | 39 --- 77 files changed, 7010 insertions(+), 7011 deletions(-) delete mode 100644 bin/gensquashfs/dirscan_xattr.c delete mode 100644 bin/gensquashfs/filemap_xattr.c delete mode 100644 bin/gensquashfs/fstree_from_dir.c delete mode 100644 bin/gensquashfs/fstree_from_file.c delete mode 100644 bin/gensquashfs/mkfs.c delete mode 100644 bin/gensquashfs/mkfs.h delete mode 100644 bin/gensquashfs/options.c delete mode 100644 bin/gensquashfs/selinux.c delete mode 100644 bin/gensquashfs/sort_by_file.c create mode 100644 bin/gensquashfs/src/dirscan_xattr.c create mode 100644 bin/gensquashfs/src/filemap_xattr.c create mode 100644 bin/gensquashfs/src/fstree_from_dir.c create mode 100644 bin/gensquashfs/src/fstree_from_file.c create mode 100644 bin/gensquashfs/src/mkfs.c create mode 100644 bin/gensquashfs/src/mkfs.h create mode 100644 bin/gensquashfs/src/options.c create mode 100644 bin/gensquashfs/src/selinux.c create mode 100644 bin/gensquashfs/src/sort_by_file.c delete mode 100644 bin/rdsquashfs/describe.c delete mode 100644 bin/rdsquashfs/dump_xattrs.c delete mode 100644 bin/rdsquashfs/fill_files.c delete mode 100644 bin/rdsquashfs/list_files.c delete mode 100644 bin/rdsquashfs/options.c delete mode 100644 bin/rdsquashfs/rdsquashfs.c delete mode 100644 bin/rdsquashfs/rdsquashfs.h delete mode 100644 bin/rdsquashfs/restore_fstree.c create mode 100644 bin/rdsquashfs/src/describe.c create mode 100644 bin/rdsquashfs/src/dump_xattrs.c create mode 100644 bin/rdsquashfs/src/fill_files.c create mode 100644 bin/rdsquashfs/src/list_files.c create mode 100644 bin/rdsquashfs/src/options.c create mode 100644 bin/rdsquashfs/src/rdsquashfs.c create mode 100644 bin/rdsquashfs/src/rdsquashfs.h create mode 100644 bin/rdsquashfs/src/restore_fstree.c create mode 100644 bin/rdsquashfs/src/stat.c delete mode 100644 bin/rdsquashfs/stat.c delete mode 100644 bin/sqfs2tar/options.c delete mode 100644 bin/sqfs2tar/sqfs2tar.c delete mode 100644 bin/sqfs2tar/sqfs2tar.h create mode 100644 bin/sqfs2tar/src/options.c create mode 100644 bin/sqfs2tar/src/sqfs2tar.c create mode 100644 bin/sqfs2tar/src/sqfs2tar.h create mode 100644 bin/sqfs2tar/src/write_tree.c create mode 100644 bin/sqfs2tar/src/xattr.c delete mode 100644 bin/sqfs2tar/write_tree.c delete mode 100644 bin/sqfs2tar/xattr.c delete mode 100644 bin/sqfsdiff/compare_dir.c delete mode 100644 bin/sqfsdiff/compare_files.c delete mode 100644 bin/sqfsdiff/extract.c delete mode 100644 bin/sqfsdiff/node_compare.c delete mode 100644 bin/sqfsdiff/options.c delete mode 100644 bin/sqfsdiff/sqfsdiff.c delete mode 100644 bin/sqfsdiff/sqfsdiff.h create mode 100644 bin/sqfsdiff/src/compare_dir.c create mode 100644 bin/sqfsdiff/src/compare_files.c create mode 100644 bin/sqfsdiff/src/extract.c create mode 100644 bin/sqfsdiff/src/node_compare.c create mode 100644 bin/sqfsdiff/src/options.c create mode 100644 bin/sqfsdiff/src/sqfsdiff.c create mode 100644 bin/sqfsdiff/src/sqfsdiff.h create mode 100644 bin/sqfsdiff/src/super.c create mode 100644 bin/sqfsdiff/src/util.c delete mode 100644 bin/sqfsdiff/super.c delete mode 100644 bin/sqfsdiff/util.c delete mode 100644 bin/tar2sqfs/options.c delete mode 100644 bin/tar2sqfs/process_tarball.c create mode 100644 bin/tar2sqfs/src/options.c create mode 100644 bin/tar2sqfs/src/process_tarball.c create mode 100644 bin/tar2sqfs/src/tar2sqfs.c create mode 100644 bin/tar2sqfs/src/tar2sqfs.h delete mode 100644 bin/tar2sqfs/tar2sqfs.c delete mode 100644 bin/tar2sqfs/tar2sqfs.h (limited to 'bin') diff --git a/bin/gensquashfs/Makemodule.am b/bin/gensquashfs/Makemodule.am index c6a98a2..7edc39a 100644 --- a/bin/gensquashfs/Makemodule.am +++ b/bin/gensquashfs/Makemodule.am @@ -1,10 +1,9 @@ -gensquashfs_SOURCES = bin/gensquashfs/mkfs.c bin/gensquashfs/mkfs.h -gensquashfs_SOURCES += bin/gensquashfs/options.c bin/gensquashfs/selinux.c -gensquashfs_SOURCES += bin/gensquashfs/dirscan_xattr.c -gensquashfs_SOURCES += bin/gensquashfs/filemap_xattr.c -gensquashfs_SOURCES += bin/gensquashfs/fstree_from_file.c -gensquashfs_SOURCES += bin/gensquashfs/fstree_from_dir.c -gensquashfs_SOURCES += bin/gensquashfs/sort_by_file.c +gensquashfs_SOURCES = bin/gensquashfs/src/mkfs.c bin/gensquashfs/src/mkfs.h \ + bin/gensquashfs/src/options.c bin/gensquashfs/src/selinux.c \ + bin/gensquashfs/src/dirscan_xattr.c bin/gensquashfs/src/filemap_xattr.c\ + bin/gensquashfs/src/fstree_from_file.c \ + bin/gensquashfs/src/fstree_from_dir.c \ + bin/gensquashfs/src/sort_by_file.c gensquashfs_LDADD = libcommon.a libsquashfs.la libfstree.a libio.a gensquashfs_LDADD += libutil.a libcompat.a $(LZO_LIBS) $(PTHREAD_LIBS) gensquashfs_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/bin/gensquashfs/dirscan_xattr.c b/bin/gensquashfs/dirscan_xattr.c deleted file mode 100644 index 7d4e552..0000000 --- a/bin/gensquashfs/dirscan_xattr.c +++ /dev/null @@ -1,220 +0,0 @@ -/* 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/filemap_xattr.c b/bin/gensquashfs/filemap_xattr.c deleted file mode 100644 index dd76b50..0000000 --- a/bin/gensquashfs/filemap_xattr.c +++ /dev/null @@ -1,254 +0,0 @@ -/* 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/fstree_from_dir.c b/bin/gensquashfs/fstree_from_dir.c deleted file mode 100644 index 5b3f003..0000000 --- a/bin/gensquashfs/fstree_from_dir.c +++ /dev/null @@ -1,493 +0,0 @@ -/* 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/fstree_from_file.c b/bin/gensquashfs/fstree_from_file.c deleted file mode 100644 index e26d4b1..0000000 --- a/bin/gensquashfs/fstree_from_file.c +++ /dev/null @@ -1,591 +0,0 @@ -/* 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/mkfs.c b/bin/gensquashfs/mkfs.c deleted file mode 100644 index c773dd7..0000000 --- a/bin/gensquashfs/mkfs.c +++ /dev/null @@ -1,215 +0,0 @@ -/* 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/mkfs.h b/bin/gensquashfs/mkfs.h deleted file mode 100644 index 53fb018..0000000 --- a/bin/gensquashfs/mkfs.h +++ /dev/null @@ -1,137 +0,0 @@ -/* 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/options.c b/bin/gensquashfs/options.c deleted file mode 100644 index f263bce..0000000 --- a/bin/gensquashfs/options.c +++ /dev/null @@ -1,383 +0,0 @@ -/* 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/selinux.c b/bin/gensquashfs/selinux.c deleted file mode 100644 index 678723b..0000000 --- a/bin/gensquashfs/selinux.c +++ /dev/null @@ -1,78 +0,0 @@ -/* 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/sort_by_file.c b/bin/gensquashfs/sort_by_file.c deleted file mode 100644 index a555718..0000000 --- a/bin/gensquashfs/sort_by_file.c +++ /dev/null @@ -1,368 +0,0 @@ -/* 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; -} 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; +} diff --git a/bin/rdsquashfs/Makemodule.am b/bin/rdsquashfs/Makemodule.am index 1ff9c60..f8f9d3d 100644 --- a/bin/rdsquashfs/Makemodule.am +++ b/bin/rdsquashfs/Makemodule.am @@ -1,8 +1,8 @@ -rdsquashfs_SOURCES = bin/rdsquashfs/rdsquashfs.c bin/rdsquashfs/rdsquashfs.h -rdsquashfs_SOURCES += bin/rdsquashfs/list_files.c bin/rdsquashfs/options.c -rdsquashfs_SOURCES += bin/rdsquashfs/restore_fstree.c bin/rdsquashfs/describe.c -rdsquashfs_SOURCES += bin/rdsquashfs/fill_files.c bin/rdsquashfs/dump_xattrs.c -rdsquashfs_SOURCES += bin/rdsquashfs/stat.c +rdsquashfs_SOURCES = bin/rdsquashfs/src/rdsquashfs.c \ + bin/rdsquashfs/src/rdsquashfs.h bin/rdsquashfs/src/list_files.c \ + bin/rdsquashfs/src/options.c bin/rdsquashfs/src/restore_fstree.c \ + bin/rdsquashfs/src/describe.c bin/rdsquashfs/src/fill_files.c \ + bin/rdsquashfs/src/dump_xattrs.c bin/rdsquashfs/src/stat.c rdsquashfs_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) rdsquashfs_LDADD = libcommon.a libio.a libcompat.a libutil.a libsquashfs.la rdsquashfs_LDADD += libfstree.a $(LZO_LIBS) $(PTHREAD_LIBS) diff --git a/bin/rdsquashfs/describe.c b/bin/rdsquashfs/describe.c deleted file mode 100644 index 540b126..0000000 --- a/bin/rdsquashfs/describe.c +++ /dev/null @@ -1,139 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * describe.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "rdsquashfs.h" - -static int print_name(const sqfs_tree_node_t *n, bool dont_escape) -{ - char *start, *ptr, *name; - int ret; - - ret = sqfs_tree_node_get_path(n, &name); - if (ret != 0) { - sqfs_perror(NULL, "Recovering file path of tree node", ret); - return -1; - } - - if (canonicalize_name(name) != 0) { - fprintf(stderr, "Error sanitizing file path '%s'\n", name); - sqfs_free(name); - return -1; - } - - if (dont_escape || (strchr(name, ' ') == NULL && - strchr(name, '"') == NULL)) { - fputs(name, stdout); - } else { - fputc('"', stdout); - - ptr = strchr(name, '"'); - - if (ptr != NULL) { - start = name; - - do { - fwrite(start, 1, ptr - start, stdout); - fputs("\\\"", stdout); - start = ptr + 1; - ptr = strchr(start, '"'); - } while (ptr != NULL); - - fputs(start, stdout); - } else { - fputs(name, stdout); - } - - fputc('"', stdout); - } - - sqfs_free(name); - return 0; -} - -static void print_perm(const sqfs_tree_node_t *n) -{ - printf(" 0%o %u %u", (unsigned int)n->inode->base.mode & (~S_IFMT), - n->uid, n->gid); -} - -static int print_simple(const char *type, const sqfs_tree_node_t *n, - const char *extra) -{ - printf("%s ", type); - if (print_name(n, false)) - return -1; - print_perm(n); - if (extra != NULL) - printf(" %s", extra); - fputc('\n', stdout); - return 0; -} - -int describe_tree(const sqfs_tree_node_t *root, const char *unpack_root) -{ - const sqfs_tree_node_t *n; - - if (!is_filename_sane((const char *)root->name, false)) { - fprintf(stderr, "Encountered illegal file name '%s'\n", - root->name); - return -1; - } - - switch (root->inode->base.mode & S_IFMT) { - case S_IFSOCK: - return print_simple("sock", root, NULL); - case S_IFLNK: - return print_simple("slink", root, - (const char *)root->inode->extra); - case S_IFIFO: - return print_simple("pipe", root, NULL); - case S_IFREG: - if (unpack_root == NULL) - return print_simple("file", root, NULL); - - fputs("file ", stdout); - if (print_name(root, false)) - return -1; - print_perm(root); - printf(" %s/", unpack_root); - if (print_name(root, true)) - return -1; - fputc('\n', stdout); - break; - case S_IFCHR: - case S_IFBLK: { - char buffer[32]; - sqfs_u32 devno; - - if (root->inode->base.type == SQFS_INODE_EXT_BDEV || - root->inode->base.type == SQFS_INODE_EXT_CDEV) { - devno = root->inode->data.dev_ext.devno; - } else { - devno = root->inode->data.dev.devno; - } - - sprintf(buffer, "%c %u %u", - S_ISCHR(root->inode->base.mode) ? 'c' : 'b', - major(devno), minor(devno)); - return print_simple("nod", root, buffer); - } - case S_IFDIR: - if (root->name[0] != '\0') { - if (print_simple("dir", root, NULL)) - return -1; - } - - for (n = root->children; n != NULL; n = n->next) { - if (describe_tree(n, unpack_root)) - return -1; - } - break; - default: - break; - } - - return 0; -} diff --git a/bin/rdsquashfs/dump_xattrs.c b/bin/rdsquashfs/dump_xattrs.c deleted file mode 100644 index 9dbe437..0000000 --- a/bin/rdsquashfs/dump_xattrs.c +++ /dev/null @@ -1,120 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * dump_xattrs.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "rdsquashfs.h" - -static void print_hex(const sqfs_u8 *value, size_t len) -{ - printf("0x"); - - while (len--) - printf("%02X", *(value++)); -} - -static bool is_printable(const sqfs_u8 *value, size_t len) -{ - size_t utf8_cont = 0; - sqfs_u8 x; - - while (len--) { - x = *(value++); - - if (utf8_cont > 0) { - if ((x & 0xC0) != 0x80) - return false; - - --utf8_cont; - } else { - if (x < 0x80) { - if (x < 0x20) { - if (x >= 0x07 && x <= 0x0D) - continue; - if (x == 0x00) - continue; - return false; - } - - if (x == 0x7F) - return false; - } - - if ((x & 0xE0) == 0xC0) { - utf8_cont = 1; - } else if ((x & 0xF0) == 0xE0) { - utf8_cont = 2; - } else if ((x & 0xF8) == 0xF0) { - utf8_cont = 3; - } else if ((x & 0xFC) == 0xF8) { - utf8_cont = 4; - } else if ((x & 0xFE) == 0xFC) { - utf8_cont = 5; - } - - if (utf8_cont > 0 && len < utf8_cont) - return false; - } - } - - return true; -} - -int dump_xattrs(sqfs_xattr_reader_t *xattr, const sqfs_inode_generic_t *inode) -{ - sqfs_xattr_value_t *value; - sqfs_xattr_entry_t *key; - sqfs_xattr_id_t desc; - sqfs_u32 index; - size_t i; - - if (xattr == NULL) - return 0; - - sqfs_inode_get_xattr_index(inode, &index); - - if (index == 0xFFFFFFFF) - return 0; - - if (sqfs_xattr_reader_get_desc(xattr, index, &desc)) { - fputs("Error resolving xattr index\n", stderr); - return -1; - } - - if (sqfs_xattr_reader_seek_kv(xattr, &desc)) { - fputs("Error locating xattr key-value pairs\n", stderr); - return -1; - } - - for (i = 0; i < desc.count; ++i) { - if (sqfs_xattr_reader_read_key(xattr, &key)) { - fputs("Error reading xattr key\n", stderr); - return -1; - } - - if (sqfs_xattr_reader_read_value(xattr, key, &value)) { - fputs("Error reading xattr value\n", stderr); - sqfs_free(key); - return -1; - } - - if (is_printable(key->key, key->size)) { - printf("%s=", key->key); - } else { - print_hex(key->key, key->size); - } - - if (is_printable(value->value, value->size)) { - printf("%s\n", value->value); - } else { - print_hex(value->value, value->size); - printf("\n"); - } - - sqfs_free(key); - sqfs_free(value); - } - - return 0; -} diff --git a/bin/rdsquashfs/fill_files.c b/bin/rdsquashfs/fill_files.c deleted file mode 100644 index 3104146..0000000 --- a/bin/rdsquashfs/fill_files.c +++ /dev/null @@ -1,186 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * fill_files.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "config.h" -#include "rdsquashfs.h" - -static struct file_ent { - char *path; - const sqfs_inode_generic_t *inode; -} *files = NULL; - -static size_t num_files = 0, max_files = 0; -static size_t block_size = 0; - -static int compare_files(const void *l, const void *r) -{ - sqfs_u32 lhs_frag_idx, lhs_frag_off, rhs_frag_idx, rhs_frag_off; - sqfs_u64 lhs_size, rhs_size, lhs_start, rhs_start; - const struct file_ent *lhs = l, *rhs = r; - - sqfs_inode_get_frag_location(lhs->inode, &lhs_frag_idx, &lhs_frag_off); - sqfs_inode_get_file_block_start(lhs->inode, &lhs_start); - sqfs_inode_get_file_size(lhs->inode, &lhs_size); - - sqfs_inode_get_frag_location(rhs->inode, &rhs_frag_idx, &rhs_frag_off); - sqfs_inode_get_file_block_start(rhs->inode, &rhs_start); - sqfs_inode_get_file_size(rhs->inode, &rhs_size); - - /* Files with fragments come first, ordered by ID. - In case of tie, files without data blocks come first, - and the others are ordered by start block. */ - if ((lhs_size % block_size) && (lhs_frag_off < block_size) && - (lhs_frag_idx != 0xFFFFFFFF)) { - if ((rhs_size % block_size) && (rhs_frag_off < block_size) && - (rhs_frag_idx != 0xFFFFFFFF)) - return -1; - - if (lhs_frag_idx < rhs_frag_idx) - return -1; - - if (lhs_frag_idx > rhs_frag_idx) - return 1; - - if (lhs_size < block_size) - return (rhs_size < block_size) ? 0 : -1; - - if (rhs_size < block_size) - return 1; - - goto order_by_start; - } - - if ((rhs_size % block_size) && (rhs_frag_off < block_size) && - (rhs_frag_idx != 0xFFFFFFFF)) - return 1; - - /* order the rest by start block */ -order_by_start: - return lhs_start < rhs_start ? -1 : lhs_start > rhs_start ? 1 : 0; -} - -static int add_file(const sqfs_tree_node_t *node) -{ - struct file_ent *new; - size_t new_sz; - char *path; - int ret; - - if (num_files == max_files) { - new_sz = max_files ? max_files * 2 : 256; - new = realloc(files, sizeof(files[0]) * new_sz); - - if (new == NULL) { - perror("expanding file list"); - return -1; - } - - files = new; - max_files = new_sz; - } - - ret = sqfs_tree_node_get_path(node, &path); - if (ret != 0) { - sqfs_perror(NULL, "assembling file path", ret); - return -1; - } - - if (canonicalize_name(path)) { - fprintf(stderr, "Invalid file path '%s'\n", path); - sqfs_free(path); - return -1; - } - - files[num_files].path = path; - files[num_files].inode = node->inode; - num_files++; - return 0; -} - -static void clear_file_list(void) -{ - size_t i; - - for (i = 0; i < num_files; ++i) - sqfs_free(files[i].path); - - free(files); - files = NULL; - num_files = 0; - max_files = 0; -} - -static int gen_file_list_dfs(const sqfs_tree_node_t *n) -{ - if (!is_filename_sane((const char *)n->name, true)) { - fprintf(stderr, "Found an entry named '%s', skipping.\n", - n->name); - return 0; - } - - if (S_ISREG(n->inode->base.mode)) - return add_file(n); - - if (S_ISDIR(n->inode->base.mode)) { - for (n = n->children; n != NULL; n = n->next) { - if (gen_file_list_dfs(n)) - return -1; - } - } - - return 0; -} - -static int fill_files(sqfs_data_reader_t *data, int flags) -{ - int ret, openflags; - ostream_t *fp; - size_t i; - - openflags = OSTREAM_OPEN_OVERWRITE; - - if (flags & UNPACK_NO_SPARSE) - openflags |= OSTREAM_OPEN_SPARSE; - - for (i = 0; i < num_files; ++i) { - fp = ostream_open_file(files[i].path, openflags); - if (fp == NULL) - return -1; - - if (!(flags & UNPACK_QUIET)) - printf("unpacking %s\n", files[i].path); - - ret = sqfs_data_reader_dump(files[i].path, data, files[i].inode, - fp, block_size); - if (ret == 0) - ret = ostream_flush(fp); - - sqfs_drop(fp); - if (ret) - return -1; - } - - return 0; -} - -int fill_unpacked_files(size_t blk_sz, const sqfs_tree_node_t *root, - sqfs_data_reader_t *data, int flags) -{ - int status; - - block_size = blk_sz; - - if (gen_file_list_dfs(root)) { - clear_file_list(); - return -1; - } - - qsort(files, num_files, sizeof(files[0]), compare_files); - - status = fill_files(data, flags); - clear_file_list(); - return status; -} diff --git a/bin/rdsquashfs/list_files.c b/bin/rdsquashfs/list_files.c deleted file mode 100644 index b1a0102..0000000 --- a/bin/rdsquashfs/list_files.c +++ /dev/null @@ -1,158 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * list_files.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "rdsquashfs.h" - -static void mode_to_str(sqfs_u16 mode, char *p) -{ - switch (mode & S_IFMT) { - case S_IFDIR: *(p++) = 'd'; break; - case S_IFCHR: *(p++) = 'c'; break; - case S_IFBLK: *(p++) = 'b'; break; - case S_IFREG: *(p++) = '-'; break; - case S_IFLNK: *(p++) = 'l'; break; - case S_IFSOCK: *(p++) = 's'; break; - case S_IFIFO: *(p++) = 'p'; break; - default: *(p++) = '?'; break; - } - - *(p++) = (mode & S_IRUSR) ? 'r' : '-'; - *(p++) = (mode & S_IWUSR) ? 'w' : '-'; - - switch (mode & (S_IXUSR | S_ISUID)) { - case S_IXUSR | S_ISUID: *(p++) = 's'; break; - case S_IXUSR: *(p++) = 'x'; break; - case S_ISUID: *(p++) = 'S'; break; - default: *(p++) = '-'; break; - } - - *(p++) = (mode & S_IRGRP) ? 'r' : '-'; - *(p++) = (mode & S_IWGRP) ? 'w' : '-'; - - switch (mode & (S_IXGRP | S_ISGID)) { - case S_IXGRP | S_ISGID: *(p++) = 's'; break; - case S_IXGRP: *(p++) = 'x'; break; - case S_ISGID: *(p++) = 'S'; break; - case 0: *(p++) = '-'; break; - default: break; - } - - *(p++) = (mode & S_IROTH) ? 'r' : '-'; - *(p++) = (mode & S_IWOTH) ? 'w' : '-'; - - switch (mode & (S_IXOTH | S_ISVTX)) { - case S_IXOTH | S_ISVTX: *(p++) = 't'; break; - case S_IXOTH: *(p++) = 'x'; break; - case S_ISVTX: *(p++) = 'T'; break; - case 0: *(p++) = '-'; break; - default: break; - } - - *p = '\0'; -} - -static int count_int_chars(unsigned int i) -{ - int count = 1; - - while (i > 10) { - ++count; - i /= 10; - } - - return count; -} - -static void print_node_size(const sqfs_tree_node_t *n, char *buffer) -{ - switch (n->inode->base.mode & S_IFMT) { - case S_IFLNK: - print_size(strlen((const char *)n->inode->extra), buffer, true); - break; - case S_IFREG: { - sqfs_u64 size; - sqfs_inode_get_file_size(n->inode, &size); - print_size(size, buffer, true); - break; - } - case S_IFDIR: - if (n->inode->base.type == SQFS_INODE_EXT_DIR) { - print_size(n->inode->data.dir_ext.size, buffer, true); - } else { - print_size(n->inode->data.dir.size, buffer, true); - } - break; - case S_IFBLK: - case S_IFCHR: { - sqfs_u32 devno; - - if (n->inode->base.type == SQFS_INODE_EXT_BDEV || - n->inode->base.type == SQFS_INODE_EXT_CDEV) { - devno = n->inode->data.dev_ext.devno; - } else { - devno = n->inode->data.dev.devno; - } - - sprintf(buffer, "%u:%u", major(devno), minor(devno)); - break; - } - default: - buffer[0] = '0'; - buffer[1] = '\0'; - break; - } -} - -void list_files(const sqfs_tree_node_t *node) -{ - int i, max_uid_chars = 0, max_gid_chars = 0, max_sz_chars = 0; - char modestr[12], sizestr[32]; - const sqfs_tree_node_t *n; - - if (S_ISDIR(node->inode->base.mode)) { - for (n = node->children; n != NULL; n = n->next) { - i = count_int_chars(n->uid); - max_uid_chars = i > max_uid_chars ? i : max_uid_chars; - - i = count_int_chars(n->gid); - max_gid_chars = i > max_gid_chars ? i : max_gid_chars; - - print_node_size(n, sizestr); - i = strlen(sizestr); - max_sz_chars = i > max_sz_chars ? i : max_sz_chars; - } - - for (n = node->children; n != NULL; n = n->next) { - mode_to_str(n->inode->base.mode, modestr); - print_node_size(n, sizestr); - - printf("%s %*u/%-*u %*s %s", modestr, - max_uid_chars, n->uid, - max_gid_chars, n->gid, - max_sz_chars, sizestr, - n->name); - - if (S_ISLNK(n->inode->base.mode)) { - printf(" -> %s\n", - (const char *)n->inode->extra); - } else { - fputc('\n', stdout); - } - } - } else { - mode_to_str(node->inode->base.mode, modestr); - print_node_size(node, sizestr); - - printf("%s %u/%u %s %s", modestr, - node->uid, node->gid, sizestr, node->name); - - if (S_ISLNK(node->inode->base.mode)) { - printf(" -> %s\n", (const char *)node->inode->extra); - } else { - fputc('\n', stdout); - } - } -} diff --git a/bin/rdsquashfs/options.c b/bin/rdsquashfs/options.c deleted file mode 100644 index dbb5e40..0000000 --- a/bin/rdsquashfs/options.c +++ /dev/null @@ -1,226 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * options.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "rdsquashfs.h" - -static struct option long_opts[] = { - { "list", required_argument, NULL, 'l' }, - { "cat", required_argument, NULL, 'c' }, - { "xattr", required_argument, NULL, 'x' }, - { "stat", required_argument, NULL, 's' }, - { "unpack-root", required_argument, NULL, 'p' }, - { "unpack-path", required_argument, NULL, 'u' }, - { "no-dev", no_argument, NULL, 'D' }, - { "no-sock", no_argument, NULL, 'S' }, - { "no-fifo", no_argument, NULL, 'F' }, - { "no-slink", no_argument, NULL, 'L' }, - { "no-empty-dir", no_argument, NULL, 'E' }, - { "no-sparse", no_argument, NULL, 'Z' }, -#ifdef HAVE_SYS_XATTR_H - { "set-xattr", no_argument, NULL, 'X' }, -#endif - { "set-times", no_argument, NULL, 'T' }, - { "describe", no_argument, NULL, 'd' }, - { "chmod", no_argument, NULL, 'C' }, - { "chown", no_argument, NULL, 'O' }, - { "quiet", no_argument, NULL, 'q' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { NULL, 0, NULL, 0 }, -}; - -static const char *short_opts = - "l:c:u:p:x:s:DSFLCOEZTj:dqhV" -#ifdef HAVE_SYS_XATTR_H - "X" -#endif - ; - -static const char *help_string = -"Usage: rdsquashfs [OPTIONS] <squashfs-file>\n" -"\n" -"View or extract the contents of a squashfs image.\n" -"\n" -"Possible options:\n" -"\n" -" --list, -l <path> Produce a directory listing for a given path in\n" -" the squashfs image.\n" -" --cat, -c <path> If the specified path is a regular file in the,\n" -" image, dump its contents to stdout.\n" -" --xattr, -x <path> Enumerate extended attributes associated with\n" -" an inode that the given path resolves to.\n" -" --unpack-path, -u <path> Unpack this sub directory from the image. To\n" -" unpack everything, simply specify /.\n" -" --stat, -s <path> Dump all information that can be extracted from\n" -" the inode coresponding to a path, including\n" -" SquashFS specific internals.\n" -" --describe, -d Produce a file listing from the image.\n" -"\n" -" --unpack-root, -p <path> If used with --unpack-path, this is where the\n" -" data unpacked to. If used with --describe, this\n" -" is used as a prefix for the input path of\n" -" regular files.\n" -"\n" -" --no-dev, -D Do not unpack device special files.\n" -" --no-sock, -S Do not unpack socket files.\n" -" --no-fifo, -F Do not unpack named pipes.\n" -" --no-slink, -L Do not unpack symbolic links.\n" -" --no-empty-dir, -E Do not unpack directories that would end up\n" -" empty after applying the above rules.\n" -" --no-sparse, -Z Do not create sparse files, always write zero\n" -" blocks to disk.\n" -#ifdef HAVE_SYS_XATTR_H -" --set-xattr, -X When unpacking files to disk, set the extended\n" -" attributes from the squashfs image.\n" -#endif -" --set-times, -T When unpacking files to disk, set the create\n" -" and modify timestamps from the squashfs image.\n" -" --chmod, -C Change permission flags of unpacked files to\n" -" those store in the squashfs image.\n" -" --chown, -O Change ownership of unpacked files to the\n" -" UID/GID set in the squashfs image.\n" -" --quiet, -q Do not print out progress while unpacking.\n" -"\n" -" --help, -h Print help text and exit.\n" -" --version, -V Print version information and exit.\n" -"\n"; - -static char *get_path(char *old, const char *arg) -{ - char *path; - - free(old); - - path = strdup(arg); - if (path == NULL) { - perror("processing arguments"); - exit(EXIT_FAILURE); - } - - if (canonicalize_name(path)) { - fprintf(stderr, "Invalid path: %s\n", arg); - free(path); - exit(EXIT_FAILURE); - } - - return path; -} - -void process_command_line(options_t *opt, int argc, char **argv) -{ - int i; - - opt->op = OP_NONE; - opt->rdtree_flags = 0; - opt->flags = 0; - opt->cmdpath = NULL; - opt->unpack_root = NULL; - opt->image_name = NULL; - - for (;;) { - i = getopt_long(argc, argv, short_opts, long_opts, NULL); - if (i == -1) - break; - - switch (i) { - case 'D': - opt->rdtree_flags |= SQFS_TREE_NO_DEVICES; - break; - case 'S': - opt->rdtree_flags |= SQFS_TREE_NO_SOCKETS; - break; - case 'F': - opt->rdtree_flags |= SQFS_TREE_NO_FIFO; - break; - case 'L': - opt->rdtree_flags |= SQFS_TREE_NO_SLINKS; - break; - case 'E': - opt->rdtree_flags |= SQFS_TREE_NO_EMPTY; - break; - case 'C': - opt->flags |= UNPACK_CHMOD; - break; - case 'O': - opt->flags |= UNPACK_CHOWN; - break; - case 'Z': - opt->flags |= UNPACK_NO_SPARSE; - break; -#ifdef HAVE_SYS_XATTR_H - case 'X': - opt->flags |= UNPACK_SET_XATTR; - break; -#endif - case 'T': - opt->flags |= UNPACK_SET_TIMES; - break; - case 'c': - opt->op = OP_CAT; - opt->cmdpath = get_path(opt->cmdpath, optarg); - break; - case 'd': - opt->op = OP_DESCRIBE; - free(opt->cmdpath); - opt->cmdpath = NULL; - break; - case 'x': - opt->op = OP_RDATTR; - opt->cmdpath = get_path(opt->cmdpath, optarg); - break; - case 's': - opt->op = OP_STAT; - opt->cmdpath = get_path(opt->cmdpath, optarg); - break; - case 'l': - opt->op = OP_LS; - opt->cmdpath = get_path(opt->cmdpath, optarg); - break; - case 'p': - opt->unpack_root = optarg; - break; - case 'u': - opt->op = OP_UNPACK; - opt->cmdpath = get_path(opt->cmdpath, optarg); - break; - case 'q': - opt->flags |= UNPACK_QUIET; - break; - case 'h': - fputs(help_string, stdout); - free(opt->cmdpath); - exit(EXIT_SUCCESS); - case 'V': - print_version("rdsquashfs"); - free(opt->cmdpath); - exit(EXIT_SUCCESS); - default: - goto fail_arg; - } - } - - if (opt->op == OP_NONE) { - fputs("No operation specified\n", stderr); - goto fail_arg; - } - - if (opt->op == OP_LS || opt->op == OP_CAT || opt->op == OP_RDATTR || - opt->op == OP_STAT) { - opt->rdtree_flags |= SQFS_TREE_NO_RECURSE; - } - - if (optind >= argc) { - fputs("Missing image argument\n", stderr); - goto fail_arg; - } - - opt->image_name = argv[optind++]; - return; -fail_arg: - fputs("Try `rdsquashfs --help' for more information.\n", stderr); - free(opt->cmdpath); - exit(EXIT_FAILURE); -} diff --git a/bin/rdsquashfs/rdsquashfs.c b/bin/rdsquashfs/rdsquashfs.c deleted file mode 100644 index bdcc5a0..0000000 --- a/bin/rdsquashfs/rdsquashfs.c +++ /dev/null @@ -1,275 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * rdsquashfs.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "rdsquashfs.h" - -static sqfs_tree_node_t *list_merge(sqfs_tree_node_t *lhs, - sqfs_tree_node_t *rhs) -{ - sqfs_tree_node_t *it, *head = NULL, **next_ptr = &head; - - while (lhs != NULL && rhs != NULL) { - if (strcmp((const char *)lhs->name, - (const char *)rhs->name) <= 0) { - it = lhs; - lhs = lhs->next; - } else { - it = rhs; - rhs = rhs->next; - } - - *next_ptr = it; - next_ptr = &it->next; - } - - it = (lhs != NULL ? lhs : rhs); - *next_ptr = it; - return head; -} - -static sqfs_tree_node_t *list_sort(sqfs_tree_node_t *head) -{ - sqfs_tree_node_t *it, *half, *prev; - - it = half = prev = head; - - while (it != NULL) { - prev = half; - half = half->next; - it = it->next; - - if (it != NULL) - it = it->next; - } - - if (half == NULL) - return head; - - prev->next = NULL; - - return list_merge(list_sort(head), list_sort(half)); -} - -static int tree_sort(sqfs_tree_node_t *root) -{ - sqfs_tree_node_t *it; - - if (root->children == NULL) - return 0; - - root->children = list_sort(root->children); - - /* - XXX: not only an inconvenience but a security issue: e.g. we unpack a - SquashFS image that has a symlink pointing somewhere, and then a - sub-directory or file with the same name, the unpacker can be tricked - to follow the symlink and write anything, anywhere on the filesystem. - */ - for (it = root->children; it->next != NULL; it = it->next) { - if (strcmp((const char *)it->name, - (const char *)it->next->name) == 0) { - char *path; - int ret; - - ret = sqfs_tree_node_get_path(it, &path); - - if (ret == 0) { - fprintf(stderr, - "Entry '%s' found more than once!\n", - path); - } else { - fputs("Entry found more than once!\n", stderr); - } - - sqfs_free(path); - return -1; - } - } - - for (it = root->children; it != NULL; it = it->next) { - if (tree_sort(it)) - return -1; - } - - return 0; -} - -int main(int argc, char **argv) -{ - sqfs_xattr_reader_t *xattr = NULL; - sqfs_data_reader_t *data = NULL; - sqfs_dir_reader_t *dirrd = NULL; - sqfs_compressor_t *cmp = NULL; - sqfs_id_table_t *idtbl = NULL; - sqfs_compressor_config_t cfg; - sqfs_tree_node_t *n = NULL; - int status = EXIT_FAILURE; - sqfs_file_t *file = NULL; - sqfs_super_t super; - options_t opt; - int ret; - - process_command_line(&opt, argc, argv); - - file = sqfs_open_file(opt.image_name, SQFS_FILE_OPEN_READ_ONLY); - if (file == NULL) { - perror(opt.image_name); - goto out; - } - - ret = sqfs_super_read(&super, file); - if (ret) { - sqfs_perror(opt.image_name, "reading super block", ret); - goto out; - } - - sqfs_compressor_config_init(&cfg, super.compression_id, - super.block_size, - SQFS_COMP_FLAG_UNCOMPRESS); - - ret = sqfs_compressor_create(&cfg, &cmp); - -#ifdef WITH_LZO - if (super.compression_id == SQFS_COMP_LZO && ret != 0) - ret = lzo_compressor_create(&cfg, &cmp); -#endif - - if (ret != 0) { - sqfs_perror(opt.image_name, "creating compressor", ret); - goto out; - } - - if (!(super.flags & SQFS_FLAG_NO_XATTRS)) { - xattr = sqfs_xattr_reader_create(0); - if (xattr == NULL) { - sqfs_perror(opt.image_name, "creating xattr reader", - SQFS_ERROR_ALLOC); - goto out; - } - - ret = sqfs_xattr_reader_load(xattr, &super, file, cmp); - if (ret) { - sqfs_perror(opt.image_name, "loading xattr table", - ret); - goto out; - } - } - - idtbl = sqfs_id_table_create(0); - if (idtbl == NULL) { - sqfs_perror(opt.image_name, "creating ID table", - SQFS_ERROR_ALLOC); - goto out; - } - - ret = sqfs_id_table_read(idtbl, file, &super, cmp); - if (ret) { - sqfs_perror(opt.image_name, "loading ID table", ret); - goto out; - } - - dirrd = sqfs_dir_reader_create(&super, cmp, file, 0); - if (dirrd == NULL) { - sqfs_perror(opt.image_name, "creating dir reader", - SQFS_ERROR_ALLOC); - goto out; - } - - data = sqfs_data_reader_create(file, super.block_size, cmp, 0); - if (data == NULL) { - sqfs_perror(opt.image_name, "creating data reader", - SQFS_ERROR_ALLOC); - goto out; - } - - ret = sqfs_data_reader_load_fragment_table(data, &super); - if (ret) { - sqfs_perror(opt.image_name, "loading fragment table", ret); - goto out; - } - - ret = sqfs_dir_reader_get_full_hierarchy(dirrd, idtbl, opt.cmdpath, - opt.rdtree_flags, &n); - if (ret) { - sqfs_perror(opt.image_name, "reading filesystem tree", ret); - goto out; - } - - switch (opt.op) { - case OP_LS: - list_files(n); - break; - case OP_STAT: - if (stat_file(n)) - goto out; - break; - case OP_CAT: { - ostream_t *fp; - - if (!S_ISREG(n->inode->base.mode)) { - fprintf(stderr, "/%s: not a regular file\n", - opt.cmdpath); - goto out; - } - - fp = ostream_open_stdout(); - if (fp == NULL) - goto out; - - ret = sqfs_data_reader_dump(opt.cmdpath, data, n->inode, - fp, super.block_size); - sqfs_drop(fp); - if (ret) - goto out; - break; - } - case OP_UNPACK: - if (tree_sort(n)) - goto out; - - if (opt.unpack_root != NULL) { - if (mkdir_p(opt.unpack_root)) - goto out; - - if (chdir(opt.unpack_root)) { - perror(opt.unpack_root); - goto out; - } - } - - if (restore_fstree(n, opt.flags)) - goto out; - - if (fill_unpacked_files(super.block_size, n, data, opt.flags)) - goto out; - - if (update_tree_attribs(xattr, n, opt.flags)) - goto out; - break; - case OP_DESCRIBE: - if (describe_tree(n, opt.unpack_root)) - goto out; - break; - case OP_RDATTR: - if (dump_xattrs(xattr, n->inode)) - goto out; - break; - default: - break; - } - - status = EXIT_SUCCESS; -out: - sqfs_dir_tree_destroy(n); - sqfs_drop(data); - sqfs_drop(dirrd); - sqfs_drop(idtbl); - sqfs_drop(xattr); - sqfs_drop(cmp); - sqfs_drop(file); - free(opt.cmdpath); - return status; -} diff --git a/bin/rdsquashfs/rdsquashfs.h b/bin/rdsquashfs/rdsquashfs.h deleted file mode 100644 index 56bb836..0000000 --- a/bin/rdsquashfs/rdsquashfs.h +++ /dev/null @@ -1,82 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * rdsquashfs.h - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#ifndef RDSQUASHFS_H -#define RDSQUASHFS_H - -#include "config.h" -#include "common.h" -#include "fstree.h" -#include "util/util.h" - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include <windows.h> -#endif -#ifdef HAVE_SYS_XATTR_H -#include <sys/xattr.h> - -#if defined(__APPLE__) && defined(__MACH__) -#define lsetxattr(path, name, value, size, flags) \ - setxattr(path, name, value, size, 0, flags | XATTR_NOFOLLOW) -#endif -#endif -#include <string.h> -#include <stdlib.h> -#include <getopt.h> -#include <assert.h> -#include <ctype.h> -#include <errno.h> -#include <stdio.h> -#include <time.h> - -enum UNPACK_FLAGS { - UNPACK_CHMOD = 0x01, - UNPACK_CHOWN = 0x02, - UNPACK_QUIET = 0x04, - UNPACK_NO_SPARSE = 0x08, - UNPACK_SET_XATTR = 0x10, - UNPACK_SET_TIMES = 0x20, -}; - -enum { - OP_NONE = 0, - OP_LS, - OP_CAT, - OP_UNPACK, - OP_DESCRIBE, - OP_RDATTR, - OP_STAT, -}; - -typedef struct { - int op; - int rdtree_flags; - int flags; - char *cmdpath; - const char *unpack_root; - const char *image_name; -} options_t; - -void list_files(const sqfs_tree_node_t *node); - -int stat_file(const sqfs_tree_node_t *node); - -int restore_fstree(sqfs_tree_node_t *root, int flags); - -int update_tree_attribs(sqfs_xattr_reader_t *xattr, - const sqfs_tree_node_t *root, int flags); - -int fill_unpacked_files(size_t blk_sz, const sqfs_tree_node_t *root, - sqfs_data_reader_t *data, int flags); - -int describe_tree(const sqfs_tree_node_t *root, const char *unpack_root); - -int dump_xattrs(sqfs_xattr_reader_t *xattr, const sqfs_inode_generic_t *inode); - -void process_command_line(options_t *opt, int argc, char **argv); - -#endif /* RDSQUASHFS_H */ diff --git a/bin/rdsquashfs/restore_fstree.c b/bin/rdsquashfs/restore_fstree.c deleted file mode 100644 index ea9d4f1..0000000 --- a/bin/rdsquashfs/restore_fstree.c +++ /dev/null @@ -1,336 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * restore_fstree.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "rdsquashfs.h" - -#ifdef _WIN32 -static int create_node(const sqfs_tree_node_t *n, const char *name, int flags) -{ - WCHAR *wpath; - HANDLE fh; - (void)flags; - - wpath = path_to_windows(name); - if (wpath == NULL) - return -1; - - switch (n->inode->base.mode & S_IFMT) { - case S_IFDIR: - if (!CreateDirectoryW(wpath, NULL)) { - if (GetLastError() != ERROR_ALREADY_EXISTS) - goto fail; - } - break; - case S_IFREG: - fh = CreateFileW(wpath, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, CREATE_NEW, 0, NULL); - - if (fh == INVALID_HANDLE_VALUE) - goto fail; - - CloseHandle(fh); - break; - default: - break; - } - - free(wpath); - return 0; -fail: { - DWORD err = GetLastError(); - free(wpath); - SetLastError(err); - w32_perror(name); - - if (err == ERROR_FILE_EXISTS) { - fputs("\nHINT: this could be caused by case " - "sensitivity on Windows.\n", stderr); - } - return -1; -} -} -#else -static int create_node(const sqfs_tree_node_t *n, const char *name, int flags) -{ - sqfs_u32 devno; - int fd, mode; - - switch (n->inode->base.mode & S_IFMT) { - case S_IFDIR: - if (mkdir(name, 0755) && errno != EEXIST) { - fprintf(stderr, "mkdir %s: %s\n", - name, strerror(errno)); - return -1; - } - break; - case S_IFLNK: - if (symlink((const char *)n->inode->extra, name)) { - fprintf(stderr, "ln -s %s %s: %s\n", - (const char *)n->inode->extra, name, - strerror(errno)); - return -1; - } - break; - case S_IFSOCK: - case S_IFIFO: - if (mknod(name, (n->inode->base.mode & S_IFMT) | 0700, 0)) { - fprintf(stderr, "creating %s: %s\n", - name, strerror(errno)); - return -1; - } - break; - case S_IFBLK: - case S_IFCHR: - if (n->inode->base.type == SQFS_INODE_EXT_BDEV || - n->inode->base.type == SQFS_INODE_EXT_CDEV) { - devno = n->inode->data.dev_ext.devno; - } else { - devno = n->inode->data.dev.devno; - } - - if (mknod(name, n->inode->base.mode & S_IFMT, devno)) { - fprintf(stderr, "creating device %s: %s\n", - name, strerror(errno)); - return -1; - } - break; - case S_IFREG: - if (flags & UNPACK_CHMOD) { - mode = (n->inode->base.mode & ~S_IFMT) | 0200; - } else { - mode = 0644; - } - - fd = open(name, O_WRONLY | O_CREAT | O_EXCL, mode); - - if (fd < 0) { - fprintf(stderr, "creating %s: %s\n", - name, strerror(errno)); - return -1; - } - - close(fd); - break; - default: - break; - } - - return 0; -} -#endif - -static int create_node_dfs(const sqfs_tree_node_t *n, int flags) -{ - const sqfs_tree_node_t *c; - char *name; - int ret; - - if (!is_filename_sane((const char *)n->name, true)) { - fprintf(stderr, "Found an entry named '%s', skipping.\n", - n->name); - return 0; - } - - ret = sqfs_tree_node_get_path(n, &name); - if (ret != 0) { - sqfs_perror((const char *)n->name, - "constructing full path", ret); - return -1; - } - - ret = canonicalize_name(name); - assert(ret == 0); - - if (!(flags & UNPACK_QUIET)) - printf("creating %s\n", name); - - ret = create_node(n, name, flags); - sqfs_free(name); - if (ret) - return -1; - - if (S_ISDIR(n->inode->base.mode)) { - for (c = n->children; c != NULL; c = c->next) { - if (create_node_dfs(c, flags)) - return -1; - } - } - return 0; -} - -#ifdef HAVE_SYS_XATTR_H -static int set_xattr(const char *path, sqfs_xattr_reader_t *xattr, - const sqfs_tree_node_t *n) -{ - sqfs_xattr_value_t *value; - sqfs_xattr_entry_t *key; - sqfs_xattr_id_t desc; - sqfs_u32 index; - size_t i; - int ret; - - sqfs_inode_get_xattr_index(n->inode, &index); - - if (index == 0xFFFFFFFF) - return 0; - - if (sqfs_xattr_reader_get_desc(xattr, index, &desc)) { - fputs("Error resolving xattr index\n", stderr); - return -1; - } - - if (sqfs_xattr_reader_seek_kv(xattr, &desc)) { - fputs("Error locating xattr key-value pairs\n", stderr); - return -1; - } - - for (i = 0; i < desc.count; ++i) { - if (sqfs_xattr_reader_read_key(xattr, &key)) { - fputs("Error reading xattr key\n", stderr); - return -1; - } - - if (sqfs_xattr_reader_read_value(xattr, key, &value)) { - fputs("Error reading xattr value\n", stderr); - sqfs_free(key); - return -1; - } - - ret = lsetxattr(path, (const char *)key->key, - value->value, value->size, 0); - if (ret) { - fprintf(stderr, "setting xattr '%s' on %s: %s\n", - key->key, path, strerror(errno)); - } - - sqfs_free(key); - sqfs_free(value); - if (ret) - return -1; - } - - return 0; -} -#endif - -static int set_attribs(sqfs_xattr_reader_t *xattr, - const sqfs_tree_node_t *n, int flags) -{ - const sqfs_tree_node_t *c; - char *path; - int ret; - - if (!is_filename_sane((const char *)n->name, true)) - return 0; - - if (S_ISDIR(n->inode->base.mode)) { - for (c = n->children; c != NULL; c = c->next) { - if (set_attribs(xattr, c, flags)) - return -1; - } - } - - ret = sqfs_tree_node_get_path(n, &path); - if (ret != 0) { - sqfs_perror(NULL, "reconstructing full path", ret); - return -1; - } - - ret = canonicalize_name(path); - assert(ret == 0); - -#ifdef HAVE_SYS_XATTR_H - if ((flags & UNPACK_SET_XATTR) && xattr != NULL) { - if (set_xattr(path, xattr, n)) - goto fail; - } -#endif - -#ifndef _WIN32 - if (flags & UNPACK_SET_TIMES) { - struct timespec times[2]; - - memset(times, 0, sizeof(times)); - times[0].tv_sec = n->inode->base.mod_time; - times[1].tv_sec = n->inode->base.mod_time; - - if (utimensat(AT_FDCWD, path, times, AT_SYMLINK_NOFOLLOW)) { - fprintf(stderr, "setting timestamp on %s: %s\n", - path, strerror(errno)); - goto fail; - } - } -#endif - if (flags & UNPACK_CHOWN) { - if (fchownat(AT_FDCWD, path, n->uid, n->gid, - AT_SYMLINK_NOFOLLOW)) { - fprintf(stderr, "chown %s: %s\n", - path, strerror(errno)); - goto fail; - } - } - - if (flags & UNPACK_CHMOD && !S_ISLNK(n->inode->base.mode)) { - if (fchmodat(AT_FDCWD, path, - n->inode->base.mode & ~S_IFMT, 0)) { - fprintf(stderr, "chmod %s: %s\n", - path, strerror(errno)); - goto fail; - } - } - - sqfs_free(path); - return 0; -fail: - sqfs_free(path); - return -1; -} - -int restore_fstree(sqfs_tree_node_t *root, int flags) -{ - sqfs_tree_node_t *n, *old_parent; - - /* make sure fstree_get_path() stops at this node */ - old_parent = root->parent; - root->parent = NULL; - - if (S_ISDIR(root->inode->base.mode)) { - for (n = root->children; n != NULL; n = n->next) { - if (create_node_dfs(n, flags)) - return -1; - } - } else { - if (create_node_dfs(root, flags)) - return -1; - } - - root->parent = old_parent; - return 0; -} - -int update_tree_attribs(sqfs_xattr_reader_t *xattr, - const sqfs_tree_node_t *root, int flags) -{ - const sqfs_tree_node_t *n; - - if ((flags & (UNPACK_CHOWN | UNPACK_CHMOD | - UNPACK_SET_TIMES | UNPACK_SET_XATTR)) == 0) { - return 0; - } - - if (S_ISDIR(root->inode->base.mode)) { - for (n = root->children; n != NULL; n = n->next) { - if (set_attribs(xattr, n, flags)) - return -1; - } - } else { - if (set_attribs(xattr, root, flags)) - return -1; - } - - return 0; -} diff --git a/bin/rdsquashfs/src/describe.c b/bin/rdsquashfs/src/describe.c new file mode 100644 index 0000000..540b126 --- /dev/null +++ b/bin/rdsquashfs/src/describe.c @@ -0,0 +1,139 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * describe.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "rdsquashfs.h" + +static int print_name(const sqfs_tree_node_t *n, bool dont_escape) +{ + char *start, *ptr, *name; + int ret; + + ret = sqfs_tree_node_get_path(n, &name); + if (ret != 0) { + sqfs_perror(NULL, "Recovering file path of tree node", ret); + return -1; + } + + if (canonicalize_name(name) != 0) { + fprintf(stderr, "Error sanitizing file path '%s'\n", name); + sqfs_free(name); + return -1; + } + + if (dont_escape || (strchr(name, ' ') == NULL && + strchr(name, '"') == NULL)) { + fputs(name, stdout); + } else { + fputc('"', stdout); + + ptr = strchr(name, '"'); + + if (ptr != NULL) { + start = name; + + do { + fwrite(start, 1, ptr - start, stdout); + fputs("\\\"", stdout); + start = ptr + 1; + ptr = strchr(start, '"'); + } while (ptr != NULL); + + fputs(start, stdout); + } else { + fputs(name, stdout); + } + + fputc('"', stdout); + } + + sqfs_free(name); + return 0; +} + +static void print_perm(const sqfs_tree_node_t *n) +{ + printf(" 0%o %u %u", (unsigned int)n->inode->base.mode & (~S_IFMT), + n->uid, n->gid); +} + +static int print_simple(const char *type, const sqfs_tree_node_t *n, + const char *extra) +{ + printf("%s ", type); + if (print_name(n, false)) + return -1; + print_perm(n); + if (extra != NULL) + printf(" %s", extra); + fputc('\n', stdout); + return 0; +} + +int describe_tree(const sqfs_tree_node_t *root, const char *unpack_root) +{ + const sqfs_tree_node_t *n; + + if (!is_filename_sane((const char *)root->name, false)) { + fprintf(stderr, "Encountered illegal file name '%s'\n", + root->name); + return -1; + } + + switch (root->inode->base.mode & S_IFMT) { + case S_IFSOCK: + return print_simple("sock", root, NULL); + case S_IFLNK: + return print_simple("slink", root, + (const char *)root->inode->extra); + case S_IFIFO: + return print_simple("pipe", root, NULL); + case S_IFREG: + if (unpack_root == NULL) + return print_simple("file", root, NULL); + + fputs("file ", stdout); + if (print_name(root, false)) + return -1; + print_perm(root); + printf(" %s/", unpack_root); + if (print_name(root, true)) + return -1; + fputc('\n', stdout); + break; + case S_IFCHR: + case S_IFBLK: { + char buffer[32]; + sqfs_u32 devno; + + if (root->inode->base.type == SQFS_INODE_EXT_BDEV || + root->inode->base.type == SQFS_INODE_EXT_CDEV) { + devno = root->inode->data.dev_ext.devno; + } else { + devno = root->inode->data.dev.devno; + } + + sprintf(buffer, "%c %u %u", + S_ISCHR(root->inode->base.mode) ? 'c' : 'b', + major(devno), minor(devno)); + return print_simple("nod", root, buffer); + } + case S_IFDIR: + if (root->name[0] != '\0') { + if (print_simple("dir", root, NULL)) + return -1; + } + + for (n = root->children; n != NULL; n = n->next) { + if (describe_tree(n, unpack_root)) + return -1; + } + break; + default: + break; + } + + return 0; +} diff --git a/bin/rdsquashfs/src/dump_xattrs.c b/bin/rdsquashfs/src/dump_xattrs.c new file mode 100644 index 0000000..9dbe437 --- /dev/null +++ b/bin/rdsquashfs/src/dump_xattrs.c @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * dump_xattrs.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "rdsquashfs.h" + +static void print_hex(const sqfs_u8 *value, size_t len) +{ + printf("0x"); + + while (len--) + printf("%02X", *(value++)); +} + +static bool is_printable(const sqfs_u8 *value, size_t len) +{ + size_t utf8_cont = 0; + sqfs_u8 x; + + while (len--) { + x = *(value++); + + if (utf8_cont > 0) { + if ((x & 0xC0) != 0x80) + return false; + + --utf8_cont; + } else { + if (x < 0x80) { + if (x < 0x20) { + if (x >= 0x07 && x <= 0x0D) + continue; + if (x == 0x00) + continue; + return false; + } + + if (x == 0x7F) + return false; + } + + if ((x & 0xE0) == 0xC0) { + utf8_cont = 1; + } else if ((x & 0xF0) == 0xE0) { + utf8_cont = 2; + } else if ((x & 0xF8) == 0xF0) { + utf8_cont = 3; + } else if ((x & 0xFC) == 0xF8) { + utf8_cont = 4; + } else if ((x & 0xFE) == 0xFC) { + utf8_cont = 5; + } + + if (utf8_cont > 0 && len < utf8_cont) + return false; + } + } + + return true; +} + +int dump_xattrs(sqfs_xattr_reader_t *xattr, const sqfs_inode_generic_t *inode) +{ + sqfs_xattr_value_t *value; + sqfs_xattr_entry_t *key; + sqfs_xattr_id_t desc; + sqfs_u32 index; + size_t i; + + if (xattr == NULL) + return 0; + + sqfs_inode_get_xattr_index(inode, &index); + + if (index == 0xFFFFFFFF) + return 0; + + if (sqfs_xattr_reader_get_desc(xattr, index, &desc)) { + fputs("Error resolving xattr index\n", stderr); + return -1; + } + + if (sqfs_xattr_reader_seek_kv(xattr, &desc)) { + fputs("Error locating xattr key-value pairs\n", stderr); + return -1; + } + + for (i = 0; i < desc.count; ++i) { + if (sqfs_xattr_reader_read_key(xattr, &key)) { + fputs("Error reading xattr key\n", stderr); + return -1; + } + + if (sqfs_xattr_reader_read_value(xattr, key, &value)) { + fputs("Error reading xattr value\n", stderr); + sqfs_free(key); + return -1; + } + + if (is_printable(key->key, key->size)) { + printf("%s=", key->key); + } else { + print_hex(key->key, key->size); + } + + if (is_printable(value->value, value->size)) { + printf("%s\n", value->value); + } else { + print_hex(value->value, value->size); + printf("\n"); + } + + sqfs_free(key); + sqfs_free(value); + } + + return 0; +} diff --git a/bin/rdsquashfs/src/fill_files.c b/bin/rdsquashfs/src/fill_files.c new file mode 100644 index 0000000..3104146 --- /dev/null +++ b/bin/rdsquashfs/src/fill_files.c @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * fill_files.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "config.h" +#include "rdsquashfs.h" + +static struct file_ent { + char *path; + const sqfs_inode_generic_t *inode; +} *files = NULL; + +static size_t num_files = 0, max_files = 0; +static size_t block_size = 0; + +static int compare_files(const void *l, const void *r) +{ + sqfs_u32 lhs_frag_idx, lhs_frag_off, rhs_frag_idx, rhs_frag_off; + sqfs_u64 lhs_size, rhs_size, lhs_start, rhs_start; + const struct file_ent *lhs = l, *rhs = r; + + sqfs_inode_get_frag_location(lhs->inode, &lhs_frag_idx, &lhs_frag_off); + sqfs_inode_get_file_block_start(lhs->inode, &lhs_start); + sqfs_inode_get_file_size(lhs->inode, &lhs_size); + + sqfs_inode_get_frag_location(rhs->inode, &rhs_frag_idx, &rhs_frag_off); + sqfs_inode_get_file_block_start(rhs->inode, &rhs_start); + sqfs_inode_get_file_size(rhs->inode, &rhs_size); + + /* Files with fragments come first, ordered by ID. + In case of tie, files without data blocks come first, + and the others are ordered by start block. */ + if ((lhs_size % block_size) && (lhs_frag_off < block_size) && + (lhs_frag_idx != 0xFFFFFFFF)) { + if ((rhs_size % block_size) && (rhs_frag_off < block_size) && + (rhs_frag_idx != 0xFFFFFFFF)) + return -1; + + if (lhs_frag_idx < rhs_frag_idx) + return -1; + + if (lhs_frag_idx > rhs_frag_idx) + return 1; + + if (lhs_size < block_size) + return (rhs_size < block_size) ? 0 : -1; + + if (rhs_size < block_size) + return 1; + + goto order_by_start; + } + + if ((rhs_size % block_size) && (rhs_frag_off < block_size) && + (rhs_frag_idx != 0xFFFFFFFF)) + return 1; + + /* order the rest by start block */ +order_by_start: + return lhs_start < rhs_start ? -1 : lhs_start > rhs_start ? 1 : 0; +} + +static int add_file(const sqfs_tree_node_t *node) +{ + struct file_ent *new; + size_t new_sz; + char *path; + int ret; + + if (num_files == max_files) { + new_sz = max_files ? max_files * 2 : 256; + new = realloc(files, sizeof(files[0]) * new_sz); + + if (new == NULL) { + perror("expanding file list"); + return -1; + } + + files = new; + max_files = new_sz; + } + + ret = sqfs_tree_node_get_path(node, &path); + if (ret != 0) { + sqfs_perror(NULL, "assembling file path", ret); + return -1; + } + + if (canonicalize_name(path)) { + fprintf(stderr, "Invalid file path '%s'\n", path); + sqfs_free(path); + return -1; + } + + files[num_files].path = path; + files[num_files].inode = node->inode; + num_files++; + return 0; +} + +static void clear_file_list(void) +{ + size_t i; + + for (i = 0; i < num_files; ++i) + sqfs_free(files[i].path); + + free(files); + files = NULL; + num_files = 0; + max_files = 0; +} + +static int gen_file_list_dfs(const sqfs_tree_node_t *n) +{ + if (!is_filename_sane((const char *)n->name, true)) { + fprintf(stderr, "Found an entry named '%s', skipping.\n", + n->name); + return 0; + } + + if (S_ISREG(n->inode->base.mode)) + return add_file(n); + + if (S_ISDIR(n->inode->base.mode)) { + for (n = n->children; n != NULL; n = n->next) { + if (gen_file_list_dfs(n)) + return -1; + } + } + + return 0; +} + +static int fill_files(sqfs_data_reader_t *data, int flags) +{ + int ret, openflags; + ostream_t *fp; + size_t i; + + openflags = OSTREAM_OPEN_OVERWRITE; + + if (flags & UNPACK_NO_SPARSE) + openflags |= OSTREAM_OPEN_SPARSE; + + for (i = 0; i < num_files; ++i) { + fp = ostream_open_file(files[i].path, openflags); + if (fp == NULL) + return -1; + + if (!(flags & UNPACK_QUIET)) + printf("unpacking %s\n", files[i].path); + + ret = sqfs_data_reader_dump(files[i].path, data, files[i].inode, + fp, block_size); + if (ret == 0) + ret = ostream_flush(fp); + + sqfs_drop(fp); + if (ret) + return -1; + } + + return 0; +} + +int fill_unpacked_files(size_t blk_sz, const sqfs_tree_node_t *root, + sqfs_data_reader_t *data, int flags) +{ + int status; + + block_size = blk_sz; + + if (gen_file_list_dfs(root)) { + clear_file_list(); + return -1; + } + + qsort(files, num_files, sizeof(files[0]), compare_files); + + status = fill_files(data, flags); + clear_file_list(); + return status; +} diff --git a/bin/rdsquashfs/src/list_files.c b/bin/rdsquashfs/src/list_files.c new file mode 100644 index 0000000..b1a0102 --- /dev/null +++ b/bin/rdsquashfs/src/list_files.c @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * list_files.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "rdsquashfs.h" + +static void mode_to_str(sqfs_u16 mode, char *p) +{ + switch (mode & S_IFMT) { + case S_IFDIR: *(p++) = 'd'; break; + case S_IFCHR: *(p++) = 'c'; break; + case S_IFBLK: *(p++) = 'b'; break; + case S_IFREG: *(p++) = '-'; break; + case S_IFLNK: *(p++) = 'l'; break; + case S_IFSOCK: *(p++) = 's'; break; + case S_IFIFO: *(p++) = 'p'; break; + default: *(p++) = '?'; break; + } + + *(p++) = (mode & S_IRUSR) ? 'r' : '-'; + *(p++) = (mode & S_IWUSR) ? 'w' : '-'; + + switch (mode & (S_IXUSR | S_ISUID)) { + case S_IXUSR | S_ISUID: *(p++) = 's'; break; + case S_IXUSR: *(p++) = 'x'; break; + case S_ISUID: *(p++) = 'S'; break; + default: *(p++) = '-'; break; + } + + *(p++) = (mode & S_IRGRP) ? 'r' : '-'; + *(p++) = (mode & S_IWGRP) ? 'w' : '-'; + + switch (mode & (S_IXGRP | S_ISGID)) { + case S_IXGRP | S_ISGID: *(p++) = 's'; break; + case S_IXGRP: *(p++) = 'x'; break; + case S_ISGID: *(p++) = 'S'; break; + case 0: *(p++) = '-'; break; + default: break; + } + + *(p++) = (mode & S_IROTH) ? 'r' : '-'; + *(p++) = (mode & S_IWOTH) ? 'w' : '-'; + + switch (mode & (S_IXOTH | S_ISVTX)) { + case S_IXOTH | S_ISVTX: *(p++) = 't'; break; + case S_IXOTH: *(p++) = 'x'; break; + case S_ISVTX: *(p++) = 'T'; break; + case 0: *(p++) = '-'; break; + default: break; + } + + *p = '\0'; +} + +static int count_int_chars(unsigned int i) +{ + int count = 1; + + while (i > 10) { + ++count; + i /= 10; + } + + return count; +} + +static void print_node_size(const sqfs_tree_node_t *n, char *buffer) +{ + switch (n->inode->base.mode & S_IFMT) { + case S_IFLNK: + print_size(strlen((const char *)n->inode->extra), buffer, true); + break; + case S_IFREG: { + sqfs_u64 size; + sqfs_inode_get_file_size(n->inode, &size); + print_size(size, buffer, true); + break; + } + case S_IFDIR: + if (n->inode->base.type == SQFS_INODE_EXT_DIR) { + print_size(n->inode->data.dir_ext.size, buffer, true); + } else { + print_size(n->inode->data.dir.size, buffer, true); + } + break; + case S_IFBLK: + case S_IFCHR: { + sqfs_u32 devno; + + if (n->inode->base.type == SQFS_INODE_EXT_BDEV || + n->inode->base.type == SQFS_INODE_EXT_CDEV) { + devno = n->inode->data.dev_ext.devno; + } else { + devno = n->inode->data.dev.devno; + } + + sprintf(buffer, "%u:%u", major(devno), minor(devno)); + break; + } + default: + buffer[0] = '0'; + buffer[1] = '\0'; + break; + } +} + +void list_files(const sqfs_tree_node_t *node) +{ + int i, max_uid_chars = 0, max_gid_chars = 0, max_sz_chars = 0; + char modestr[12], sizestr[32]; + const sqfs_tree_node_t *n; + + if (S_ISDIR(node->inode->base.mode)) { + for (n = node->children; n != NULL; n = n->next) { + i = count_int_chars(n->uid); + max_uid_chars = i > max_uid_chars ? i : max_uid_chars; + + i = count_int_chars(n->gid); + max_gid_chars = i > max_gid_chars ? i : max_gid_chars; + + print_node_size(n, sizestr); + i = strlen(sizestr); + max_sz_chars = i > max_sz_chars ? i : max_sz_chars; + } + + for (n = node->children; n != NULL; n = n->next) { + mode_to_str(n->inode->base.mode, modestr); + print_node_size(n, sizestr); + + printf("%s %*u/%-*u %*s %s", modestr, + max_uid_chars, n->uid, + max_gid_chars, n->gid, + max_sz_chars, sizestr, + n->name); + + if (S_ISLNK(n->inode->base.mode)) { + printf(" -> %s\n", + (const char *)n->inode->extra); + } else { + fputc('\n', stdout); + } + } + } else { + mode_to_str(node->inode->base.mode, modestr); + print_node_size(node, sizestr); + + printf("%s %u/%u %s %s", modestr, + node->uid, node->gid, sizestr, node->name); + + if (S_ISLNK(node->inode->base.mode)) { + printf(" -> %s\n", (const char *)node->inode->extra); + } else { + fputc('\n', stdout); + } + } +} diff --git a/bin/rdsquashfs/src/options.c b/bin/rdsquashfs/src/options.c new file mode 100644 index 0000000..dbb5e40 --- /dev/null +++ b/bin/rdsquashfs/src/options.c @@ -0,0 +1,226 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * options.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "rdsquashfs.h" + +static struct option long_opts[] = { + { "list", required_argument, NULL, 'l' }, + { "cat", required_argument, NULL, 'c' }, + { "xattr", required_argument, NULL, 'x' }, + { "stat", required_argument, NULL, 's' }, + { "unpack-root", required_argument, NULL, 'p' }, + { "unpack-path", required_argument, NULL, 'u' }, + { "no-dev", no_argument, NULL, 'D' }, + { "no-sock", no_argument, NULL, 'S' }, + { "no-fifo", no_argument, NULL, 'F' }, + { "no-slink", no_argument, NULL, 'L' }, + { "no-empty-dir", no_argument, NULL, 'E' }, + { "no-sparse", no_argument, NULL, 'Z' }, +#ifdef HAVE_SYS_XATTR_H + { "set-xattr", no_argument, NULL, 'X' }, +#endif + { "set-times", no_argument, NULL, 'T' }, + { "describe", no_argument, NULL, 'd' }, + { "chmod", no_argument, NULL, 'C' }, + { "chown", no_argument, NULL, 'O' }, + { "quiet", no_argument, NULL, 'q' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *short_opts = + "l:c:u:p:x:s:DSFLCOEZTj:dqhV" +#ifdef HAVE_SYS_XATTR_H + "X" +#endif + ; + +static const char *help_string = +"Usage: rdsquashfs [OPTIONS] <squashfs-file>\n" +"\n" +"View or extract the contents of a squashfs image.\n" +"\n" +"Possible options:\n" +"\n" +" --list, -l <path> Produce a directory listing for a given path in\n" +" the squashfs image.\n" +" --cat, -c <path> If the specified path is a regular file in the,\n" +" image, dump its contents to stdout.\n" +" --xattr, -x <path> Enumerate extended attributes associated with\n" +" an inode that the given path resolves to.\n" +" --unpack-path, -u <path> Unpack this sub directory from the image. To\n" +" unpack everything, simply specify /.\n" +" --stat, -s <path> Dump all information that can be extracted from\n" +" the inode coresponding to a path, including\n" +" SquashFS specific internals.\n" +" --describe, -d Produce a file listing from the image.\n" +"\n" +" --unpack-root, -p <path> If used with --unpack-path, this is where the\n" +" data unpacked to. If used with --describe, this\n" +" is used as a prefix for the input path of\n" +" regular files.\n" +"\n" +" --no-dev, -D Do not unpack device special files.\n" +" --no-sock, -S Do not unpack socket files.\n" +" --no-fifo, -F Do not unpack named pipes.\n" +" --no-slink, -L Do not unpack symbolic links.\n" +" --no-empty-dir, -E Do not unpack directories that would end up\n" +" empty after applying the above rules.\n" +" --no-sparse, -Z Do not create sparse files, always write zero\n" +" blocks to disk.\n" +#ifdef HAVE_SYS_XATTR_H +" --set-xattr, -X When unpacking files to disk, set the extended\n" +" attributes from the squashfs image.\n" +#endif +" --set-times, -T When unpacking files to disk, set the create\n" +" and modify timestamps from the squashfs image.\n" +" --chmod, -C Change permission flags of unpacked files to\n" +" those store in the squashfs image.\n" +" --chown, -O Change ownership of unpacked files to the\n" +" UID/GID set in the squashfs image.\n" +" --quiet, -q Do not print out progress while unpacking.\n" +"\n" +" --help, -h Print help text and exit.\n" +" --version, -V Print version information and exit.\n" +"\n"; + +static char *get_path(char *old, const char *arg) +{ + char *path; + + free(old); + + path = strdup(arg); + if (path == NULL) { + perror("processing arguments"); + exit(EXIT_FAILURE); + } + + if (canonicalize_name(path)) { + fprintf(stderr, "Invalid path: %s\n", arg); + free(path); + exit(EXIT_FAILURE); + } + + return path; +} + +void process_command_line(options_t *opt, int argc, char **argv) +{ + int i; + + opt->op = OP_NONE; + opt->rdtree_flags = 0; + opt->flags = 0; + opt->cmdpath = NULL; + opt->unpack_root = NULL; + opt->image_name = NULL; + + for (;;) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 'D': + opt->rdtree_flags |= SQFS_TREE_NO_DEVICES; + break; + case 'S': + opt->rdtree_flags |= SQFS_TREE_NO_SOCKETS; + break; + case 'F': + opt->rdtree_flags |= SQFS_TREE_NO_FIFO; + break; + case 'L': + opt->rdtree_flags |= SQFS_TREE_NO_SLINKS; + break; + case 'E': + opt->rdtree_flags |= SQFS_TREE_NO_EMPTY; + break; + case 'C': + opt->flags |= UNPACK_CHMOD; + break; + case 'O': + opt->flags |= UNPACK_CHOWN; + break; + case 'Z': + opt->flags |= UNPACK_NO_SPARSE; + break; +#ifdef HAVE_SYS_XATTR_H + case 'X': + opt->flags |= UNPACK_SET_XATTR; + break; +#endif + case 'T': + opt->flags |= UNPACK_SET_TIMES; + break; + case 'c': + opt->op = OP_CAT; + opt->cmdpath = get_path(opt->cmdpath, optarg); + break; + case 'd': + opt->op = OP_DESCRIBE; + free(opt->cmdpath); + opt->cmdpath = NULL; + break; + case 'x': + opt->op = OP_RDATTR; + opt->cmdpath = get_path(opt->cmdpath, optarg); + break; + case 's': + opt->op = OP_STAT; + opt->cmdpath = get_path(opt->cmdpath, optarg); + break; + case 'l': + opt->op = OP_LS; + opt->cmdpath = get_path(opt->cmdpath, optarg); + break; + case 'p': + opt->unpack_root = optarg; + break; + case 'u': + opt->op = OP_UNPACK; + opt->cmdpath = get_path(opt->cmdpath, optarg); + break; + case 'q': + opt->flags |= UNPACK_QUIET; + break; + case 'h': + fputs(help_string, stdout); + free(opt->cmdpath); + exit(EXIT_SUCCESS); + case 'V': + print_version("rdsquashfs"); + free(opt->cmdpath); + exit(EXIT_SUCCESS); + default: + goto fail_arg; + } + } + + if (opt->op == OP_NONE) { + fputs("No operation specified\n", stderr); + goto fail_arg; + } + + if (opt->op == OP_LS || opt->op == OP_CAT || opt->op == OP_RDATTR || + opt->op == OP_STAT) { + opt->rdtree_flags |= SQFS_TREE_NO_RECURSE; + } + + if (optind >= argc) { + fputs("Missing image argument\n", stderr); + goto fail_arg; + } + + opt->image_name = argv[optind++]; + return; +fail_arg: + fputs("Try `rdsquashfs --help' for more information.\n", stderr); + free(opt->cmdpath); + exit(EXIT_FAILURE); +} diff --git a/bin/rdsquashfs/src/rdsquashfs.c b/bin/rdsquashfs/src/rdsquashfs.c new file mode 100644 index 0000000..bdcc5a0 --- /dev/null +++ b/bin/rdsquashfs/src/rdsquashfs.c @@ -0,0 +1,275 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * rdsquashfs.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "rdsquashfs.h" + +static sqfs_tree_node_t *list_merge(sqfs_tree_node_t *lhs, + sqfs_tree_node_t *rhs) +{ + sqfs_tree_node_t *it, *head = NULL, **next_ptr = &head; + + while (lhs != NULL && rhs != NULL) { + if (strcmp((const char *)lhs->name, + (const char *)rhs->name) <= 0) { + it = lhs; + lhs = lhs->next; + } else { + it = rhs; + rhs = rhs->next; + } + + *next_ptr = it; + next_ptr = &it->next; + } + + it = (lhs != NULL ? lhs : rhs); + *next_ptr = it; + return head; +} + +static sqfs_tree_node_t *list_sort(sqfs_tree_node_t *head) +{ + sqfs_tree_node_t *it, *half, *prev; + + it = half = prev = head; + + while (it != NULL) { + prev = half; + half = half->next; + it = it->next; + + if (it != NULL) + it = it->next; + } + + if (half == NULL) + return head; + + prev->next = NULL; + + return list_merge(list_sort(head), list_sort(half)); +} + +static int tree_sort(sqfs_tree_node_t *root) +{ + sqfs_tree_node_t *it; + + if (root->children == NULL) + return 0; + + root->children = list_sort(root->children); + + /* + XXX: not only an inconvenience but a security issue: e.g. we unpack a + SquashFS image that has a symlink pointing somewhere, and then a + sub-directory or file with the same name, the unpacker can be tricked + to follow the symlink and write anything, anywhere on the filesystem. + */ + for (it = root->children; it->next != NULL; it = it->next) { + if (strcmp((const char *)it->name, + (const char *)it->next->name) == 0) { + char *path; + int ret; + + ret = sqfs_tree_node_get_path(it, &path); + + if (ret == 0) { + fprintf(stderr, + "Entry '%s' found more than once!\n", + path); + } else { + fputs("Entry found more than once!\n", stderr); + } + + sqfs_free(path); + return -1; + } + } + + for (it = root->children; it != NULL; it = it->next) { + if (tree_sort(it)) + return -1; + } + + return 0; +} + +int main(int argc, char **argv) +{ + sqfs_xattr_reader_t *xattr = NULL; + sqfs_data_reader_t *data = NULL; + sqfs_dir_reader_t *dirrd = NULL; + sqfs_compressor_t *cmp = NULL; + sqfs_id_table_t *idtbl = NULL; + sqfs_compressor_config_t cfg; + sqfs_tree_node_t *n = NULL; + int status = EXIT_FAILURE; + sqfs_file_t *file = NULL; + sqfs_super_t super; + options_t opt; + int ret; + + process_command_line(&opt, argc, argv); + + file = sqfs_open_file(opt.image_name, SQFS_FILE_OPEN_READ_ONLY); + if (file == NULL) { + perror(opt.image_name); + goto out; + } + + ret = sqfs_super_read(&super, file); + if (ret) { + sqfs_perror(opt.image_name, "reading super block", ret); + goto out; + } + + sqfs_compressor_config_init(&cfg, super.compression_id, + super.block_size, + SQFS_COMP_FLAG_UNCOMPRESS); + + ret = sqfs_compressor_create(&cfg, &cmp); + +#ifdef WITH_LZO + if (super.compression_id == SQFS_COMP_LZO && ret != 0) + ret = lzo_compressor_create(&cfg, &cmp); +#endif + + if (ret != 0) { + sqfs_perror(opt.image_name, "creating compressor", ret); + goto out; + } + + if (!(super.flags & SQFS_FLAG_NO_XATTRS)) { + xattr = sqfs_xattr_reader_create(0); + if (xattr == NULL) { + sqfs_perror(opt.image_name, "creating xattr reader", + SQFS_ERROR_ALLOC); + goto out; + } + + ret = sqfs_xattr_reader_load(xattr, &super, file, cmp); + if (ret) { + sqfs_perror(opt.image_name, "loading xattr table", + ret); + goto out; + } + } + + idtbl = sqfs_id_table_create(0); + if (idtbl == NULL) { + sqfs_perror(opt.image_name, "creating ID table", + SQFS_ERROR_ALLOC); + goto out; + } + + ret = sqfs_id_table_read(idtbl, file, &super, cmp); + if (ret) { + sqfs_perror(opt.image_name, "loading ID table", ret); + goto out; + } + + dirrd = sqfs_dir_reader_create(&super, cmp, file, 0); + if (dirrd == NULL) { + sqfs_perror(opt.image_name, "creating dir reader", + SQFS_ERROR_ALLOC); + goto out; + } + + data = sqfs_data_reader_create(file, super.block_size, cmp, 0); + if (data == NULL) { + sqfs_perror(opt.image_name, "creating data reader", + SQFS_ERROR_ALLOC); + goto out; + } + + ret = sqfs_data_reader_load_fragment_table(data, &super); + if (ret) { + sqfs_perror(opt.image_name, "loading fragment table", ret); + goto out; + } + + ret = sqfs_dir_reader_get_full_hierarchy(dirrd, idtbl, opt.cmdpath, + opt.rdtree_flags, &n); + if (ret) { + sqfs_perror(opt.image_name, "reading filesystem tree", ret); + goto out; + } + + switch (opt.op) { + case OP_LS: + list_files(n); + break; + case OP_STAT: + if (stat_file(n)) + goto out; + break; + case OP_CAT: { + ostream_t *fp; + + if (!S_ISREG(n->inode->base.mode)) { + fprintf(stderr, "/%s: not a regular file\n", + opt.cmdpath); + goto out; + } + + fp = ostream_open_stdout(); + if (fp == NULL) + goto out; + + ret = sqfs_data_reader_dump(opt.cmdpath, data, n->inode, + fp, super.block_size); + sqfs_drop(fp); + if (ret) + goto out; + break; + } + case OP_UNPACK: + if (tree_sort(n)) + goto out; + + if (opt.unpack_root != NULL) { + if (mkdir_p(opt.unpack_root)) + goto out; + + if (chdir(opt.unpack_root)) { + perror(opt.unpack_root); + goto out; + } + } + + if (restore_fstree(n, opt.flags)) + goto out; + + if (fill_unpacked_files(super.block_size, n, data, opt.flags)) + goto out; + + if (update_tree_attribs(xattr, n, opt.flags)) + goto out; + break; + case OP_DESCRIBE: + if (describe_tree(n, opt.unpack_root)) + goto out; + break; + case OP_RDATTR: + if (dump_xattrs(xattr, n->inode)) + goto out; + break; + default: + break; + } + + status = EXIT_SUCCESS; +out: + sqfs_dir_tree_destroy(n); + sqfs_drop(data); + sqfs_drop(dirrd); + sqfs_drop(idtbl); + sqfs_drop(xattr); + sqfs_drop(cmp); + sqfs_drop(file); + free(opt.cmdpath); + return status; +} diff --git a/bin/rdsquashfs/src/rdsquashfs.h b/bin/rdsquashfs/src/rdsquashfs.h new file mode 100644 index 0000000..56bb836 --- /dev/null +++ b/bin/rdsquashfs/src/rdsquashfs.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * rdsquashfs.h + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#ifndef RDSQUASHFS_H +#define RDSQUASHFS_H + +#include "config.h" +#include "common.h" +#include "fstree.h" +#include "util/util.h" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#endif +#ifdef HAVE_SYS_XATTR_H +#include <sys/xattr.h> + +#if defined(__APPLE__) && defined(__MACH__) +#define lsetxattr(path, name, value, size, flags) \ + setxattr(path, name, value, size, 0, flags | XATTR_NOFOLLOW) +#endif +#endif +#include <string.h> +#include <stdlib.h> +#include <getopt.h> +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <time.h> + +enum UNPACK_FLAGS { + UNPACK_CHMOD = 0x01, + UNPACK_CHOWN = 0x02, + UNPACK_QUIET = 0x04, + UNPACK_NO_SPARSE = 0x08, + UNPACK_SET_XATTR = 0x10, + UNPACK_SET_TIMES = 0x20, +}; + +enum { + OP_NONE = 0, + OP_LS, + OP_CAT, + OP_UNPACK, + OP_DESCRIBE, + OP_RDATTR, + OP_STAT, +}; + +typedef struct { + int op; + int rdtree_flags; + int flags; + char *cmdpath; + const char *unpack_root; + const char *image_name; +} options_t; + +void list_files(const sqfs_tree_node_t *node); + +int stat_file(const sqfs_tree_node_t *node); + +int restore_fstree(sqfs_tree_node_t *root, int flags); + +int update_tree_attribs(sqfs_xattr_reader_t *xattr, + const sqfs_tree_node_t *root, int flags); + +int fill_unpacked_files(size_t blk_sz, const sqfs_tree_node_t *root, + sqfs_data_reader_t *data, int flags); + +int describe_tree(const sqfs_tree_node_t *root, const char *unpack_root); + +int dump_xattrs(sqfs_xattr_reader_t *xattr, const sqfs_inode_generic_t *inode); + +void process_command_line(options_t *opt, int argc, char **argv); + +#endif /* RDSQUASHFS_H */ diff --git a/bin/rdsquashfs/src/restore_fstree.c b/bin/rdsquashfs/src/restore_fstree.c new file mode 100644 index 0000000..ea9d4f1 --- /dev/null +++ b/bin/rdsquashfs/src/restore_fstree.c @@ -0,0 +1,336 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * restore_fstree.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "rdsquashfs.h" + +#ifdef _WIN32 +static int create_node(const sqfs_tree_node_t *n, const char *name, int flags) +{ + WCHAR *wpath; + HANDLE fh; + (void)flags; + + wpath = path_to_windows(name); + if (wpath == NULL) + return -1; + + switch (n->inode->base.mode & S_IFMT) { + case S_IFDIR: + if (!CreateDirectoryW(wpath, NULL)) { + if (GetLastError() != ERROR_ALREADY_EXISTS) + goto fail; + } + break; + case S_IFREG: + fh = CreateFileW(wpath, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, CREATE_NEW, 0, NULL); + + if (fh == INVALID_HANDLE_VALUE) + goto fail; + + CloseHandle(fh); + break; + default: + break; + } + + free(wpath); + return 0; +fail: { + DWORD err = GetLastError(); + free(wpath); + SetLastError(err); + w32_perror(name); + + if (err == ERROR_FILE_EXISTS) { + fputs("\nHINT: this could be caused by case " + "sensitivity on Windows.\n", stderr); + } + return -1; +} +} +#else +static int create_node(const sqfs_tree_node_t *n, const char *name, int flags) +{ + sqfs_u32 devno; + int fd, mode; + + switch (n->inode->base.mode & S_IFMT) { + case S_IFDIR: + if (mkdir(name, 0755) && errno != EEXIST) { + fprintf(stderr, "mkdir %s: %s\n", + name, strerror(errno)); + return -1; + } + break; + case S_IFLNK: + if (symlink((const char *)n->inode->extra, name)) { + fprintf(stderr, "ln -s %s %s: %s\n", + (const char *)n->inode->extra, name, + strerror(errno)); + return -1; + } + break; + case S_IFSOCK: + case S_IFIFO: + if (mknod(name, (n->inode->base.mode & S_IFMT) | 0700, 0)) { + fprintf(stderr, "creating %s: %s\n", + name, strerror(errno)); + return -1; + } + break; + case S_IFBLK: + case S_IFCHR: + if (n->inode->base.type == SQFS_INODE_EXT_BDEV || + n->inode->base.type == SQFS_INODE_EXT_CDEV) { + devno = n->inode->data.dev_ext.devno; + } else { + devno = n->inode->data.dev.devno; + } + + if (mknod(name, n->inode->base.mode & S_IFMT, devno)) { + fprintf(stderr, "creating device %s: %s\n", + name, strerror(errno)); + return -1; + } + break; + case S_IFREG: + if (flags & UNPACK_CHMOD) { + mode = (n->inode->base.mode & ~S_IFMT) | 0200; + } else { + mode = 0644; + } + + fd = open(name, O_WRONLY | O_CREAT | O_EXCL, mode); + + if (fd < 0) { + fprintf(stderr, "creating %s: %s\n", + name, strerror(errno)); + return -1; + } + + close(fd); + break; + default: + break; + } + + return 0; +} +#endif + +static int create_node_dfs(const sqfs_tree_node_t *n, int flags) +{ + const sqfs_tree_node_t *c; + char *name; + int ret; + + if (!is_filename_sane((const char *)n->name, true)) { + fprintf(stderr, "Found an entry named '%s', skipping.\n", + n->name); + return 0; + } + + ret = sqfs_tree_node_get_path(n, &name); + if (ret != 0) { + sqfs_perror((const char *)n->name, + "constructing full path", ret); + return -1; + } + + ret = canonicalize_name(name); + assert(ret == 0); + + if (!(flags & UNPACK_QUIET)) + printf("creating %s\n", name); + + ret = create_node(n, name, flags); + sqfs_free(name); + if (ret) + return -1; + + if (S_ISDIR(n->inode->base.mode)) { + for (c = n->children; c != NULL; c = c->next) { + if (create_node_dfs(c, flags)) + return -1; + } + } + return 0; +} + +#ifdef HAVE_SYS_XATTR_H +static int set_xattr(const char *path, sqfs_xattr_reader_t *xattr, + const sqfs_tree_node_t *n) +{ + sqfs_xattr_value_t *value; + sqfs_xattr_entry_t *key; + sqfs_xattr_id_t desc; + sqfs_u32 index; + size_t i; + int ret; + + sqfs_inode_get_xattr_index(n->inode, &index); + + if (index == 0xFFFFFFFF) + return 0; + + if (sqfs_xattr_reader_get_desc(xattr, index, &desc)) { + fputs("Error resolving xattr index\n", stderr); + return -1; + } + + if (sqfs_xattr_reader_seek_kv(xattr, &desc)) { + fputs("Error locating xattr key-value pairs\n", stderr); + return -1; + } + + for (i = 0; i < desc.count; ++i) { + if (sqfs_xattr_reader_read_key(xattr, &key)) { + fputs("Error reading xattr key\n", stderr); + return -1; + } + + if (sqfs_xattr_reader_read_value(xattr, key, &value)) { + fputs("Error reading xattr value\n", stderr); + sqfs_free(key); + return -1; + } + + ret = lsetxattr(path, (const char *)key->key, + value->value, value->size, 0); + if (ret) { + fprintf(stderr, "setting xattr '%s' on %s: %s\n", + key->key, path, strerror(errno)); + } + + sqfs_free(key); + sqfs_free(value); + if (ret) + return -1; + } + + return 0; +} +#endif + +static int set_attribs(sqfs_xattr_reader_t *xattr, + const sqfs_tree_node_t *n, int flags) +{ + const sqfs_tree_node_t *c; + char *path; + int ret; + + if (!is_filename_sane((const char *)n->name, true)) + return 0; + + if (S_ISDIR(n->inode->base.mode)) { + for (c = n->children; c != NULL; c = c->next) { + if (set_attribs(xattr, c, flags)) + return -1; + } + } + + ret = sqfs_tree_node_get_path(n, &path); + if (ret != 0) { + sqfs_perror(NULL, "reconstructing full path", ret); + return -1; + } + + ret = canonicalize_name(path); + assert(ret == 0); + +#ifdef HAVE_SYS_XATTR_H + if ((flags & UNPACK_SET_XATTR) && xattr != NULL) { + if (set_xattr(path, xattr, n)) + goto fail; + } +#endif + +#ifndef _WIN32 + if (flags & UNPACK_SET_TIMES) { + struct timespec times[2]; + + memset(times, 0, sizeof(times)); + times[0].tv_sec = n->inode->base.mod_time; + times[1].tv_sec = n->inode->base.mod_time; + + if (utimensat(AT_FDCWD, path, times, AT_SYMLINK_NOFOLLOW)) { + fprintf(stderr, "setting timestamp on %s: %s\n", + path, strerror(errno)); + goto fail; + } + } +#endif + if (flags & UNPACK_CHOWN) { + if (fchownat(AT_FDCWD, path, n->uid, n->gid, + AT_SYMLINK_NOFOLLOW)) { + fprintf(stderr, "chown %s: %s\n", + path, strerror(errno)); + goto fail; + } + } + + if (flags & UNPACK_CHMOD && !S_ISLNK(n->inode->base.mode)) { + if (fchmodat(AT_FDCWD, path, + n->inode->base.mode & ~S_IFMT, 0)) { + fprintf(stderr, "chmod %s: %s\n", + path, strerror(errno)); + goto fail; + } + } + + sqfs_free(path); + return 0; +fail: + sqfs_free(path); + return -1; +} + +int restore_fstree(sqfs_tree_node_t *root, int flags) +{ + sqfs_tree_node_t *n, *old_parent; + + /* make sure fstree_get_path() stops at this node */ + old_parent = root->parent; + root->parent = NULL; + + if (S_ISDIR(root->inode->base.mode)) { + for (n = root->children; n != NULL; n = n->next) { + if (create_node_dfs(n, flags)) + return -1; + } + } else { + if (create_node_dfs(root, flags)) + return -1; + } + + root->parent = old_parent; + return 0; +} + +int update_tree_attribs(sqfs_xattr_reader_t *xattr, + const sqfs_tree_node_t *root, int flags) +{ + const sqfs_tree_node_t *n; + + if ((flags & (UNPACK_CHOWN | UNPACK_CHMOD | + UNPACK_SET_TIMES | UNPACK_SET_XATTR)) == 0) { + return 0; + } + + if (S_ISDIR(root->inode->base.mode)) { + for (n = root->children; n != NULL; n = n->next) { + if (set_attribs(xattr, n, flags)) + return -1; + } + } else { + if (set_attribs(xattr, root, flags)) + return -1; + } + + return 0; +} diff --git a/bin/rdsquashfs/src/stat.c b/bin/rdsquashfs/src/stat.c new file mode 100644 index 0000000..8b4581f --- /dev/null +++ b/bin/rdsquashfs/src/stat.c @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * stat.c + * + * Copyright (C) 2020 David Oberhollenzer <goliath@infraroot.at> + */ +#include "rdsquashfs.h" + +static const char *inode_types[] = { + [SQFS_INODE_DIR] = "directory", + [SQFS_INODE_FILE] = "file", + [SQFS_INODE_SLINK] = "symbolic link", + [SQFS_INODE_BDEV] = "block device", + [SQFS_INODE_CDEV] = "character device", + [SQFS_INODE_FIFO] = "named pipe", + [SQFS_INODE_SOCKET] = "socket", + [SQFS_INODE_EXT_DIR] = "extended directory", + [SQFS_INODE_EXT_FILE] = "extended file", + [SQFS_INODE_EXT_SLINK] = "extended symbolic link", + [SQFS_INODE_EXT_BDEV] = "extended block device", + [SQFS_INODE_EXT_CDEV] = "extended character device", + [SQFS_INODE_EXT_FIFO] = "extended named pipe", + [SQFS_INODE_EXT_SOCKET] = "extended socket", +}; + +int stat_file(const sqfs_tree_node_t *node) +{ + sqfs_u32 xattr_idx = 0xFFFFFFFF, devno = 0, link_size = 0; + const sqfs_inode_generic_t *inode = node->inode; + const char *type = NULL, *link_target = NULL; + sqfs_u32 frag_idx, frag_offset; + bool have_devno = false; + sqfs_u64 location, size; + unsigned int nlinks = 0; + sqfs_dir_index_t *idx; + char buffer[64]; + time_t timeval; + struct tm *tm; + size_t i; + int ret; + + /* decode */ + if ((size_t)inode->base.type < + sizeof(inode_types) / sizeof(inode_types[0])) { + type = inode_types[inode->base.type]; + } + + sqfs_inode_get_xattr_index(inode, &xattr_idx); + + switch (inode->base.type) { + case SQFS_INODE_DIR: + nlinks = inode->data.dir.nlink; + break; + case SQFS_INODE_SLINK: + nlinks = inode->data.slink.nlink; + link_target = (const char *)inode->extra; + link_size = inode->data.slink.target_size; + break; + case SQFS_INODE_BDEV: + case SQFS_INODE_CDEV: + nlinks = inode->data.dev.nlink; + devno = inode->data.dev.devno; + have_devno = true; + break; + case SQFS_INODE_FIFO: + case SQFS_INODE_SOCKET: + nlinks = inode->data.ipc.nlink; + break; + case SQFS_INODE_EXT_DIR: + nlinks = inode->data.dir_ext.nlink; + break; + case SQFS_INODE_EXT_FILE: + nlinks = inode->data.file_ext.nlink; + break; + case SQFS_INODE_EXT_SLINK: + nlinks = inode->data.slink_ext.nlink; + link_target = (const char *)inode->extra; + link_size = inode->data.slink_ext.target_size; + break; + case SQFS_INODE_EXT_BDEV: + case SQFS_INODE_EXT_CDEV: + nlinks = inode->data.dev_ext.nlink; + devno = inode->data.dev_ext.devno; + have_devno = true; + break; + case SQFS_INODE_EXT_FIFO: + case SQFS_INODE_EXT_SOCKET: + nlinks = inode->data.ipc_ext.nlink; + break; + default: + break; + } + + timeval = inode->base.mod_time; + tm = gmtime(&timeval); + strftime(buffer, sizeof(buffer), "%a, %d %b %Y %T %z", tm); + + /* info dump */ + printf("Name: %s\n", (const char *)node->name); + printf("Inode type: %s\n", type == NULL ? "UNKNOWN" : type); + printf("Inode number: %u\n", inode->base.inode_number); + printf("Access: 0%o\n", + (unsigned int)inode->base.mode & ~SQFS_INODE_MODE_MASK); + printf("UID: %u (index = %u)\n", node->uid, inode->base.uid_idx); + printf("GID: %u (index = %u)\n", node->gid, inode->base.gid_idx); + printf("Last modified: %s (%u)\n", buffer, inode->base.mod_time); + + if (type != NULL && inode->base.type != SQFS_INODE_FILE) + printf("Hard link count: %u\n", nlinks); + + if (type != NULL && inode->base.type >= SQFS_INODE_EXT_DIR) + printf("Xattr index: 0x%X\n", xattr_idx); + + if (link_target != NULL) + printf("Link target: %.*s\n", (int)link_size, link_target); + + if (have_devno) { + printf("Device number: %u:%u (%u)\n", + major(devno), minor(devno), devno); + } + + switch (inode->base.type) { + case SQFS_INODE_FILE: + case SQFS_INODE_EXT_FILE: + sqfs_inode_get_file_block_start(inode, &location); + sqfs_inode_get_file_size(inode, &size); + sqfs_inode_get_frag_location(inode, &frag_idx, &frag_offset); + + printf("Fragment index: 0x%X\n", frag_idx); + printf("Fragment offset: %u\n", frag_offset); + printf("File size: %lu\n", (unsigned long)size); + + if (inode->base.type == SQFS_INODE_EXT_FILE) { + printf("Sparse: " PRI_U64 "\n", + inode->data.file_ext.sparse); + } + + printf("Blocks start: %lu\n", (unsigned long)location); + printf("Block count: %lu\n", + (unsigned long)sqfs_inode_get_file_block_count(inode)); + + for (i = 0; i < sqfs_inode_get_file_block_count(inode); ++i) { + printf("\tBlock #%lu size: %u (%s)\n", (unsigned long)i, + SQFS_ON_DISK_BLOCK_SIZE(inode->extra[i]), + SQFS_IS_BLOCK_COMPRESSED(inode->extra[i]) ? + "compressed" : "uncompressed"); + } + break; + case SQFS_INODE_DIR: + printf("Start block: %u\n", inode->data.dir.start_block); + printf("Offset: %u\n", inode->data.dir.offset); + printf("Listing size: %u\n", inode->data.dir.size); + printf("Parent inode: %u\n", inode->data.dir.parent_inode); + break; + case SQFS_INODE_EXT_DIR: + printf("Start block: %u\n", inode->data.dir_ext.start_block); + printf("Offset: %u\n", inode->data.dir_ext.offset); + printf("Listing size: %u\n", inode->data.dir_ext.size); + printf("Parent inode: %u\n", inode->data.dir_ext.parent_inode); + printf("Directory index entries: %u\n", + inode->data.dir_ext.inodex_count); + + if (inode->data.dir_ext.size == 0) + break; + + for (i = 0; ; ++i) { + ret = sqfs_inode_unpack_dir_index_entry(inode, &idx, i); + if (ret == SQFS_ERROR_OUT_OF_BOUNDS) + break; + if (ret < 0) { + sqfs_perror(NULL, "reading directory index", + ret); + return -1; + } + + printf("\t'%.*s' -> block %u, header offset %u\n", + (int)(idx->size + 1), idx->name, + idx->start_block, idx->index); + + sqfs_free(idx); + } + break; + default: + break; + } + return 0; +} diff --git a/bin/rdsquashfs/stat.c b/bin/rdsquashfs/stat.c deleted file mode 100644 index 8b4581f..0000000 --- a/bin/rdsquashfs/stat.c +++ /dev/null @@ -1,187 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * stat.c - * - * Copyright (C) 2020 David Oberhollenzer <goliath@infraroot.at> - */ -#include "rdsquashfs.h" - -static const char *inode_types[] = { - [SQFS_INODE_DIR] = "directory", - [SQFS_INODE_FILE] = "file", - [SQFS_INODE_SLINK] = "symbolic link", - [SQFS_INODE_BDEV] = "block device", - [SQFS_INODE_CDEV] = "character device", - [SQFS_INODE_FIFO] = "named pipe", - [SQFS_INODE_SOCKET] = "socket", - [SQFS_INODE_EXT_DIR] = "extended directory", - [SQFS_INODE_EXT_FILE] = "extended file", - [SQFS_INODE_EXT_SLINK] = "extended symbolic link", - [SQFS_INODE_EXT_BDEV] = "extended block device", - [SQFS_INODE_EXT_CDEV] = "extended character device", - [SQFS_INODE_EXT_FIFO] = "extended named pipe", - [SQFS_INODE_EXT_SOCKET] = "extended socket", -}; - -int stat_file(const sqfs_tree_node_t *node) -{ - sqfs_u32 xattr_idx = 0xFFFFFFFF, devno = 0, link_size = 0; - const sqfs_inode_generic_t *inode = node->inode; - const char *type = NULL, *link_target = NULL; - sqfs_u32 frag_idx, frag_offset; - bool have_devno = false; - sqfs_u64 location, size; - unsigned int nlinks = 0; - sqfs_dir_index_t *idx; - char buffer[64]; - time_t timeval; - struct tm *tm; - size_t i; - int ret; - - /* decode */ - if ((size_t)inode->base.type < - sizeof(inode_types) / sizeof(inode_types[0])) { - type = inode_types[inode->base.type]; - } - - sqfs_inode_get_xattr_index(inode, &xattr_idx); - - switch (inode->base.type) { - case SQFS_INODE_DIR: - nlinks = inode->data.dir.nlink; - break; - case SQFS_INODE_SLINK: - nlinks = inode->data.slink.nlink; - link_target = (const char *)inode->extra; - link_size = inode->data.slink.target_size; - break; - case SQFS_INODE_BDEV: - case SQFS_INODE_CDEV: - nlinks = inode->data.dev.nlink; - devno = inode->data.dev.devno; - have_devno = true; - break; - case SQFS_INODE_FIFO: - case SQFS_INODE_SOCKET: - nlinks = inode->data.ipc.nlink; - break; - case SQFS_INODE_EXT_DIR: - nlinks = inode->data.dir_ext.nlink; - break; - case SQFS_INODE_EXT_FILE: - nlinks = inode->data.file_ext.nlink; - break; - case SQFS_INODE_EXT_SLINK: - nlinks = inode->data.slink_ext.nlink; - link_target = (const char *)inode->extra; - link_size = inode->data.slink_ext.target_size; - break; - case SQFS_INODE_EXT_BDEV: - case SQFS_INODE_EXT_CDEV: - nlinks = inode->data.dev_ext.nlink; - devno = inode->data.dev_ext.devno; - have_devno = true; - break; - case SQFS_INODE_EXT_FIFO: - case SQFS_INODE_EXT_SOCKET: - nlinks = inode->data.ipc_ext.nlink; - break; - default: - break; - } - - timeval = inode->base.mod_time; - tm = gmtime(&timeval); - strftime(buffer, sizeof(buffer), "%a, %d %b %Y %T %z", tm); - - /* info dump */ - printf("Name: %s\n", (const char *)node->name); - printf("Inode type: %s\n", type == NULL ? "UNKNOWN" : type); - printf("Inode number: %u\n", inode->base.inode_number); - printf("Access: 0%o\n", - (unsigned int)inode->base.mode & ~SQFS_INODE_MODE_MASK); - printf("UID: %u (index = %u)\n", node->uid, inode->base.uid_idx); - printf("GID: %u (index = %u)\n", node->gid, inode->base.gid_idx); - printf("Last modified: %s (%u)\n", buffer, inode->base.mod_time); - - if (type != NULL && inode->base.type != SQFS_INODE_FILE) - printf("Hard link count: %u\n", nlinks); - - if (type != NULL && inode->base.type >= SQFS_INODE_EXT_DIR) - printf("Xattr index: 0x%X\n", xattr_idx); - - if (link_target != NULL) - printf("Link target: %.*s\n", (int)link_size, link_target); - - if (have_devno) { - printf("Device number: %u:%u (%u)\n", - major(devno), minor(devno), devno); - } - - switch (inode->base.type) { - case SQFS_INODE_FILE: - case SQFS_INODE_EXT_FILE: - sqfs_inode_get_file_block_start(inode, &location); - sqfs_inode_get_file_size(inode, &size); - sqfs_inode_get_frag_location(inode, &frag_idx, &frag_offset); - - printf("Fragment index: 0x%X\n", frag_idx); - printf("Fragment offset: %u\n", frag_offset); - printf("File size: %lu\n", (unsigned long)size); - - if (inode->base.type == SQFS_INODE_EXT_FILE) { - printf("Sparse: " PRI_U64 "\n", - inode->data.file_ext.sparse); - } - - printf("Blocks start: %lu\n", (unsigned long)location); - printf("Block count: %lu\n", - (unsigned long)sqfs_inode_get_file_block_count(inode)); - - for (i = 0; i < sqfs_inode_get_file_block_count(inode); ++i) { - printf("\tBlock #%lu size: %u (%s)\n", (unsigned long)i, - SQFS_ON_DISK_BLOCK_SIZE(inode->extra[i]), - SQFS_IS_BLOCK_COMPRESSED(inode->extra[i]) ? - "compressed" : "uncompressed"); - } - break; - case SQFS_INODE_DIR: - printf("Start block: %u\n", inode->data.dir.start_block); - printf("Offset: %u\n", inode->data.dir.offset); - printf("Listing size: %u\n", inode->data.dir.size); - printf("Parent inode: %u\n", inode->data.dir.parent_inode); - break; - case SQFS_INODE_EXT_DIR: - printf("Start block: %u\n", inode->data.dir_ext.start_block); - printf("Offset: %u\n", inode->data.dir_ext.offset); - printf("Listing size: %u\n", inode->data.dir_ext.size); - printf("Parent inode: %u\n", inode->data.dir_ext.parent_inode); - printf("Directory index entries: %u\n", - inode->data.dir_ext.inodex_count); - - if (inode->data.dir_ext.size == 0) - break; - - for (i = 0; ; ++i) { - ret = sqfs_inode_unpack_dir_index_entry(inode, &idx, i); - if (ret == SQFS_ERROR_OUT_OF_BOUNDS) - break; - if (ret < 0) { - sqfs_perror(NULL, "reading directory index", - ret); - return -1; - } - - printf("\t'%.*s' -> block %u, header offset %u\n", - (int)(idx->size + 1), idx->name, - idx->start_block, idx->index); - - sqfs_free(idx); - } - break; - default: - break; - } - return 0; -} diff --git a/bin/sqfs2tar/Makemodule.am b/bin/sqfs2tar/Makemodule.am index 05cee5b..2e6c411 100644 --- a/bin/sqfs2tar/Makemodule.am +++ b/bin/sqfs2tar/Makemodule.am @@ -1,6 +1,6 @@ -sqfs2tar_SOURCES = bin/sqfs2tar/sqfs2tar.c bin/sqfs2tar/sqfs2tar.h -sqfs2tar_SOURCES += bin/sqfs2tar/options.c bin/sqfs2tar/write_tree.c -sqfs2tar_SOURCES += bin/sqfs2tar/xattr.c +sqfs2tar_SOURCES = bin/sqfs2tar/src/sqfs2tar.c bin/sqfs2tar/src/sqfs2tar.h \ + bin/sqfs2tar/src/options.c bin/sqfs2tar/src/write_tree.c \ + bin/sqfs2tar/src/xattr.c sqfs2tar_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) sqfs2tar_LDADD = libcommon.a libutil.a libsquashfs.la libtar.a sqfs2tar_LDADD += libio.a libxfrm.a libcompat.a libfstree.a diff --git a/bin/sqfs2tar/options.c b/bin/sqfs2tar/options.c deleted file mode 100644 index ba1588d..0000000 --- a/bin/sqfs2tar/options.c +++ /dev/null @@ -1,212 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * options.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfs2tar.h" - -static struct option long_opts[] = { - { "compressor", required_argument, NULL, 'c' }, - { "subdir", required_argument, NULL, 'd' }, - { "keep-as-dir", no_argument, NULL, 'k' }, - { "root-becomes", required_argument, NULL, 'r' }, - { "no-skip", no_argument, NULL, 's' }, - { "no-xattr", no_argument, NULL, 'X' }, - { "no-hard-links", no_argument, NULL, 'L' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { NULL, 0, NULL, 0 }, -}; - -static const char *short_opts = "c:d:kr:sXLhV"; - -static const char *usagestr = -"Usage: sqfs2tar [OPTIONS...] <sqfsfile>\n" -"\n" -"Read an input squashfs archive and turn it into a tar archive, written\n" -"to stdout.\n" -"\n" -"Possible options:\n" -"\n" -" --compressor, -c <name> If set, stream compress the resulting tarball.\n" -" By default, the tarball is uncompressed.\n" -"\n" -" --subdir, -d <dir> Unpack the given sub directory instead of the\n" -" filesystem root. Can be specified more than\n" -" once to select multiple directories. If only\n" -" one is specified, it becomes the new root of\n" -" node of the archive file system tree.\n" -"\n" -" --root-becomes, -r <dir> Turn the root inode into a directory with the\n" -" specified name. Everything else will be stored\n" -" inside this directory. The special value '.' is\n" -" allowed to prefix all tar paths with './' and\n" -" add an entry named '.' for the root inode.\n" -" If this option isn't used, all meta data stored\n" -" in the root inode IS LOST!\n" -"\n" -" --keep-as-dir, -k If --subdir is used only once, don't make the\n" -" subdir the archive root, instead keep it as\n" -" prefix for all unpacked files.\n" -" Using --subdir more than once implies\n" -" --keep-as-dir.\n" -" --no-xattr, -X Do not copy extended attributes.\n" -" --no-hard-links, -L Do not generate hard links. Produce duplicate\n" -" entries instead.\n" -"\n" -" --no-skip, -s Abort if a file cannot be stored in a tar\n" -" archive. By default, it is simply skipped\n" -" and a warning is written to stderr.\n" -"\n" -" --help, -h Print help text and exit.\n" -" --version, -V Print version information and exit.\n" -"\n" -"Supported tar compression formats:\n"; - -bool dont_skip = false; -bool keep_as_dir = false; -bool no_xattr = false; -bool no_links = false; - -char *root_becomes = NULL; -char **subdirs = NULL; -size_t num_subdirs = 0; -static size_t max_subdirs = 0; -int compressor = 0; - -const char *filename = NULL; - -void process_args(int argc, char **argv) -{ - size_t idx, new_count; - const char *name; - int i, ret; - char **new; - - for (;;) { - i = getopt_long(argc, argv, short_opts, long_opts, NULL); - if (i == -1) - break; - - switch (i) { - case 'c': - compressor = xfrm_compressor_id_from_name(optarg); - if (compressor <= 0) { - fprintf(stderr, "unknown compressor '%s'.\n", - optarg); - goto fail; - } - break; - case 'd': - if (num_subdirs == max_subdirs) { - new_count = max_subdirs ? max_subdirs * 2 : 16; - new = realloc(subdirs, - new_count * sizeof(subdirs[0])); - if (new == NULL) - goto fail_errno; - - max_subdirs = new_count; - subdirs = new; - } - - subdirs[num_subdirs] = strdup(optarg); - if (subdirs[num_subdirs] == NULL) - goto fail_errno; - - if (canonicalize_name(subdirs[num_subdirs])) { - perror(optarg); - goto fail; - } - - ++num_subdirs; - break; - case 'r': - free(root_becomes); - root_becomes = strdup(optarg); - if (root_becomes == NULL) - goto fail_errno; - - if (strcmp(root_becomes, "./") == 0) - root_becomes[1] = '\0'; - - if (strcmp(root_becomes, ".") == 0) - break; - - if (canonicalize_name(root_becomes) != 0 || - strlen(root_becomes) == 0) { - fprintf(stderr, - "Invalid root directory '%s'.\n", - optarg); - goto fail_arg; - } - break; - case 'k': - keep_as_dir = true; - break; - case 's': - dont_skip = true; - break; - case 'X': - no_xattr = true; - break; - case 'L': - no_links = true; - break; - case 'h': - fputs(usagestr, stdout); - - i = XFRM_COMPRESSOR_MIN; - - while (i <= XFRM_COMPRESSOR_MAX) { - name = xfrm_compressor_name_from_id(i); - if (name != NULL) - printf("\t%s\n", name); - ++i; - } - - fputc('\n', stdout); - goto out_success; - case 'V': - print_version("sqfs2tar"); - goto out_success; - default: - goto fail_arg; - } - } - - if (optind >= argc) { - fputs("Missing argument: squashfs image\n", stderr); - goto fail_arg; - } - - filename = argv[optind++]; - - if (optind < argc) { - fputs("Unknown extra arguments\n", stderr); - goto fail_arg; - } - - if (num_subdirs > 1) - keep_as_dir = true; - - return; -fail_errno: - perror("parsing options"); - goto fail; -fail_arg: - fputs("Try `sqfs2tar --help' for more information.\n", stderr); - goto fail; -fail: - ret = EXIT_FAILURE; - goto out_exit; -out_success: - ret = EXIT_SUCCESS; - goto out_exit; -out_exit: - for (idx = 0; idx < num_subdirs; ++idx) - free(subdirs[idx]); - free(root_becomes); - free(subdirs); - exit(ret); -} diff --git a/bin/sqfs2tar/sqfs2tar.c b/bin/sqfs2tar/sqfs2tar.c deleted file mode 100644 index 43f9e78..0000000 --- a/bin/sqfs2tar/sqfs2tar.c +++ /dev/null @@ -1,274 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * sqfs2tar.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfs2tar.h" - -sqfs_xattr_reader_t *xr; -sqfs_data_reader_t *data; -sqfs_super_t super; -ostream_t *out_file = NULL; - -static sqfs_file_t *file; - -char *assemble_tar_path(char *name, bool is_dir) -{ - size_t len, new_len; - char *temp; - (void)is_dir; - - if (root_becomes == NULL && !is_dir) - return name; - - new_len = strlen(name); - if (root_becomes != NULL) - new_len += strlen(root_becomes) + 1; - if (is_dir) - new_len += 1; - - temp = realloc(name, new_len + 1); - if (temp == NULL) { - perror("assembling tar entry filename"); - free(name); - return NULL; - } - - name = temp; - - if (root_becomes != NULL) { - len = strlen(root_becomes); - - memmove(name + len + 1, name, strlen(name) + 1); - memcpy(name, root_becomes, len); - name[len] = '/'; - } - - if (is_dir) { - len = strlen(name); - - if (len == 0 || name[len - 1] != '/') { - name[len++] = '/'; - name[len] = '\0'; - } - } - - return name; -} - -static int terminate_archive(void) -{ - char buffer[1024]; - - memset(buffer, '\0', sizeof(buffer)); - - return ostream_append(out_file, buffer, sizeof(buffer)); -} - -static sqfs_tree_node_t *tree_merge(sqfs_tree_node_t *lhs, - sqfs_tree_node_t *rhs) -{ - sqfs_tree_node_t *head = NULL, **next_ptr = &head; - sqfs_tree_node_t *it, *l, *r; - int diff; - - while (lhs->children != NULL && rhs->children != NULL) { - diff = strcmp((const char *)lhs->children->name, - (const char *)rhs->children->name); - - if (diff < 0) { - it = lhs->children; - lhs->children = lhs->children->next; - } else if (diff > 0) { - it = rhs->children; - rhs->children = rhs->children->next; - } else { - l = lhs->children; - lhs->children = lhs->children->next; - - r = rhs->children; - rhs->children = rhs->children->next; - - it = tree_merge(l, r); - } - - *next_ptr = it; - next_ptr = &it->next; - } - - it = (lhs->children != NULL ? lhs->children : rhs->children); - *next_ptr = it; - - sqfs_dir_tree_destroy(rhs); - lhs->children = head; - return lhs; -} - -int main(int argc, char **argv) -{ - sqfs_tree_node_t *root = NULL, *subtree = NULL; - int flags, ret, status = EXIT_FAILURE; - sqfs_compressor_t *cmp = NULL; - sqfs_id_table_t *idtbl = NULL; - sqfs_dir_reader_t *dr = NULL; - sqfs_compressor_config_t cfg; - size_t i; - - process_args(argc, argv); - - out_file = ostream_open_stdout(); - if (out_file == NULL) { - perror("changing stdout to binary mode"); - goto out; - } - - if (compressor > 0) { - xfrm_stream_t *xfrm = compressor_stream_create(compressor,NULL); - ostream_t *strm; - - if (xfrm == NULL) - goto out; - - strm = ostream_xfrm_create(out_file, xfrm); - sqfs_drop(out_file); - sqfs_drop(xfrm); - out_file = strm; - - if (out_file == NULL) - goto out; - } - - file = sqfs_open_file(filename, SQFS_FILE_OPEN_READ_ONLY); - if (file == NULL) { - perror(filename); - goto out; - } - - ret = sqfs_super_read(&super, file); - if (ret) { - sqfs_perror(filename, "reading super block", ret); - goto out; - } - - sqfs_compressor_config_init(&cfg, super.compression_id, - super.block_size, - SQFS_COMP_FLAG_UNCOMPRESS); - - ret = sqfs_compressor_create(&cfg, &cmp); - -#ifdef WITH_LZO - if (super.compression_id == SQFS_COMP_LZO && ret != 0) - ret = lzo_compressor_create(&cfg, &cmp); -#endif - - if (ret != 0) { - sqfs_perror(filename, "creating compressor", ret); - goto out; - } - - idtbl = sqfs_id_table_create(0); - - if (idtbl == NULL) { - perror("creating ID table"); - goto out; - } - - ret = sqfs_id_table_read(idtbl, file, &super, cmp); - if (ret) { - sqfs_perror(filename, "loading ID table", ret); - goto out; - } - - data = sqfs_data_reader_create(file, super.block_size, cmp, 0); - if (data == NULL) { - sqfs_perror(filename, "creating data reader", - SQFS_ERROR_ALLOC); - goto out; - } - - ret = sqfs_data_reader_load_fragment_table(data, &super); - if (ret) { - sqfs_perror(filename, "loading fragment table", ret); - goto out; - } - - dr = sqfs_dir_reader_create(&super, cmp, file, 0); - if (dr == NULL) { - sqfs_perror(filename, "creating dir reader", - SQFS_ERROR_ALLOC); - goto out; - } - - if (!no_xattr && !(super.flags & SQFS_FLAG_NO_XATTRS)) { - xr = sqfs_xattr_reader_create(0); - if (xr == NULL) { - sqfs_perror(filename, "creating xattr reader", - SQFS_ERROR_ALLOC); - goto out; - } - - ret = sqfs_xattr_reader_load(xr, &super, file, cmp); - if (ret) { - sqfs_perror(filename, "loading xattr table", ret); - goto out; - } - } - - if (num_subdirs == 0) { - ret = sqfs_dir_reader_get_full_hierarchy(dr, idtbl, NULL, - 0, &root); - if (ret) { - sqfs_perror(filename, "loading filesystem tree", ret); - goto out; - } - } else { - flags = 0; - - if (keep_as_dir || num_subdirs > 1) - flags = SQFS_TREE_STORE_PARENTS; - - for (i = 0; i < num_subdirs; ++i) { - ret = sqfs_dir_reader_get_full_hierarchy(dr, idtbl, - subdirs[i], - flags, - &subtree); - if (ret) { - sqfs_perror(subdirs[i], "loading filesystem " - "tree", ret); - goto out; - } - - if (root == NULL) { - root = subtree; - } else { - root = tree_merge(root, subtree); - } - } - } - - if (write_tree(root)) - goto out; - - if (terminate_archive()) - goto out; - - if (ostream_flush(out_file)) - goto out; - - status = EXIT_SUCCESS; -out: - sqfs_dir_tree_destroy(root); - sqfs_drop(xr); - sqfs_drop(dr); - sqfs_drop(data); - sqfs_drop(idtbl); - sqfs_drop(cmp); - sqfs_drop(file); - sqfs_drop(out_file); - for (i = 0; i < num_subdirs; ++i) - free(subdirs[i]); - free(subdirs); - free(root_becomes); - return status; -} diff --git a/bin/sqfs2tar/sqfs2tar.h b/bin/sqfs2tar/sqfs2tar.h deleted file mode 100644 index 4bf5428..0000000 --- a/bin/sqfs2tar/sqfs2tar.h +++ /dev/null @@ -1,56 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * sqfs2tar.h - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#ifndef SQFS2TAR_H -#define SQFS2TAR_H - -#include "config.h" -#include "common.h" - -#include "util/util.h" -#include "tar/tar.h" -#include "xfrm/compress.h" -#include "io/xfrm.h" - -#include <getopt.h> -#include <string.h> -#include <stdlib.h> -#include <assert.h> -#include <errno.h> -#include <fcntl.h> -#include <stdio.h> - -/* options.c */ -extern bool dont_skip; -extern bool keep_as_dir; -extern bool no_xattr; -extern bool no_links; - -extern char *root_becomes; -extern char **subdirs; -extern size_t num_subdirs; -extern int compressor; - -extern const char *filename; - -void process_args(int argc, char **argv); - -/* tar2sqfs.c */ -extern sqfs_xattr_reader_t *xr; -extern sqfs_data_reader_t *data; -extern sqfs_super_t super; -extern ostream_t *out_file; - -char *assemble_tar_path(char *name, bool is_dir); - -/* xattr.c */ -int get_xattrs(const char *name, const sqfs_inode_generic_t *inode, - tar_xattr_t **out); - -/* write_tree.c */ -int write_tree(const sqfs_tree_node_t *n); - -#endif /* SQFS2TAR_H */ diff --git a/bin/sqfs2tar/src/options.c b/bin/sqfs2tar/src/options.c new file mode 100644 index 0000000..ba1588d --- /dev/null +++ b/bin/sqfs2tar/src/options.c @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * options.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfs2tar.h" + +static struct option long_opts[] = { + { "compressor", required_argument, NULL, 'c' }, + { "subdir", required_argument, NULL, 'd' }, + { "keep-as-dir", no_argument, NULL, 'k' }, + { "root-becomes", required_argument, NULL, 'r' }, + { "no-skip", no_argument, NULL, 's' }, + { "no-xattr", no_argument, NULL, 'X' }, + { "no-hard-links", no_argument, NULL, 'L' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *short_opts = "c:d:kr:sXLhV"; + +static const char *usagestr = +"Usage: sqfs2tar [OPTIONS...] <sqfsfile>\n" +"\n" +"Read an input squashfs archive and turn it into a tar archive, written\n" +"to stdout.\n" +"\n" +"Possible options:\n" +"\n" +" --compressor, -c <name> If set, stream compress the resulting tarball.\n" +" By default, the tarball is uncompressed.\n" +"\n" +" --subdir, -d <dir> Unpack the given sub directory instead of the\n" +" filesystem root. Can be specified more than\n" +" once to select multiple directories. If only\n" +" one is specified, it becomes the new root of\n" +" node of the archive file system tree.\n" +"\n" +" --root-becomes, -r <dir> Turn the root inode into a directory with the\n" +" specified name. Everything else will be stored\n" +" inside this directory. The special value '.' is\n" +" allowed to prefix all tar paths with './' and\n" +" add an entry named '.' for the root inode.\n" +" If this option isn't used, all meta data stored\n" +" in the root inode IS LOST!\n" +"\n" +" --keep-as-dir, -k If --subdir is used only once, don't make the\n" +" subdir the archive root, instead keep it as\n" +" prefix for all unpacked files.\n" +" Using --subdir more than once implies\n" +" --keep-as-dir.\n" +" --no-xattr, -X Do not copy extended attributes.\n" +" --no-hard-links, -L Do not generate hard links. Produce duplicate\n" +" entries instead.\n" +"\n" +" --no-skip, -s Abort if a file cannot be stored in a tar\n" +" archive. By default, it is simply skipped\n" +" and a warning is written to stderr.\n" +"\n" +" --help, -h Print help text and exit.\n" +" --version, -V Print version information and exit.\n" +"\n" +"Supported tar compression formats:\n"; + +bool dont_skip = false; +bool keep_as_dir = false; +bool no_xattr = false; +bool no_links = false; + +char *root_becomes = NULL; +char **subdirs = NULL; +size_t num_subdirs = 0; +static size_t max_subdirs = 0; +int compressor = 0; + +const char *filename = NULL; + +void process_args(int argc, char **argv) +{ + size_t idx, new_count; + const char *name; + int i, ret; + char **new; + + for (;;) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 'c': + compressor = xfrm_compressor_id_from_name(optarg); + if (compressor <= 0) { + fprintf(stderr, "unknown compressor '%s'.\n", + optarg); + goto fail; + } + break; + case 'd': + if (num_subdirs == max_subdirs) { + new_count = max_subdirs ? max_subdirs * 2 : 16; + new = realloc(subdirs, + new_count * sizeof(subdirs[0])); + if (new == NULL) + goto fail_errno; + + max_subdirs = new_count; + subdirs = new; + } + + subdirs[num_subdirs] = strdup(optarg); + if (subdirs[num_subdirs] == NULL) + goto fail_errno; + + if (canonicalize_name(subdirs[num_subdirs])) { + perror(optarg); + goto fail; + } + + ++num_subdirs; + break; + case 'r': + free(root_becomes); + root_becomes = strdup(optarg); + if (root_becomes == NULL) + goto fail_errno; + + if (strcmp(root_becomes, "./") == 0) + root_becomes[1] = '\0'; + + if (strcmp(root_becomes, ".") == 0) + break; + + if (canonicalize_name(root_becomes) != 0 || + strlen(root_becomes) == 0) { + fprintf(stderr, + "Invalid root directory '%s'.\n", + optarg); + goto fail_arg; + } + break; + case 'k': + keep_as_dir = true; + break; + case 's': + dont_skip = true; + break; + case 'X': + no_xattr = true; + break; + case 'L': + no_links = true; + break; + case 'h': + fputs(usagestr, stdout); + + i = XFRM_COMPRESSOR_MIN; + + while (i <= XFRM_COMPRESSOR_MAX) { + name = xfrm_compressor_name_from_id(i); + if (name != NULL) + printf("\t%s\n", name); + ++i; + } + + fputc('\n', stdout); + goto out_success; + case 'V': + print_version("sqfs2tar"); + goto out_success; + default: + goto fail_arg; + } + } + + if (optind >= argc) { + fputs("Missing argument: squashfs image\n", stderr); + goto fail_arg; + } + + filename = argv[optind++]; + + if (optind < argc) { + fputs("Unknown extra arguments\n", stderr); + goto fail_arg; + } + + if (num_subdirs > 1) + keep_as_dir = true; + + return; +fail_errno: + perror("parsing options"); + goto fail; +fail_arg: + fputs("Try `sqfs2tar --help' for more information.\n", stderr); + goto fail; +fail: + ret = EXIT_FAILURE; + goto out_exit; +out_success: + ret = EXIT_SUCCESS; + goto out_exit; +out_exit: + for (idx = 0; idx < num_subdirs; ++idx) + free(subdirs[idx]); + free(root_becomes); + free(subdirs); + exit(ret); +} diff --git a/bin/sqfs2tar/src/sqfs2tar.c b/bin/sqfs2tar/src/sqfs2tar.c new file mode 100644 index 0000000..43f9e78 --- /dev/null +++ b/bin/sqfs2tar/src/sqfs2tar.c @@ -0,0 +1,274 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * sqfs2tar.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfs2tar.h" + +sqfs_xattr_reader_t *xr; +sqfs_data_reader_t *data; +sqfs_super_t super; +ostream_t *out_file = NULL; + +static sqfs_file_t *file; + +char *assemble_tar_path(char *name, bool is_dir) +{ + size_t len, new_len; + char *temp; + (void)is_dir; + + if (root_becomes == NULL && !is_dir) + return name; + + new_len = strlen(name); + if (root_becomes != NULL) + new_len += strlen(root_becomes) + 1; + if (is_dir) + new_len += 1; + + temp = realloc(name, new_len + 1); + if (temp == NULL) { + perror("assembling tar entry filename"); + free(name); + return NULL; + } + + name = temp; + + if (root_becomes != NULL) { + len = strlen(root_becomes); + + memmove(name + len + 1, name, strlen(name) + 1); + memcpy(name, root_becomes, len); + name[len] = '/'; + } + + if (is_dir) { + len = strlen(name); + + if (len == 0 || name[len - 1] != '/') { + name[len++] = '/'; + name[len] = '\0'; + } + } + + return name; +} + +static int terminate_archive(void) +{ + char buffer[1024]; + + memset(buffer, '\0', sizeof(buffer)); + + return ostream_append(out_file, buffer, sizeof(buffer)); +} + +static sqfs_tree_node_t *tree_merge(sqfs_tree_node_t *lhs, + sqfs_tree_node_t *rhs) +{ + sqfs_tree_node_t *head = NULL, **next_ptr = &head; + sqfs_tree_node_t *it, *l, *r; + int diff; + + while (lhs->children != NULL && rhs->children != NULL) { + diff = strcmp((const char *)lhs->children->name, + (const char *)rhs->children->name); + + if (diff < 0) { + it = lhs->children; + lhs->children = lhs->children->next; + } else if (diff > 0) { + it = rhs->children; + rhs->children = rhs->children->next; + } else { + l = lhs->children; + lhs->children = lhs->children->next; + + r = rhs->children; + rhs->children = rhs->children->next; + + it = tree_merge(l, r); + } + + *next_ptr = it; + next_ptr = &it->next; + } + + it = (lhs->children != NULL ? lhs->children : rhs->children); + *next_ptr = it; + + sqfs_dir_tree_destroy(rhs); + lhs->children = head; + return lhs; +} + +int main(int argc, char **argv) +{ + sqfs_tree_node_t *root = NULL, *subtree = NULL; + int flags, ret, status = EXIT_FAILURE; + sqfs_compressor_t *cmp = NULL; + sqfs_id_table_t *idtbl = NULL; + sqfs_dir_reader_t *dr = NULL; + sqfs_compressor_config_t cfg; + size_t i; + + process_args(argc, argv); + + out_file = ostream_open_stdout(); + if (out_file == NULL) { + perror("changing stdout to binary mode"); + goto out; + } + + if (compressor > 0) { + xfrm_stream_t *xfrm = compressor_stream_create(compressor,NULL); + ostream_t *strm; + + if (xfrm == NULL) + goto out; + + strm = ostream_xfrm_create(out_file, xfrm); + sqfs_drop(out_file); + sqfs_drop(xfrm); + out_file = strm; + + if (out_file == NULL) + goto out; + } + + file = sqfs_open_file(filename, SQFS_FILE_OPEN_READ_ONLY); + if (file == NULL) { + perror(filename); + goto out; + } + + ret = sqfs_super_read(&super, file); + if (ret) { + sqfs_perror(filename, "reading super block", ret); + goto out; + } + + sqfs_compressor_config_init(&cfg, super.compression_id, + super.block_size, + SQFS_COMP_FLAG_UNCOMPRESS); + + ret = sqfs_compressor_create(&cfg, &cmp); + +#ifdef WITH_LZO + if (super.compression_id == SQFS_COMP_LZO && ret != 0) + ret = lzo_compressor_create(&cfg, &cmp); +#endif + + if (ret != 0) { + sqfs_perror(filename, "creating compressor", ret); + goto out; + } + + idtbl = sqfs_id_table_create(0); + + if (idtbl == NULL) { + perror("creating ID table"); + goto out; + } + + ret = sqfs_id_table_read(idtbl, file, &super, cmp); + if (ret) { + sqfs_perror(filename, "loading ID table", ret); + goto out; + } + + data = sqfs_data_reader_create(file, super.block_size, cmp, 0); + if (data == NULL) { + sqfs_perror(filename, "creating data reader", + SQFS_ERROR_ALLOC); + goto out; + } + + ret = sqfs_data_reader_load_fragment_table(data, &super); + if (ret) { + sqfs_perror(filename, "loading fragment table", ret); + goto out; + } + + dr = sqfs_dir_reader_create(&super, cmp, file, 0); + if (dr == NULL) { + sqfs_perror(filename, "creating dir reader", + SQFS_ERROR_ALLOC); + goto out; + } + + if (!no_xattr && !(super.flags & SQFS_FLAG_NO_XATTRS)) { + xr = sqfs_xattr_reader_create(0); + if (xr == NULL) { + sqfs_perror(filename, "creating xattr reader", + SQFS_ERROR_ALLOC); + goto out; + } + + ret = sqfs_xattr_reader_load(xr, &super, file, cmp); + if (ret) { + sqfs_perror(filename, "loading xattr table", ret); + goto out; + } + } + + if (num_subdirs == 0) { + ret = sqfs_dir_reader_get_full_hierarchy(dr, idtbl, NULL, + 0, &root); + if (ret) { + sqfs_perror(filename, "loading filesystem tree", ret); + goto out; + } + } else { + flags = 0; + + if (keep_as_dir || num_subdirs > 1) + flags = SQFS_TREE_STORE_PARENTS; + + for (i = 0; i < num_subdirs; ++i) { + ret = sqfs_dir_reader_get_full_hierarchy(dr, idtbl, + subdirs[i], + flags, + &subtree); + if (ret) { + sqfs_perror(subdirs[i], "loading filesystem " + "tree", ret); + goto out; + } + + if (root == NULL) { + root = subtree; + } else { + root = tree_merge(root, subtree); + } + } + } + + if (write_tree(root)) + goto out; + + if (terminate_archive()) + goto out; + + if (ostream_flush(out_file)) + goto out; + + status = EXIT_SUCCESS; +out: + sqfs_dir_tree_destroy(root); + sqfs_drop(xr); + sqfs_drop(dr); + sqfs_drop(data); + sqfs_drop(idtbl); + sqfs_drop(cmp); + sqfs_drop(file); + sqfs_drop(out_file); + for (i = 0; i < num_subdirs; ++i) + free(subdirs[i]); + free(subdirs); + free(root_becomes); + return status; +} diff --git a/bin/sqfs2tar/src/sqfs2tar.h b/bin/sqfs2tar/src/sqfs2tar.h new file mode 100644 index 0000000..4bf5428 --- /dev/null +++ b/bin/sqfs2tar/src/sqfs2tar.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * sqfs2tar.h + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#ifndef SQFS2TAR_H +#define SQFS2TAR_H + +#include "config.h" +#include "common.h" + +#include "util/util.h" +#include "tar/tar.h" +#include "xfrm/compress.h" +#include "io/xfrm.h" + +#include <getopt.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> + +/* options.c */ +extern bool dont_skip; +extern bool keep_as_dir; +extern bool no_xattr; +extern bool no_links; + +extern char *root_becomes; +extern char **subdirs; +extern size_t num_subdirs; +extern int compressor; + +extern const char *filename; + +void process_args(int argc, char **argv); + +/* tar2sqfs.c */ +extern sqfs_xattr_reader_t *xr; +extern sqfs_data_reader_t *data; +extern sqfs_super_t super; +extern ostream_t *out_file; + +char *assemble_tar_path(char *name, bool is_dir); + +/* xattr.c */ +int get_xattrs(const char *name, const sqfs_inode_generic_t *inode, + tar_xattr_t **out); + +/* write_tree.c */ +int write_tree(const sqfs_tree_node_t *n); + +#endif /* SQFS2TAR_H */ diff --git a/bin/sqfs2tar/src/write_tree.c b/bin/sqfs2tar/src/write_tree.c new file mode 100644 index 0000000..354ec21 --- /dev/null +++ b/bin/sqfs2tar/src/write_tree.c @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * write_tree.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfs2tar.h" + +static sqfs_hard_link_t *links = NULL; +static unsigned int record_counter; + +static sqfs_hard_link_t *find_hard_link(const char *name, sqfs_u32 inum) +{ + sqfs_hard_link_t *lnk = NULL; + + for (lnk = links; lnk != NULL; lnk = lnk->next) { + if (lnk->inode_number == inum) { + if (strcmp(name, lnk->target) == 0) + lnk = NULL; + break; + } + } + + return lnk; +} + +static void inode_stat(const sqfs_tree_node_t *node, struct stat *sb) +{ + memset(sb, 0, sizeof(*sb)); + + sb->st_mode = node->inode->base.mode; + sb->st_uid = node->uid; + sb->st_gid = node->gid; + sb->st_mtime = node->inode->base.mod_time; + + switch (node->inode->base.type) { + case SQFS_INODE_BDEV: + case SQFS_INODE_CDEV: + sb->st_rdev = node->inode->data.dev.devno; + break; + case SQFS_INODE_EXT_BDEV: + case SQFS_INODE_EXT_CDEV: + sb->st_rdev = node->inode->data.dev_ext.devno; + break; + case SQFS_INODE_SLINK: + sb->st_size = node->inode->data.slink.target_size; + break; + case SQFS_INODE_EXT_SLINK: + sb->st_size = node->inode->data.slink_ext.target_size; + break; + case SQFS_INODE_FILE: + sb->st_size = node->inode->data.file.file_size; + break; + case SQFS_INODE_EXT_FILE: + sb->st_size = node->inode->data.file_ext.file_size; + break; + case SQFS_INODE_DIR: + sb->st_size = node->inode->data.dir.size; + break; + case SQFS_INODE_EXT_DIR: + sb->st_size = node->inode->data.dir_ext.size; + break; + default: + break; + } +} + +static int write_tree_dfs(const sqfs_tree_node_t *n) +{ + sqfs_hard_link_t *lnk = NULL; + tar_xattr_t *xattr = NULL; + char *name, *target; + struct stat sb; + size_t len; + int ret; + + inode_stat(n, &sb); + + if (n->parent == NULL) { + if (root_becomes == NULL) + goto skip_hdr; + + len = strlen(root_becomes); + name = malloc(len + 2); + if (name == NULL) { + perror("creating root directory"); + return -1; + } + + memcpy(name, root_becomes, len); + name[len] = '/'; + name[len + 1] = '\0'; + } else { + if (!is_filename_sane((const char *)n->name, false)) { + fprintf(stderr, "Found a file named '%s', skipping.\n", + n->name); + if (dont_skip) { + fputs("Not allowed to skip files, aborting!\n", + stderr); + return -1; + } + return 0; + } + + ret = sqfs_tree_node_get_path(n, &name); + if (ret != 0) { + sqfs_perror(NULL, "resolving tree node path", ret); + return -1; + } + + if (canonicalize_name(name)) + goto out_skip; + + name = assemble_tar_path(name, S_ISDIR(sb.st_mode)); + if (name == NULL) + return -1; + + lnk = find_hard_link(name, n->inode->base.inode_number); + if (lnk != NULL) { + ret = write_hard_link(out_file, &sb, name, lnk->target, + record_counter++); + sqfs_free(name); + return ret; + } + } + + if (!no_xattr) { + if (get_xattrs(name, n->inode, &xattr)) { + sqfs_free(name); + return -1; + } + } + + target = S_ISLNK(sb.st_mode) ? (char *)n->inode->extra : NULL; + ret = write_tar_header(out_file, &sb, name, target, xattr, + record_counter++); + free_xattr_list(xattr); + + if (ret > 0) + goto out_skip; + + if (ret < 0) { + sqfs_free(name); + return -1; + } + + if (S_ISREG(sb.st_mode)) { + if (sqfs_data_reader_dump(name, data, n->inode, out_file, + super.block_size)) { + sqfs_free(name); + return -1; + } + + if (padd_file(out_file, sb.st_size)) { + sqfs_free(name); + return -1; + } + } + + sqfs_free(name); +skip_hdr: + for (n = n->children; n != NULL; n = n->next) { + if (write_tree_dfs(n)) + return -1; + } + return 0; +out_skip: + if (dont_skip) { + fputs("Not allowed to skip files, aborting!\n", stderr); + ret = -1; + } else { + fprintf(stderr, "Skipping %s\n", name); + ret = 0; + } + sqfs_free(name); + return ret; +} + +int write_tree(const sqfs_tree_node_t *n) +{ + sqfs_hard_link_t *lnk; + int status = -1; + + if (!no_links) { + int ret = sqfs_tree_find_hard_links(n, &links); + if (ret) { + sqfs_perror(NULL, "detecting hard links in " + "file system tree", ret); + return -1; + } + + for (lnk = links; lnk != NULL; lnk = lnk->next) { + lnk->target = assemble_tar_path(lnk->target, false); + + if (lnk->target == NULL) + goto out_links; + } + } + + status = write_tree_dfs(n); +out_links: + while (links != NULL) { + lnk = links; + links = links->next; + sqfs_free(lnk->target); + free(lnk); + } + return status; +} diff --git a/bin/sqfs2tar/src/xattr.c b/bin/sqfs2tar/src/xattr.c new file mode 100644 index 0000000..abec4fb --- /dev/null +++ b/bin/sqfs2tar/src/xattr.c @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * xattr.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfs2tar.h" + +static tar_xattr_t *mkxattr(const sqfs_xattr_entry_t *key, + const sqfs_xattr_value_t *value) +{ + tar_xattr_t *ent; + + ent = calloc(1, sizeof(*ent) + strlen((const char *)key->key) + + value->size + 2); + + if (ent == NULL) { + perror("creating xattr entry"); + return NULL; + } + + ent->key = ent->data; + ent->value = (sqfs_u8 *)ent->key + strlen((const char *)key->key) + 1; + ent->value_len = value->size; + + strcpy(ent->key, (const char *)key->key); + memcpy(ent->value, value->value, value->size + 1); + return ent; +} + +int get_xattrs(const char *name, const sqfs_inode_generic_t *inode, + tar_xattr_t **out) +{ + tar_xattr_t *list = NULL, *ent; + sqfs_xattr_value_t *value; + sqfs_xattr_entry_t *key; + sqfs_xattr_id_t desc; + sqfs_u32 index; + size_t i; + int ret; + + if (xr == NULL) + return 0; + + sqfs_inode_get_xattr_index(inode, &index); + if (index == 0xFFFFFFFF) + return 0; + + ret = sqfs_xattr_reader_get_desc(xr, index, &desc); + if (ret) { + sqfs_perror(name, "resolving xattr index", ret); + return -1; + } + + ret = sqfs_xattr_reader_seek_kv(xr, &desc); + if (ret) { + sqfs_perror(name, "locating xattr key-value pairs", ret); + return -1; + } + + for (i = 0; i < desc.count; ++i) { + ret = sqfs_xattr_reader_read_key(xr, &key); + if (ret) { + sqfs_perror(name, "reading xattr key", ret); + goto fail; + } + + ret = sqfs_xattr_reader_read_value(xr, key, &value); + if (ret) { + sqfs_perror(name, "reading xattr value", ret); + sqfs_free(key); + goto fail; + } + + ent = mkxattr(key, value); + sqfs_free(key); + sqfs_free(value); + + if (ent == NULL) + goto fail; + + ent->next = list; + list = ent; + } + + *out = list; + return 0; +fail: + free_xattr_list(list); + return -1; +} diff --git a/bin/sqfs2tar/write_tree.c b/bin/sqfs2tar/write_tree.c deleted file mode 100644 index 354ec21..0000000 --- a/bin/sqfs2tar/write_tree.c +++ /dev/null @@ -1,209 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * write_tree.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfs2tar.h" - -static sqfs_hard_link_t *links = NULL; -static unsigned int record_counter; - -static sqfs_hard_link_t *find_hard_link(const char *name, sqfs_u32 inum) -{ - sqfs_hard_link_t *lnk = NULL; - - for (lnk = links; lnk != NULL; lnk = lnk->next) { - if (lnk->inode_number == inum) { - if (strcmp(name, lnk->target) == 0) - lnk = NULL; - break; - } - } - - return lnk; -} - -static void inode_stat(const sqfs_tree_node_t *node, struct stat *sb) -{ - memset(sb, 0, sizeof(*sb)); - - sb->st_mode = node->inode->base.mode; - sb->st_uid = node->uid; - sb->st_gid = node->gid; - sb->st_mtime = node->inode->base.mod_time; - - switch (node->inode->base.type) { - case SQFS_INODE_BDEV: - case SQFS_INODE_CDEV: - sb->st_rdev = node->inode->data.dev.devno; - break; - case SQFS_INODE_EXT_BDEV: - case SQFS_INODE_EXT_CDEV: - sb->st_rdev = node->inode->data.dev_ext.devno; - break; - case SQFS_INODE_SLINK: - sb->st_size = node->inode->data.slink.target_size; - break; - case SQFS_INODE_EXT_SLINK: - sb->st_size = node->inode->data.slink_ext.target_size; - break; - case SQFS_INODE_FILE: - sb->st_size = node->inode->data.file.file_size; - break; - case SQFS_INODE_EXT_FILE: - sb->st_size = node->inode->data.file_ext.file_size; - break; - case SQFS_INODE_DIR: - sb->st_size = node->inode->data.dir.size; - break; - case SQFS_INODE_EXT_DIR: - sb->st_size = node->inode->data.dir_ext.size; - break; - default: - break; - } -} - -static int write_tree_dfs(const sqfs_tree_node_t *n) -{ - sqfs_hard_link_t *lnk = NULL; - tar_xattr_t *xattr = NULL; - char *name, *target; - struct stat sb; - size_t len; - int ret; - - inode_stat(n, &sb); - - if (n->parent == NULL) { - if (root_becomes == NULL) - goto skip_hdr; - - len = strlen(root_becomes); - name = malloc(len + 2); - if (name == NULL) { - perror("creating root directory"); - return -1; - } - - memcpy(name, root_becomes, len); - name[len] = '/'; - name[len + 1] = '\0'; - } else { - if (!is_filename_sane((const char *)n->name, false)) { - fprintf(stderr, "Found a file named '%s', skipping.\n", - n->name); - if (dont_skip) { - fputs("Not allowed to skip files, aborting!\n", - stderr); - return -1; - } - return 0; - } - - ret = sqfs_tree_node_get_path(n, &name); - if (ret != 0) { - sqfs_perror(NULL, "resolving tree node path", ret); - return -1; - } - - if (canonicalize_name(name)) - goto out_skip; - - name = assemble_tar_path(name, S_ISDIR(sb.st_mode)); - if (name == NULL) - return -1; - - lnk = find_hard_link(name, n->inode->base.inode_number); - if (lnk != NULL) { - ret = write_hard_link(out_file, &sb, name, lnk->target, - record_counter++); - sqfs_free(name); - return ret; - } - } - - if (!no_xattr) { - if (get_xattrs(name, n->inode, &xattr)) { - sqfs_free(name); - return -1; - } - } - - target = S_ISLNK(sb.st_mode) ? (char *)n->inode->extra : NULL; - ret = write_tar_header(out_file, &sb, name, target, xattr, - record_counter++); - free_xattr_list(xattr); - - if (ret > 0) - goto out_skip; - - if (ret < 0) { - sqfs_free(name); - return -1; - } - - if (S_ISREG(sb.st_mode)) { - if (sqfs_data_reader_dump(name, data, n->inode, out_file, - super.block_size)) { - sqfs_free(name); - return -1; - } - - if (padd_file(out_file, sb.st_size)) { - sqfs_free(name); - return -1; - } - } - - sqfs_free(name); -skip_hdr: - for (n = n->children; n != NULL; n = n->next) { - if (write_tree_dfs(n)) - return -1; - } - return 0; -out_skip: - if (dont_skip) { - fputs("Not allowed to skip files, aborting!\n", stderr); - ret = -1; - } else { - fprintf(stderr, "Skipping %s\n", name); - ret = 0; - } - sqfs_free(name); - return ret; -} - -int write_tree(const sqfs_tree_node_t *n) -{ - sqfs_hard_link_t *lnk; - int status = -1; - - if (!no_links) { - int ret = sqfs_tree_find_hard_links(n, &links); - if (ret) { - sqfs_perror(NULL, "detecting hard links in " - "file system tree", ret); - return -1; - } - - for (lnk = links; lnk != NULL; lnk = lnk->next) { - lnk->target = assemble_tar_path(lnk->target, false); - - if (lnk->target == NULL) - goto out_links; - } - } - - status = write_tree_dfs(n); -out_links: - while (links != NULL) { - lnk = links; - links = links->next; - sqfs_free(lnk->target); - free(lnk); - } - return status; -} diff --git a/bin/sqfs2tar/xattr.c b/bin/sqfs2tar/xattr.c deleted file mode 100644 index abec4fb..0000000 --- a/bin/sqfs2tar/xattr.c +++ /dev/null @@ -1,91 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * xattr.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfs2tar.h" - -static tar_xattr_t *mkxattr(const sqfs_xattr_entry_t *key, - const sqfs_xattr_value_t *value) -{ - tar_xattr_t *ent; - - ent = calloc(1, sizeof(*ent) + strlen((const char *)key->key) + - value->size + 2); - - if (ent == NULL) { - perror("creating xattr entry"); - return NULL; - } - - ent->key = ent->data; - ent->value = (sqfs_u8 *)ent->key + strlen((const char *)key->key) + 1; - ent->value_len = value->size; - - strcpy(ent->key, (const char *)key->key); - memcpy(ent->value, value->value, value->size + 1); - return ent; -} - -int get_xattrs(const char *name, const sqfs_inode_generic_t *inode, - tar_xattr_t **out) -{ - tar_xattr_t *list = NULL, *ent; - sqfs_xattr_value_t *value; - sqfs_xattr_entry_t *key; - sqfs_xattr_id_t desc; - sqfs_u32 index; - size_t i; - int ret; - - if (xr == NULL) - return 0; - - sqfs_inode_get_xattr_index(inode, &index); - if (index == 0xFFFFFFFF) - return 0; - - ret = sqfs_xattr_reader_get_desc(xr, index, &desc); - if (ret) { - sqfs_perror(name, "resolving xattr index", ret); - return -1; - } - - ret = sqfs_xattr_reader_seek_kv(xr, &desc); - if (ret) { - sqfs_perror(name, "locating xattr key-value pairs", ret); - return -1; - } - - for (i = 0; i < desc.count; ++i) { - ret = sqfs_xattr_reader_read_key(xr, &key); - if (ret) { - sqfs_perror(name, "reading xattr key", ret); - goto fail; - } - - ret = sqfs_xattr_reader_read_value(xr, key, &value); - if (ret) { - sqfs_perror(name, "reading xattr value", ret); - sqfs_free(key); - goto fail; - } - - ent = mkxattr(key, value); - sqfs_free(key); - sqfs_free(value); - - if (ent == NULL) - goto fail; - - ent->next = list; - list = ent; - } - - *out = list; - return 0; -fail: - free_xattr_list(list); - return -1; -} diff --git a/bin/sqfsdiff/Makemodule.am b/bin/sqfsdiff/Makemodule.am index ff08c7a..4f21901 100644 --- a/bin/sqfsdiff/Makemodule.am +++ b/bin/sqfsdiff/Makemodule.am @@ -1,8 +1,8 @@ -sqfsdiff_SOURCES = bin/sqfsdiff/sqfsdiff.c bin/sqfsdiff/sqfsdiff.h -sqfsdiff_SOURCES += bin/sqfsdiff/util.c bin/sqfsdiff/options.c -sqfsdiff_SOURCES += bin/sqfsdiff/compare_dir.c bin/sqfsdiff/node_compare.c -sqfsdiff_SOURCES += bin/sqfsdiff/compare_files.c bin/sqfsdiff/super.c -sqfsdiff_SOURCES += bin/sqfsdiff/extract.c +sqfsdiff_SOURCES = bin/sqfsdiff/src/sqfsdiff.c bin/sqfsdiff/src/sqfsdiff.h \ + bin/sqfsdiff/src/util.c bin/sqfsdiff/src/options.c \ + bin/sqfsdiff/src/compare_dir.c bin/sqfsdiff/src/node_compare.c \ + bin/sqfsdiff/src/compare_files.c bin/sqfsdiff/src/super.c \ + bin/sqfsdiff/src/extract.c sqfsdiff_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) sqfsdiff_LDADD = libcommon.a libsquashfs.la libio.a libcompat.a libutil.a sqfsdiff_LDADD += $(LZO_LIBS) libfstree.a $(PTHREAD_LIBS) diff --git a/bin/sqfsdiff/compare_dir.c b/bin/sqfsdiff/compare_dir.c deleted file mode 100644 index 1a4c800..0000000 --- a/bin/sqfsdiff/compare_dir.c +++ /dev/null @@ -1,94 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * compare_dir.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfsdiff.h" - -static int print_omitted(sqfsdiff_t *sd, bool is_old, sqfs_tree_node_t *n) -{ - char *path = node_path(n); - - if (path == NULL) - return -1; - - fprintf(stdout, "%c %s\n", is_old ? '<' : '>', path); - - if ((sd->compare_flags & COMPARE_EXTRACT_FILES) && - S_ISREG(n->inode->base.mode)) { - if (extract_files(sd, is_old ? n->inode : NULL, - is_old ? NULL : n->inode, path)) { - free(path); - return -1; - } - } - - free(path); - - for (n = n->children; n != NULL; n = n->next) { - if (print_omitted(sd, is_old, n)) - return -1; - } - - return 0; -} - -int compare_dir_entries(sqfsdiff_t *sd, sqfs_tree_node_t *old, - sqfs_tree_node_t *new) -{ - sqfs_tree_node_t *old_it = old->children, *old_prev = NULL; - sqfs_tree_node_t *new_it = new->children, *new_prev = NULL; - int ret, result = 0; - - while (old_it != NULL || new_it != NULL) { - if (old_it != NULL && new_it != NULL) { - ret = strcmp((const char *)old_it->name, - (const char *)new_it->name); - } else if (old_it == NULL) { - ret = 1; - } else { - ret = -1; - } - - if (ret < 0) { - result = 1; - - if (print_omitted(sd, true, old_it)) - return -1; - - if (old_prev == NULL) { - old->children = old_it->next; - sqfs_dir_tree_destroy(old_it); - old_it = old->children; - } else { - old_prev->next = old_it->next; - sqfs_dir_tree_destroy(old_it); - old_it = old_prev->next; - } - } else if (ret > 0) { - result = 1; - - if (print_omitted(sd, false, new_it)) - return -1; - - if (new_prev == NULL) { - new->children = new_it->next; - sqfs_dir_tree_destroy(new_it); - new_it = new->children; - } else { - new_prev->next = new_it->next; - sqfs_dir_tree_destroy(new_it); - new_it = new_prev->next; - } - } else { - old_prev = old_it; - old_it = old_it->next; - - new_prev = new_it; - new_it = new_it->next; - } - } - - return result; -} diff --git a/bin/sqfsdiff/compare_files.c b/bin/sqfsdiff/compare_files.c deleted file mode 100644 index 51b66bb..0000000 --- a/bin/sqfsdiff/compare_files.c +++ /dev/null @@ -1,72 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * compare_files.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfsdiff.h" - -static unsigned char old_buf[MAX_WINDOW_SIZE]; -static unsigned char new_buf[MAX_WINDOW_SIZE]; - -static int read_blob(const char *prefix, const char *path, - sqfs_data_reader_t *rd, const sqfs_inode_generic_t *inode, - void *buffer, sqfs_u64 offset, size_t size) -{ - ssize_t ret; - - ret = sqfs_data_reader_read(rd, inode, offset, buffer, size); - ret = (ret < 0 || (size_t)ret < size) ? -1 : 0; - - if (ret) { - fprintf(stderr, "Failed to read %s from %s\n", - path, prefix); - return -1; - } - - return 0; -} - -int compare_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, - const sqfs_inode_generic_t *new, const char *path) -{ - sqfs_u64 offset, diff, oldsz, newsz; - int status = 0, ret; - - sqfs_inode_get_file_size(old, &oldsz); - sqfs_inode_get_file_size(new, &newsz); - - if (oldsz != newsz) - goto out_different; - - if (sd->compare_flags & COMPARE_NO_CONTENTS) - return 0; - - for (offset = 0; offset < oldsz; offset += diff) { - diff = oldsz - offset; - - if (diff > MAX_WINDOW_SIZE) - diff = MAX_WINDOW_SIZE; - - ret = read_blob(sd->old_path, path, - sd->sqfs_old.data, old, old_buf, offset, diff); - if (ret) - return -1; - - ret = read_blob(sd->new_path, path, - sd->sqfs_new.data, new, new_buf, offset, diff); - if (ret) - return -1; - - if (memcmp(old_buf, new_buf, diff) != 0) - goto out_different; - } - - return status; -out_different: - if (sd->compare_flags & COMPARE_EXTRACT_FILES) { - if (extract_files(sd, old, new, path)) - return -1; - } - return 1; -} diff --git a/bin/sqfsdiff/extract.c b/bin/sqfsdiff/extract.c deleted file mode 100644 index f2072d4..0000000 --- a/bin/sqfsdiff/extract.c +++ /dev/null @@ -1,58 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * extract.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfsdiff.h" - -static int extract(sqfs_data_reader_t *data, const sqfs_inode_generic_t *inode, - const char *prefix, const char *path, size_t block_size) -{ - char *ptr, *temp; - ostream_t *fp; - - temp = alloca(strlen(prefix) + strlen(path) + 2); - sprintf(temp, "%s/%s", prefix, path); - - ptr = strrchr(temp, '/'); - *ptr = '\0'; - if (mkdir_p(temp)) - return -1; - *ptr = '/'; - - fp = ostream_open_file(temp, OSTREAM_OPEN_OVERWRITE | - OSTREAM_OPEN_SPARSE); - if (fp == NULL) { - perror(temp); - return -1; - } - - if (sqfs_data_reader_dump(path, data, inode, fp, block_size)) { - sqfs_drop(fp); - return -1; - } - - ostream_flush(fp); - sqfs_drop(fp); - return 0; -} - -int extract_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, - const sqfs_inode_generic_t *new, - const char *path) -{ - if (old != NULL) { - if (extract(sd->sqfs_old.data, old, "old", - path, sd->sqfs_old.super.block_size)) - return -1; - } - - if (new != NULL) { - if (extract(sd->sqfs_new.data, new, "new", - path, sd->sqfs_new.super.block_size)) - return -1; - } - - return 0; -} diff --git a/bin/sqfsdiff/node_compare.c b/bin/sqfsdiff/node_compare.c deleted file mode 100644 index a0c99c7..0000000 --- a/bin/sqfsdiff/node_compare.c +++ /dev/null @@ -1,206 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * node_compare.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfsdiff.h" - -int node_compare(sqfsdiff_t *sd, sqfs_tree_node_t *a, sqfs_tree_node_t *b) -{ - sqfs_tree_node_t *ait, *bit; - bool promoted, demoted; - int ret, status = 0; - char *path; - - ret = sqfs_tree_node_get_path(a, &path); - if (ret != 0) { - sqfs_perror(NULL, "constructing absolute file path", ret); - return -1; - } - - if (a->inode->base.type != b->inode->base.type) { - promoted = demoted = false; - - switch (a->inode->base.type) { - case SQFS_INODE_DIR: - if (b->inode->base.type == SQFS_INODE_EXT_DIR) - promoted = true; - break; - case SQFS_INODE_FILE: - if (b->inode->base.type == SQFS_INODE_EXT_FILE) - promoted = true; - break; - case SQFS_INODE_SLINK: - if (b->inode->base.type == SQFS_INODE_EXT_SLINK) - promoted = true; - break; - case SQFS_INODE_BDEV: - if (b->inode->base.type == SQFS_INODE_EXT_BDEV) - promoted = true; - break; - case SQFS_INODE_CDEV: - if (b->inode->base.type == SQFS_INODE_EXT_CDEV) - promoted = true; - break; - case SQFS_INODE_FIFO: - if (b->inode->base.type == SQFS_INODE_EXT_FIFO) - promoted = true; - break; - case SQFS_INODE_SOCKET: - if (b->inode->base.type == SQFS_INODE_EXT_SOCKET) - promoted = true; - break; - case SQFS_INODE_EXT_DIR: - if (b->inode->base.type == SQFS_INODE_DIR) - demoted = true; - break; - case SQFS_INODE_EXT_FILE: - if (b->inode->base.type == SQFS_INODE_FILE) - demoted = true; - break; - case SQFS_INODE_EXT_SLINK: - if (b->inode->base.type == SQFS_INODE_SLINK) - demoted = true; - break; - case SQFS_INODE_EXT_BDEV: - if (b->inode->base.type == SQFS_INODE_BDEV) - demoted = true; - break; - case SQFS_INODE_EXT_CDEV: - if (b->inode->base.type == SQFS_INODE_CDEV) - demoted = true; - break; - case SQFS_INODE_EXT_FIFO: - if (b->inode->base.type == SQFS_INODE_FIFO) - demoted = true; - break; - case SQFS_INODE_EXT_SOCKET: - if (b->inode->base.type == SQFS_INODE_SOCKET) - demoted = true; - break; - default: - break; - } - - if (promoted) { - fprintf(stdout, "%s has an extended type\n", path); - status = 1; - } else if (demoted) { - fprintf(stdout, "%s has a basic type\n", path); - status = 1; - } else { - fprintf(stdout, "%s has a different type\n", path); - sqfs_free(path); - return 1; - } - } - - if (!(sd->compare_flags & COMPARE_NO_PERM)) { - if ((a->inode->base.mode & ~S_IFMT) != - (b->inode->base.mode & ~S_IFMT)) { - fprintf(stdout, "%s has different permissions\n", - path); - status = 1; - } - } - - if (!(sd->compare_flags & COMPARE_NO_OWNER)) { - if (a->uid != b->uid || a->gid != b->gid) { - fprintf(stdout, "%s has different ownership\n", path); - status = 1; - } - } - - if (sd->compare_flags & COMPARE_TIMESTAMP) { - if (a->inode->base.mod_time != b->inode->base.mod_time) { - fprintf(stdout, "%s has a different timestamp\n", path); - status = 1; - } - } - - if (sd->compare_flags & COMPARE_INODE_NUM) { - if (a->inode->base.inode_number != - b->inode->base.inode_number) { - fprintf(stdout, "%s has a different inode number\n", - path); - status = 1; - } - } - - switch (a->inode->base.type) { - case SQFS_INODE_SOCKET: - case SQFS_INODE_EXT_SOCKET: - case SQFS_INODE_FIFO: - case SQFS_INODE_EXT_FIFO: - break; - case SQFS_INODE_BDEV: - case SQFS_INODE_CDEV: - if (a->inode->data.dev.devno != b->inode->data.dev.devno) { - fprintf(stdout, "%s has different device number\n", - path); - status = 1; - } - break; - case SQFS_INODE_EXT_BDEV: - case SQFS_INODE_EXT_CDEV: - if (a->inode->data.dev_ext.devno != - b->inode->data.dev_ext.devno) { - fprintf(stdout, "%s has different device number\n", - path); - status = 1; - } - break; - case SQFS_INODE_SLINK: - case SQFS_INODE_EXT_SLINK: - if (strcmp((const char *)a->inode->extra, - (const char *)b->inode->extra)) { - fprintf(stdout, "%s has a different link target\n", - path); - } - break; - case SQFS_INODE_DIR: - case SQFS_INODE_EXT_DIR: - ret = compare_dir_entries(sd, a, b); - if (ret < 0) { - status = -1; - break; - } - if (ret > 0) - status = 1; - - sqfs_free(path); - path = NULL; - - ait = a->children; - bit = b->children; - - while (ait != NULL && bit != NULL) { - ret = node_compare(sd, ait, bit); - if (ret < 0) - return -1; - if (ret > 0) - status = 1; - - ait = ait->next; - bit = bit->next; - } - break; - case SQFS_INODE_FILE: - case SQFS_INODE_EXT_FILE: - ret = compare_files(sd, a->inode, b->inode, path); - if (ret < 0) { - status = -1; - } else if (ret > 0) { - fprintf(stdout, "regular file %s differs\n", path); - status = 1; - } - break; - default: - fprintf(stdout, "%s has unknown type, ignoring\n", path); - break; - } - - sqfs_free(path); - return status; -} diff --git a/bin/sqfsdiff/options.c b/bin/sqfsdiff/options.c deleted file mode 100644 index b8ce7f0..0000000 --- a/bin/sqfsdiff/options.c +++ /dev/null @@ -1,131 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * sqfsdiff.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfsdiff.h" - -static struct option long_opts[] = { - { "old", required_argument, NULL, 'a' }, - { "new", required_argument, NULL, 'b' }, - { "no-owner", no_argument, NULL, 'O' }, - { "no-permissions", no_argument, NULL, 'P' }, - { "no-contents", no_argument, NULL, 'C' }, - { "timestamps", no_argument, NULL, 'T' }, - { "inode-num", no_argument, NULL, 'I' }, - { "super", no_argument, NULL, 'S' }, - { "extract", required_argument, NULL, 'e' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { NULL, 0, NULL, 0 }, -}; - -static const char *short_opts = "a:b:OPCTISe:hV"; - -static const char *usagestr = -"Usage: sqfsdiff [OPTIONS...] --old,-a <first> --new,-b <second>\n" -"\n" -"Compare two squashfs images. In contrast to doing a direct diff of the\n" -"images, this actually parses the filesystems and generates a more\n" -"meaningful difference report.\n" -"\n" -"If only contents are compared, any differences in packed file layout,\n" -"ordering, compression, inode meta data and so on is ignored and the two\n" -"images are considered equal if each directory contains the same entries,\n" -"symlink with the same paths have the same targets, device nodes the same\n" -"device number and files the same size and contents.\n" -"\n" -"A report of any difference is printed to stdout. The exit status is similar\n" -"that of diff(1): 0 means equal, 1 means different, 2 means problem.\n" -"\n" -"Possible options:\n" -"\n" -" --old, -a <first> The first of the two filesystems to compare.\n" -" --new, -b <second> The second of the two filesystems to compare.\n" -"\n" -" --no-contents, -C Do not compare file contents.\n" -" --no-owner, -O Do not compare file owners.\n" -" --no-permissions, -P Do not compare permission bits.\n" -"\n" -" --timestamps, -T Compare file timestamps.\n" -" --inode-num, -I Compare inode numbers of all files.\n" -" --super, -S Also compare meta data in super blocks.\n" -"\n" -" --extract, -e <path> Extract files that differ to the specified\n" -" directory. Contents of the first filesystem\n" -" end up in a subdirectory 'old' and of the\n" -" second filesystem in a subdirectory 'new'.\n" -"\n" -" --help, -h Print help text and exit.\n" -" --version, -V Print version information and exit.\n" -"\n"; - -void process_options(sqfsdiff_t *sd, int argc, char **argv) -{ - int i; - - for (;;) { - i = getopt_long(argc, argv, short_opts, long_opts, NULL); - if (i == -1) - break; - - switch (i) { - case 'a': - sd->old_path = optarg; - break; - case 'b': - sd->new_path = optarg; - break; - case 'O': - sd->compare_flags |= COMPARE_NO_OWNER; - break; - case 'P': - sd->compare_flags |= COMPARE_NO_PERM; - break; - case 'C': - sd->compare_flags |= COMPARE_NO_CONTENTS; - break; - case 'T': - sd->compare_flags |= COMPARE_TIMESTAMP; - break; - case 'I': - sd->compare_flags |= COMPARE_INODE_NUM; - break; - case 'S': - sd->compare_super = true; - break; - case 'e': - sd->compare_flags |= COMPARE_EXTRACT_FILES; - sd->extract_dir = optarg; - break; - case 'h': - fputs(usagestr, stdout); - exit(0); - case 'V': - print_version("sqfsdiff"); - exit(0); - default: - goto fail_arg; - } - } - - if (sd->old_path == NULL) { - fputs("Missing arguments: first filesystem\n", stderr); - goto fail_arg; - } - - if (sd->new_path == NULL) { - fputs("Missing arguments: second filesystem\n", stderr); - goto fail_arg; - } - - if (optind < argc) { - fputs("Unknown extra arguments\n", stderr); - goto fail_arg; - } - return; -fail_arg: - fprintf(stderr, "Try `sqfsdiff --help' for more information.\n"); - exit(2); -} diff --git a/bin/sqfsdiff/sqfsdiff.c b/bin/sqfsdiff/sqfsdiff.c deleted file mode 100644 index d789fe1..0000000 --- a/bin/sqfsdiff/sqfsdiff.c +++ /dev/null @@ -1,167 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * sqfsdiff.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfsdiff.h" - -static void close_sfqs(sqfs_state_t *state) -{ - sqfs_drop(state->data); - sqfs_dir_tree_destroy(state->root); - sqfs_drop(state->dr); - sqfs_drop(state->idtbl); - sqfs_drop(state->cmp); - sqfs_drop(state->file); -} - -static int open_sfqs(sqfs_state_t *state, const char *path) -{ - int ret; - - memset(state, 0, sizeof(*state)); - - state->file = sqfs_open_file(path, SQFS_FILE_OPEN_READ_ONLY); - if (state->file == NULL) { - perror(path); - return -1; - } - - ret = sqfs_super_read(&state->super, state->file); - if (ret) { - sqfs_perror(path, "reading super block", ret); - goto fail; - } - - sqfs_compressor_config_init(&state->cfg, state->super.compression_id, - state->super.block_size, - SQFS_COMP_FLAG_UNCOMPRESS); - - ret = sqfs_compressor_create(&state->cfg, &state->cmp); - -#ifdef WITH_LZO - if (state->super.compression_id == SQFS_COMP_LZO && ret != 0) - ret = lzo_compressor_create(&state->cfg, &state->cmp); -#endif - - if (ret != 0) { - sqfs_perror(path, "creating compressor", ret); - goto fail; - } - - if (state->super.flags & SQFS_FLAG_COMPRESSOR_OPTIONS) { - ret = state->cmp->read_options(state->cmp, state->file); - - if (ret == 0) { - state->cmp->get_configuration(state->cmp, - &state->options); - state->have_options = true; - } else { - sqfs_perror(path, "reading compressor options", ret); - state->have_options = false; - } - } else { - state->have_options = false; - } - - state->idtbl = sqfs_id_table_create(0); - if (state->idtbl == NULL) { - sqfs_perror(path, "creating ID table", SQFS_ERROR_ALLOC); - goto fail; - } - - ret = sqfs_id_table_read(state->idtbl, state->file, - &state->super, state->cmp); - if (ret) { - sqfs_perror(path, "loading ID table", ret); - goto fail; - } - - state->dr = sqfs_dir_reader_create(&state->super, state->cmp, - state->file, 0); - if (state->dr == NULL) { - sqfs_perror(path, "creating directory reader", - SQFS_ERROR_ALLOC); - goto fail; - } - - ret = sqfs_dir_reader_get_full_hierarchy(state->dr, state->idtbl, - NULL, 0, &state->root); - if (ret) { - sqfs_perror(path, "loading filesystem tree", ret); - goto fail; - } - - state->data = sqfs_data_reader_create(state->file, - state->super.block_size, - state->cmp, 0); - if (state->data == NULL) { - sqfs_perror(path, "creating data reader", SQFS_ERROR_ALLOC); - goto fail; - } - - ret = sqfs_data_reader_load_fragment_table(state->data, &state->super); - if (ret) { - sqfs_perror(path, "loading fragment table", ret); - goto fail; - } - - return 0; -fail: - close_sfqs(state); - return -1; -} - -int main(int argc, char **argv) -{ - int status, ret = 0; - sqfsdiff_t sd; - - memset(&sd, 0, sizeof(sd)); - process_options(&sd, argc, argv); - - if (sd.extract_dir != NULL) { - if (mkdir_p(sd.extract_dir)) - return 2; - } - - if (open_sfqs(&sd.sqfs_old, sd.old_path)) - return 2; - - if (open_sfqs(&sd.sqfs_new, sd.new_path)) { - status = 2; - goto out_sqfs_old; - } - - if (sd.extract_dir != NULL) { - if (chdir(sd.extract_dir)) { - perror(sd.extract_dir); - ret = -1; - goto out; - } - } - - ret = node_compare(&sd, sd.sqfs_old.root, sd.sqfs_new.root); - if (ret != 0) - goto out; - - if (sd.compare_super) { - ret = compare_super_blocks(&sd.sqfs_old.super, - &sd.sqfs_new.super); - if (ret != 0) - goto out; - } -out: - if (ret < 0) { - status = 2; - } else if (ret > 0) { - status = 1; - } else { - status = 0; - } - close_sfqs(&sd.sqfs_new); -out_sqfs_old: - close_sfqs(&sd.sqfs_old); - return status; -} diff --git a/bin/sqfsdiff/sqfsdiff.h b/bin/sqfsdiff/sqfsdiff.h deleted file mode 100644 index 65e8120..0000000 --- a/bin/sqfsdiff/sqfsdiff.h +++ /dev/null @@ -1,73 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * sqfsdiff.h - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#ifndef DIFFTOOL_H -#define DIFFTOOL_H - -#include "config.h" -#include "common.h" -#include "fstree.h" -#include "util/util.h" - -#include <stdlib.h> -#include <getopt.h> -#include <string.h> -#include <errno.h> - -#define MAX_WINDOW_SIZE (1024 * 1024 * 4) - -typedef struct { - sqfs_compressor_config_t cfg; - sqfs_compressor_t *cmp; - sqfs_super_t super; - sqfs_file_t *file; - sqfs_id_table_t *idtbl; - sqfs_dir_reader_t *dr; - sqfs_tree_node_t *root; - sqfs_data_reader_t *data; - - sqfs_compressor_config_t options; - bool have_options; -} sqfs_state_t; - -typedef struct { - const char *old_path; - const char *new_path; - int compare_flags; - sqfs_state_t sqfs_old; - sqfs_state_t sqfs_new; - bool compare_super; - const char *extract_dir; -} sqfsdiff_t; - -enum { - COMPARE_NO_PERM = 0x01, - COMPARE_NO_OWNER = 0x02, - COMPARE_NO_CONTENTS = 0x04, - COMPARE_TIMESTAMP = 0x08, - COMPARE_INODE_NUM = 0x10, - COMPARE_EXTRACT_FILES = 0x20, -}; - -int compare_dir_entries(sqfsdiff_t *sd, sqfs_tree_node_t *old, - sqfs_tree_node_t *new); - -char *node_path(const sqfs_tree_node_t *n); - -int compare_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, - const sqfs_inode_generic_t *new, const char *path); - -int node_compare(sqfsdiff_t *sd, sqfs_tree_node_t *a, sqfs_tree_node_t *b); - -int compare_super_blocks(const sqfs_super_t *a, const sqfs_super_t *b); - -int extract_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, - const sqfs_inode_generic_t *new, - const char *path); - -void process_options(sqfsdiff_t *sd, int argc, char **argv); - -#endif /* DIFFTOOL_H */ diff --git a/bin/sqfsdiff/src/compare_dir.c b/bin/sqfsdiff/src/compare_dir.c new file mode 100644 index 0000000..1a4c800 --- /dev/null +++ b/bin/sqfsdiff/src/compare_dir.c @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * compare_dir.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfsdiff.h" + +static int print_omitted(sqfsdiff_t *sd, bool is_old, sqfs_tree_node_t *n) +{ + char *path = node_path(n); + + if (path == NULL) + return -1; + + fprintf(stdout, "%c %s\n", is_old ? '<' : '>', path); + + if ((sd->compare_flags & COMPARE_EXTRACT_FILES) && + S_ISREG(n->inode->base.mode)) { + if (extract_files(sd, is_old ? n->inode : NULL, + is_old ? NULL : n->inode, path)) { + free(path); + return -1; + } + } + + free(path); + + for (n = n->children; n != NULL; n = n->next) { + if (print_omitted(sd, is_old, n)) + return -1; + } + + return 0; +} + +int compare_dir_entries(sqfsdiff_t *sd, sqfs_tree_node_t *old, + sqfs_tree_node_t *new) +{ + sqfs_tree_node_t *old_it = old->children, *old_prev = NULL; + sqfs_tree_node_t *new_it = new->children, *new_prev = NULL; + int ret, result = 0; + + while (old_it != NULL || new_it != NULL) { + if (old_it != NULL && new_it != NULL) { + ret = strcmp((const char *)old_it->name, + (const char *)new_it->name); + } else if (old_it == NULL) { + ret = 1; + } else { + ret = -1; + } + + if (ret < 0) { + result = 1; + + if (print_omitted(sd, true, old_it)) + return -1; + + if (old_prev == NULL) { + old->children = old_it->next; + sqfs_dir_tree_destroy(old_it); + old_it = old->children; + } else { + old_prev->next = old_it->next; + sqfs_dir_tree_destroy(old_it); + old_it = old_prev->next; + } + } else if (ret > 0) { + result = 1; + + if (print_omitted(sd, false, new_it)) + return -1; + + if (new_prev == NULL) { + new->children = new_it->next; + sqfs_dir_tree_destroy(new_it); + new_it = new->children; + } else { + new_prev->next = new_it->next; + sqfs_dir_tree_destroy(new_it); + new_it = new_prev->next; + } + } else { + old_prev = old_it; + old_it = old_it->next; + + new_prev = new_it; + new_it = new_it->next; + } + } + + return result; +} diff --git a/bin/sqfsdiff/src/compare_files.c b/bin/sqfsdiff/src/compare_files.c new file mode 100644 index 0000000..51b66bb --- /dev/null +++ b/bin/sqfsdiff/src/compare_files.c @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * compare_files.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfsdiff.h" + +static unsigned char old_buf[MAX_WINDOW_SIZE]; +static unsigned char new_buf[MAX_WINDOW_SIZE]; + +static int read_blob(const char *prefix, const char *path, + sqfs_data_reader_t *rd, const sqfs_inode_generic_t *inode, + void *buffer, sqfs_u64 offset, size_t size) +{ + ssize_t ret; + + ret = sqfs_data_reader_read(rd, inode, offset, buffer, size); + ret = (ret < 0 || (size_t)ret < size) ? -1 : 0; + + if (ret) { + fprintf(stderr, "Failed to read %s from %s\n", + path, prefix); + return -1; + } + + return 0; +} + +int compare_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, + const sqfs_inode_generic_t *new, const char *path) +{ + sqfs_u64 offset, diff, oldsz, newsz; + int status = 0, ret; + + sqfs_inode_get_file_size(old, &oldsz); + sqfs_inode_get_file_size(new, &newsz); + + if (oldsz != newsz) + goto out_different; + + if (sd->compare_flags & COMPARE_NO_CONTENTS) + return 0; + + for (offset = 0; offset < oldsz; offset += diff) { + diff = oldsz - offset; + + if (diff > MAX_WINDOW_SIZE) + diff = MAX_WINDOW_SIZE; + + ret = read_blob(sd->old_path, path, + sd->sqfs_old.data, old, old_buf, offset, diff); + if (ret) + return -1; + + ret = read_blob(sd->new_path, path, + sd->sqfs_new.data, new, new_buf, offset, diff); + if (ret) + return -1; + + if (memcmp(old_buf, new_buf, diff) != 0) + goto out_different; + } + + return status; +out_different: + if (sd->compare_flags & COMPARE_EXTRACT_FILES) { + if (extract_files(sd, old, new, path)) + return -1; + } + return 1; +} diff --git a/bin/sqfsdiff/src/extract.c b/bin/sqfsdiff/src/extract.c new file mode 100644 index 0000000..f2072d4 --- /dev/null +++ b/bin/sqfsdiff/src/extract.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * extract.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfsdiff.h" + +static int extract(sqfs_data_reader_t *data, const sqfs_inode_generic_t *inode, + const char *prefix, const char *path, size_t block_size) +{ + char *ptr, *temp; + ostream_t *fp; + + temp = alloca(strlen(prefix) + strlen(path) + 2); + sprintf(temp, "%s/%s", prefix, path); + + ptr = strrchr(temp, '/'); + *ptr = '\0'; + if (mkdir_p(temp)) + return -1; + *ptr = '/'; + + fp = ostream_open_file(temp, OSTREAM_OPEN_OVERWRITE | + OSTREAM_OPEN_SPARSE); + if (fp == NULL) { + perror(temp); + return -1; + } + + if (sqfs_data_reader_dump(path, data, inode, fp, block_size)) { + sqfs_drop(fp); + return -1; + } + + ostream_flush(fp); + sqfs_drop(fp); + return 0; +} + +int extract_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, + const sqfs_inode_generic_t *new, + const char *path) +{ + if (old != NULL) { + if (extract(sd->sqfs_old.data, old, "old", + path, sd->sqfs_old.super.block_size)) + return -1; + } + + if (new != NULL) { + if (extract(sd->sqfs_new.data, new, "new", + path, sd->sqfs_new.super.block_size)) + return -1; + } + + return 0; +} diff --git a/bin/sqfsdiff/src/node_compare.c b/bin/sqfsdiff/src/node_compare.c new file mode 100644 index 0000000..a0c99c7 --- /dev/null +++ b/bin/sqfsdiff/src/node_compare.c @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * node_compare.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfsdiff.h" + +int node_compare(sqfsdiff_t *sd, sqfs_tree_node_t *a, sqfs_tree_node_t *b) +{ + sqfs_tree_node_t *ait, *bit; + bool promoted, demoted; + int ret, status = 0; + char *path; + + ret = sqfs_tree_node_get_path(a, &path); + if (ret != 0) { + sqfs_perror(NULL, "constructing absolute file path", ret); + return -1; + } + + if (a->inode->base.type != b->inode->base.type) { + promoted = demoted = false; + + switch (a->inode->base.type) { + case SQFS_INODE_DIR: + if (b->inode->base.type == SQFS_INODE_EXT_DIR) + promoted = true; + break; + case SQFS_INODE_FILE: + if (b->inode->base.type == SQFS_INODE_EXT_FILE) + promoted = true; + break; + case SQFS_INODE_SLINK: + if (b->inode->base.type == SQFS_INODE_EXT_SLINK) + promoted = true; + break; + case SQFS_INODE_BDEV: + if (b->inode->base.type == SQFS_INODE_EXT_BDEV) + promoted = true; + break; + case SQFS_INODE_CDEV: + if (b->inode->base.type == SQFS_INODE_EXT_CDEV) + promoted = true; + break; + case SQFS_INODE_FIFO: + if (b->inode->base.type == SQFS_INODE_EXT_FIFO) + promoted = true; + break; + case SQFS_INODE_SOCKET: + if (b->inode->base.type == SQFS_INODE_EXT_SOCKET) + promoted = true; + break; + case SQFS_INODE_EXT_DIR: + if (b->inode->base.type == SQFS_INODE_DIR) + demoted = true; + break; + case SQFS_INODE_EXT_FILE: + if (b->inode->base.type == SQFS_INODE_FILE) + demoted = true; + break; + case SQFS_INODE_EXT_SLINK: + if (b->inode->base.type == SQFS_INODE_SLINK) + demoted = true; + break; + case SQFS_INODE_EXT_BDEV: + if (b->inode->base.type == SQFS_INODE_BDEV) + demoted = true; + break; + case SQFS_INODE_EXT_CDEV: + if (b->inode->base.type == SQFS_INODE_CDEV) + demoted = true; + break; + case SQFS_INODE_EXT_FIFO: + if (b->inode->base.type == SQFS_INODE_FIFO) + demoted = true; + break; + case SQFS_INODE_EXT_SOCKET: + if (b->inode->base.type == SQFS_INODE_SOCKET) + demoted = true; + break; + default: + break; + } + + if (promoted) { + fprintf(stdout, "%s has an extended type\n", path); + status = 1; + } else if (demoted) { + fprintf(stdout, "%s has a basic type\n", path); + status = 1; + } else { + fprintf(stdout, "%s has a different type\n", path); + sqfs_free(path); + return 1; + } + } + + if (!(sd->compare_flags & COMPARE_NO_PERM)) { + if ((a->inode->base.mode & ~S_IFMT) != + (b->inode->base.mode & ~S_IFMT)) { + fprintf(stdout, "%s has different permissions\n", + path); + status = 1; + } + } + + if (!(sd->compare_flags & COMPARE_NO_OWNER)) { + if (a->uid != b->uid || a->gid != b->gid) { + fprintf(stdout, "%s has different ownership\n", path); + status = 1; + } + } + + if (sd->compare_flags & COMPARE_TIMESTAMP) { + if (a->inode->base.mod_time != b->inode->base.mod_time) { + fprintf(stdout, "%s has a different timestamp\n", path); + status = 1; + } + } + + if (sd->compare_flags & COMPARE_INODE_NUM) { + if (a->inode->base.inode_number != + b->inode->base.inode_number) { + fprintf(stdout, "%s has a different inode number\n", + path); + status = 1; + } + } + + switch (a->inode->base.type) { + case SQFS_INODE_SOCKET: + case SQFS_INODE_EXT_SOCKET: + case SQFS_INODE_FIFO: + case SQFS_INODE_EXT_FIFO: + break; + case SQFS_INODE_BDEV: + case SQFS_INODE_CDEV: + if (a->inode->data.dev.devno != b->inode->data.dev.devno) { + fprintf(stdout, "%s has different device number\n", + path); + status = 1; + } + break; + case SQFS_INODE_EXT_BDEV: + case SQFS_INODE_EXT_CDEV: + if (a->inode->data.dev_ext.devno != + b->inode->data.dev_ext.devno) { + fprintf(stdout, "%s has different device number\n", + path); + status = 1; + } + break; + case SQFS_INODE_SLINK: + case SQFS_INODE_EXT_SLINK: + if (strcmp((const char *)a->inode->extra, + (const char *)b->inode->extra)) { + fprintf(stdout, "%s has a different link target\n", + path); + } + break; + case SQFS_INODE_DIR: + case SQFS_INODE_EXT_DIR: + ret = compare_dir_entries(sd, a, b); + if (ret < 0) { + status = -1; + break; + } + if (ret > 0) + status = 1; + + sqfs_free(path); + path = NULL; + + ait = a->children; + bit = b->children; + + while (ait != NULL && bit != NULL) { + ret = node_compare(sd, ait, bit); + if (ret < 0) + return -1; + if (ret > 0) + status = 1; + + ait = ait->next; + bit = bit->next; + } + break; + case SQFS_INODE_FILE: + case SQFS_INODE_EXT_FILE: + ret = compare_files(sd, a->inode, b->inode, path); + if (ret < 0) { + status = -1; + } else if (ret > 0) { + fprintf(stdout, "regular file %s differs\n", path); + status = 1; + } + break; + default: + fprintf(stdout, "%s has unknown type, ignoring\n", path); + break; + } + + sqfs_free(path); + return status; +} diff --git a/bin/sqfsdiff/src/options.c b/bin/sqfsdiff/src/options.c new file mode 100644 index 0000000..b8ce7f0 --- /dev/null +++ b/bin/sqfsdiff/src/options.c @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * sqfsdiff.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfsdiff.h" + +static struct option long_opts[] = { + { "old", required_argument, NULL, 'a' }, + { "new", required_argument, NULL, 'b' }, + { "no-owner", no_argument, NULL, 'O' }, + { "no-permissions", no_argument, NULL, 'P' }, + { "no-contents", no_argument, NULL, 'C' }, + { "timestamps", no_argument, NULL, 'T' }, + { "inode-num", no_argument, NULL, 'I' }, + { "super", no_argument, NULL, 'S' }, + { "extract", required_argument, NULL, 'e' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *short_opts = "a:b:OPCTISe:hV"; + +static const char *usagestr = +"Usage: sqfsdiff [OPTIONS...] --old,-a <first> --new,-b <second>\n" +"\n" +"Compare two squashfs images. In contrast to doing a direct diff of the\n" +"images, this actually parses the filesystems and generates a more\n" +"meaningful difference report.\n" +"\n" +"If only contents are compared, any differences in packed file layout,\n" +"ordering, compression, inode meta data and so on is ignored and the two\n" +"images are considered equal if each directory contains the same entries,\n" +"symlink with the same paths have the same targets, device nodes the same\n" +"device number and files the same size and contents.\n" +"\n" +"A report of any difference is printed to stdout. The exit status is similar\n" +"that of diff(1): 0 means equal, 1 means different, 2 means problem.\n" +"\n" +"Possible options:\n" +"\n" +" --old, -a <first> The first of the two filesystems to compare.\n" +" --new, -b <second> The second of the two filesystems to compare.\n" +"\n" +" --no-contents, -C Do not compare file contents.\n" +" --no-owner, -O Do not compare file owners.\n" +" --no-permissions, -P Do not compare permission bits.\n" +"\n" +" --timestamps, -T Compare file timestamps.\n" +" --inode-num, -I Compare inode numbers of all files.\n" +" --super, -S Also compare meta data in super blocks.\n" +"\n" +" --extract, -e <path> Extract files that differ to the specified\n" +" directory. Contents of the first filesystem\n" +" end up in a subdirectory 'old' and of the\n" +" second filesystem in a subdirectory 'new'.\n" +"\n" +" --help, -h Print help text and exit.\n" +" --version, -V Print version information and exit.\n" +"\n"; + +void process_options(sqfsdiff_t *sd, int argc, char **argv) +{ + int i; + + for (;;) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 'a': + sd->old_path = optarg; + break; + case 'b': + sd->new_path = optarg; + break; + case 'O': + sd->compare_flags |= COMPARE_NO_OWNER; + break; + case 'P': + sd->compare_flags |= COMPARE_NO_PERM; + break; + case 'C': + sd->compare_flags |= COMPARE_NO_CONTENTS; + break; + case 'T': + sd->compare_flags |= COMPARE_TIMESTAMP; + break; + case 'I': + sd->compare_flags |= COMPARE_INODE_NUM; + break; + case 'S': + sd->compare_super = true; + break; + case 'e': + sd->compare_flags |= COMPARE_EXTRACT_FILES; + sd->extract_dir = optarg; + break; + case 'h': + fputs(usagestr, stdout); + exit(0); + case 'V': + print_version("sqfsdiff"); + exit(0); + default: + goto fail_arg; + } + } + + if (sd->old_path == NULL) { + fputs("Missing arguments: first filesystem\n", stderr); + goto fail_arg; + } + + if (sd->new_path == NULL) { + fputs("Missing arguments: second filesystem\n", stderr); + goto fail_arg; + } + + if (optind < argc) { + fputs("Unknown extra arguments\n", stderr); + goto fail_arg; + } + return; +fail_arg: + fprintf(stderr, "Try `sqfsdiff --help' for more information.\n"); + exit(2); +} diff --git a/bin/sqfsdiff/src/sqfsdiff.c b/bin/sqfsdiff/src/sqfsdiff.c new file mode 100644 index 0000000..d789fe1 --- /dev/null +++ b/bin/sqfsdiff/src/sqfsdiff.c @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * sqfsdiff.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfsdiff.h" + +static void close_sfqs(sqfs_state_t *state) +{ + sqfs_drop(state->data); + sqfs_dir_tree_destroy(state->root); + sqfs_drop(state->dr); + sqfs_drop(state->idtbl); + sqfs_drop(state->cmp); + sqfs_drop(state->file); +} + +static int open_sfqs(sqfs_state_t *state, const char *path) +{ + int ret; + + memset(state, 0, sizeof(*state)); + + state->file = sqfs_open_file(path, SQFS_FILE_OPEN_READ_ONLY); + if (state->file == NULL) { + perror(path); + return -1; + } + + ret = sqfs_super_read(&state->super, state->file); + if (ret) { + sqfs_perror(path, "reading super block", ret); + goto fail; + } + + sqfs_compressor_config_init(&state->cfg, state->super.compression_id, + state->super.block_size, + SQFS_COMP_FLAG_UNCOMPRESS); + + ret = sqfs_compressor_create(&state->cfg, &state->cmp); + +#ifdef WITH_LZO + if (state->super.compression_id == SQFS_COMP_LZO && ret != 0) + ret = lzo_compressor_create(&state->cfg, &state->cmp); +#endif + + if (ret != 0) { + sqfs_perror(path, "creating compressor", ret); + goto fail; + } + + if (state->super.flags & SQFS_FLAG_COMPRESSOR_OPTIONS) { + ret = state->cmp->read_options(state->cmp, state->file); + + if (ret == 0) { + state->cmp->get_configuration(state->cmp, + &state->options); + state->have_options = true; + } else { + sqfs_perror(path, "reading compressor options", ret); + state->have_options = false; + } + } else { + state->have_options = false; + } + + state->idtbl = sqfs_id_table_create(0); + if (state->idtbl == NULL) { + sqfs_perror(path, "creating ID table", SQFS_ERROR_ALLOC); + goto fail; + } + + ret = sqfs_id_table_read(state->idtbl, state->file, + &state->super, state->cmp); + if (ret) { + sqfs_perror(path, "loading ID table", ret); + goto fail; + } + + state->dr = sqfs_dir_reader_create(&state->super, state->cmp, + state->file, 0); + if (state->dr == NULL) { + sqfs_perror(path, "creating directory reader", + SQFS_ERROR_ALLOC); + goto fail; + } + + ret = sqfs_dir_reader_get_full_hierarchy(state->dr, state->idtbl, + NULL, 0, &state->root); + if (ret) { + sqfs_perror(path, "loading filesystem tree", ret); + goto fail; + } + + state->data = sqfs_data_reader_create(state->file, + state->super.block_size, + state->cmp, 0); + if (state->data == NULL) { + sqfs_perror(path, "creating data reader", SQFS_ERROR_ALLOC); + goto fail; + } + + ret = sqfs_data_reader_load_fragment_table(state->data, &state->super); + if (ret) { + sqfs_perror(path, "loading fragment table", ret); + goto fail; + } + + return 0; +fail: + close_sfqs(state); + return -1; +} + +int main(int argc, char **argv) +{ + int status, ret = 0; + sqfsdiff_t sd; + + memset(&sd, 0, sizeof(sd)); + process_options(&sd, argc, argv); + + if (sd.extract_dir != NULL) { + if (mkdir_p(sd.extract_dir)) + return 2; + } + + if (open_sfqs(&sd.sqfs_old, sd.old_path)) + return 2; + + if (open_sfqs(&sd.sqfs_new, sd.new_path)) { + status = 2; + goto out_sqfs_old; + } + + if (sd.extract_dir != NULL) { + if (chdir(sd.extract_dir)) { + perror(sd.extract_dir); + ret = -1; + goto out; + } + } + + ret = node_compare(&sd, sd.sqfs_old.root, sd.sqfs_new.root); + if (ret != 0) + goto out; + + if (sd.compare_super) { + ret = compare_super_blocks(&sd.sqfs_old.super, + &sd.sqfs_new.super); + if (ret != 0) + goto out; + } +out: + if (ret < 0) { + status = 2; + } else if (ret > 0) { + status = 1; + } else { + status = 0; + } + close_sfqs(&sd.sqfs_new); +out_sqfs_old: + close_sfqs(&sd.sqfs_old); + return status; +} diff --git a/bin/sqfsdiff/src/sqfsdiff.h b/bin/sqfsdiff/src/sqfsdiff.h new file mode 100644 index 0000000..65e8120 --- /dev/null +++ b/bin/sqfsdiff/src/sqfsdiff.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * sqfsdiff.h + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#ifndef DIFFTOOL_H +#define DIFFTOOL_H + +#include "config.h" +#include "common.h" +#include "fstree.h" +#include "util/util.h" + +#include <stdlib.h> +#include <getopt.h> +#include <string.h> +#include <errno.h> + +#define MAX_WINDOW_SIZE (1024 * 1024 * 4) + +typedef struct { + sqfs_compressor_config_t cfg; + sqfs_compressor_t *cmp; + sqfs_super_t super; + sqfs_file_t *file; + sqfs_id_table_t *idtbl; + sqfs_dir_reader_t *dr; + sqfs_tree_node_t *root; + sqfs_data_reader_t *data; + + sqfs_compressor_config_t options; + bool have_options; +} sqfs_state_t; + +typedef struct { + const char *old_path; + const char *new_path; + int compare_flags; + sqfs_state_t sqfs_old; + sqfs_state_t sqfs_new; + bool compare_super; + const char *extract_dir; +} sqfsdiff_t; + +enum { + COMPARE_NO_PERM = 0x01, + COMPARE_NO_OWNER = 0x02, + COMPARE_NO_CONTENTS = 0x04, + COMPARE_TIMESTAMP = 0x08, + COMPARE_INODE_NUM = 0x10, + COMPARE_EXTRACT_FILES = 0x20, +}; + +int compare_dir_entries(sqfsdiff_t *sd, sqfs_tree_node_t *old, + sqfs_tree_node_t *new); + +char *node_path(const sqfs_tree_node_t *n); + +int compare_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, + const sqfs_inode_generic_t *new, const char *path); + +int node_compare(sqfsdiff_t *sd, sqfs_tree_node_t *a, sqfs_tree_node_t *b); + +int compare_super_blocks(const sqfs_super_t *a, const sqfs_super_t *b); + +int extract_files(sqfsdiff_t *sd, const sqfs_inode_generic_t *old, + const sqfs_inode_generic_t *new, + const char *path); + +void process_options(sqfsdiff_t *sd, int argc, char **argv); + +#endif /* DIFFTOOL_H */ diff --git a/bin/sqfsdiff/src/super.c b/bin/sqfsdiff/src/super.c new file mode 100644 index 0000000..0cf18e0 --- /dev/null +++ b/bin/sqfsdiff/src/super.c @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * super.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfsdiff.h" + +static const struct { + sqfs_u16 mask; + const char *name; +} sqfs_flags[] = { + { SQFS_FLAG_UNCOMPRESSED_INODES, "uncompressed inodes" }, + { SQFS_FLAG_UNCOMPRESSED_DATA, "uncompressed data" }, + { SQFS_FLAG_UNCOMPRESSED_FRAGMENTS, "uncompressed fragments" }, + { SQFS_FLAG_NO_FRAGMENTS, "no fragments" }, + { SQFS_FLAG_ALWAYS_FRAGMENTS, "always fragments" }, + { SQFS_FLAG_NO_DUPLICATES, "no duplicates" }, + { SQFS_FLAG_EXPORTABLE, "exportable" }, + { SQFS_FLAG_UNCOMPRESSED_XATTRS, "uncompressed xattrs" }, + { SQFS_FLAG_NO_XATTRS, "no xattrs" }, + { SQFS_FLAG_COMPRESSOR_OPTIONS, "compressor options" }, + { SQFS_FLAG_UNCOMPRESSED_IDS, "uncompressed ids" }, +}; + +static void print_value_difference(const char *name, sqfs_u64 a, sqfs_u64 b) +{ + sqfs_u64 diff; + char c; + + if (a != b) { + if (a < b) { + c = '+'; + diff = b - a; + } else { + c = '-'; + diff = a - b; + } + fprintf(stdout, "%s: %c%lu\n", name, c, + (unsigned long)diff); + } +} + +static void print_offset_diff(const char *name, sqfs_u64 a, sqfs_u64 b) +{ + if (a != b) + fprintf(stdout, "Location of %s differs\n", name); +} + +static void print_flag_diff(sqfs_u16 a, sqfs_u16 b) +{ + sqfs_u16 diff = a ^ b, mask; + size_t i; + char c; + + if (diff == 0) + return; + + fputs("flags:\n", stdout); + + for (i = 0; i < sizeof(sqfs_flags) / sizeof(sqfs_flags[0]); ++i) { + if (diff & sqfs_flags[i].mask) { + c = a & sqfs_flags[i].mask ? '<' : '>'; + + fprintf(stdout, "\t%c%s\n", c, sqfs_flags[i].name); + } + + a &= ~sqfs_flags[i].mask; + b &= ~sqfs_flags[i].mask; + diff &= ~sqfs_flags[i].mask; + } + + for (i = 0, mask = 0x01; i < 16; ++i, mask <<= 1) { + if (diff & mask) { + fprintf(stdout, "\t%c additional unknown\n", + a & mask ? '<' : '>'); + } + } +} + +int compare_super_blocks(const sqfs_super_t *a, const sqfs_super_t *b) +{ + if (memcmp(a, b, sizeof(*a)) == 0) + return 0; + + fputs("======== super blocks are different ========\n", stdout); + + /* TODO: if a new magic number or squashfs version is introduced, + compare them. */ + + print_value_difference("inode count", a->inode_count, b->inode_count); + print_value_difference("modification time", a->modification_time, + b->modification_time); + print_value_difference("block size", a->block_size, b->block_size); + print_value_difference("block log", a->block_log, b->block_log); + print_value_difference("fragment table entries", + a->fragment_entry_count, + b->fragment_entry_count); + print_value_difference("ID table entries", a->id_count, b->id_count); + + if (a->compression_id != b->compression_id) { + fprintf(stdout, "compressor: %s vs %s\n", + sqfs_compressor_name_from_id(a->compression_id), + sqfs_compressor_name_from_id(b->compression_id)); + } + + print_flag_diff(a->flags, b->flags); + + print_value_difference("total bytes used", a->bytes_used, + b->bytes_used); + + print_offset_diff("root inode", a->root_inode_ref, b->root_inode_ref); + print_offset_diff("ID table", a->id_table_start, b->id_table_start); + print_offset_diff("xattr ID table", a->xattr_id_table_start, + b->xattr_id_table_start); + print_offset_diff("inode table", a->inode_table_start, + b->inode_table_start); + print_offset_diff("directory table", a->directory_table_start, + b->directory_table_start); + print_offset_diff("fragment table", a->fragment_table_start, + b->fragment_table_start); + print_offset_diff("export table", a->export_table_start, + b->export_table_start); + return 1; +} diff --git a/bin/sqfsdiff/src/util.c b/bin/sqfsdiff/src/util.c new file mode 100644 index 0000000..a11770f --- /dev/null +++ b/bin/sqfsdiff/src/util.c @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * util.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "sqfsdiff.h" + +char *node_path(const sqfs_tree_node_t *n) +{ + char *path; + int ret; + + ret = sqfs_tree_node_get_path(n, &path); + if (ret != 0) { + sqfs_perror(NULL, "get path", ret); + return NULL; + } + + if (canonicalize_name(path)) { + fprintf(stderr, "failed to canonicalization '%s'\n", path); + sqfs_free(path); + return NULL; + } + + return path; +} diff --git a/bin/sqfsdiff/super.c b/bin/sqfsdiff/super.c deleted file mode 100644 index 0cf18e0..0000000 --- a/bin/sqfsdiff/super.c +++ /dev/null @@ -1,125 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * super.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfsdiff.h" - -static const struct { - sqfs_u16 mask; - const char *name; -} sqfs_flags[] = { - { SQFS_FLAG_UNCOMPRESSED_INODES, "uncompressed inodes" }, - { SQFS_FLAG_UNCOMPRESSED_DATA, "uncompressed data" }, - { SQFS_FLAG_UNCOMPRESSED_FRAGMENTS, "uncompressed fragments" }, - { SQFS_FLAG_NO_FRAGMENTS, "no fragments" }, - { SQFS_FLAG_ALWAYS_FRAGMENTS, "always fragments" }, - { SQFS_FLAG_NO_DUPLICATES, "no duplicates" }, - { SQFS_FLAG_EXPORTABLE, "exportable" }, - { SQFS_FLAG_UNCOMPRESSED_XATTRS, "uncompressed xattrs" }, - { SQFS_FLAG_NO_XATTRS, "no xattrs" }, - { SQFS_FLAG_COMPRESSOR_OPTIONS, "compressor options" }, - { SQFS_FLAG_UNCOMPRESSED_IDS, "uncompressed ids" }, -}; - -static void print_value_difference(const char *name, sqfs_u64 a, sqfs_u64 b) -{ - sqfs_u64 diff; - char c; - - if (a != b) { - if (a < b) { - c = '+'; - diff = b - a; - } else { - c = '-'; - diff = a - b; - } - fprintf(stdout, "%s: %c%lu\n", name, c, - (unsigned long)diff); - } -} - -static void print_offset_diff(const char *name, sqfs_u64 a, sqfs_u64 b) -{ - if (a != b) - fprintf(stdout, "Location of %s differs\n", name); -} - -static void print_flag_diff(sqfs_u16 a, sqfs_u16 b) -{ - sqfs_u16 diff = a ^ b, mask; - size_t i; - char c; - - if (diff == 0) - return; - - fputs("flags:\n", stdout); - - for (i = 0; i < sizeof(sqfs_flags) / sizeof(sqfs_flags[0]); ++i) { - if (diff & sqfs_flags[i].mask) { - c = a & sqfs_flags[i].mask ? '<' : '>'; - - fprintf(stdout, "\t%c%s\n", c, sqfs_flags[i].name); - } - - a &= ~sqfs_flags[i].mask; - b &= ~sqfs_flags[i].mask; - diff &= ~sqfs_flags[i].mask; - } - - for (i = 0, mask = 0x01; i < 16; ++i, mask <<= 1) { - if (diff & mask) { - fprintf(stdout, "\t%c additional unknown\n", - a & mask ? '<' : '>'); - } - } -} - -int compare_super_blocks(const sqfs_super_t *a, const sqfs_super_t *b) -{ - if (memcmp(a, b, sizeof(*a)) == 0) - return 0; - - fputs("======== super blocks are different ========\n", stdout); - - /* TODO: if a new magic number or squashfs version is introduced, - compare them. */ - - print_value_difference("inode count", a->inode_count, b->inode_count); - print_value_difference("modification time", a->modification_time, - b->modification_time); - print_value_difference("block size", a->block_size, b->block_size); - print_value_difference("block log", a->block_log, b->block_log); - print_value_difference("fragment table entries", - a->fragment_entry_count, - b->fragment_entry_count); - print_value_difference("ID table entries", a->id_count, b->id_count); - - if (a->compression_id != b->compression_id) { - fprintf(stdout, "compressor: %s vs %s\n", - sqfs_compressor_name_from_id(a->compression_id), - sqfs_compressor_name_from_id(b->compression_id)); - } - - print_flag_diff(a->flags, b->flags); - - print_value_difference("total bytes used", a->bytes_used, - b->bytes_used); - - print_offset_diff("root inode", a->root_inode_ref, b->root_inode_ref); - print_offset_diff("ID table", a->id_table_start, b->id_table_start); - print_offset_diff("xattr ID table", a->xattr_id_table_start, - b->xattr_id_table_start); - print_offset_diff("inode table", a->inode_table_start, - b->inode_table_start); - print_offset_diff("directory table", a->directory_table_start, - b->directory_table_start); - print_offset_diff("fragment table", a->fragment_table_start, - b->fragment_table_start); - print_offset_diff("export table", a->export_table_start, - b->export_table_start); - return 1; -} diff --git a/bin/sqfsdiff/util.c b/bin/sqfsdiff/util.c deleted file mode 100644 index a11770f..0000000 --- a/bin/sqfsdiff/util.c +++ /dev/null @@ -1,27 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * util.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "sqfsdiff.h" - -char *node_path(const sqfs_tree_node_t *n) -{ - char *path; - int ret; - - ret = sqfs_tree_node_get_path(n, &path); - if (ret != 0) { - sqfs_perror(NULL, "get path", ret); - return NULL; - } - - if (canonicalize_name(path)) { - fprintf(stderr, "failed to canonicalization '%s'\n", path); - sqfs_free(path); - return NULL; - } - - return path; -} diff --git a/bin/tar2sqfs/Makemodule.am b/bin/tar2sqfs/Makemodule.am index faa2948..c8f52ea 100644 --- a/bin/tar2sqfs/Makemodule.am +++ b/bin/tar2sqfs/Makemodule.am @@ -1,5 +1,5 @@ -tar2sqfs_SOURCES = bin/tar2sqfs/tar2sqfs.c bin/tar2sqfs/tar2sqfs.h -tar2sqfs_SOURCES += bin/tar2sqfs/options.c bin/tar2sqfs/process_tarball.c +tar2sqfs_SOURCES = bin/tar2sqfs/src/tar2sqfs.c bin/tar2sqfs/src/tar2sqfs.h \ + bin/tar2sqfs/src/options.c bin/tar2sqfs/src/process_tarball.c tar2sqfs_CFLAGS = $(AM_CFLAGS) $(PTHREAD_CFLAGS) tar2sqfs_LDADD = libcommon.a libsquashfs.la libtar.a libio.a libxfrm.a tar2sqfs_LDADD += libfstree.a libcompat.a libfstree.a libutil.a $(LZO_LIBS) diff --git a/bin/tar2sqfs/options.c b/bin/tar2sqfs/options.c deleted file mode 100644 index f2185a6..0000000 --- a/bin/tar2sqfs/options.c +++ /dev/null @@ -1,257 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * options.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "tar2sqfs.h" - -static struct option long_opts[] = { - { "root-becomes", required_argument, NULL, 'r' }, - { "compressor", required_argument, NULL, 'c' }, - { "block-size", required_argument, NULL, 'b' }, - { "dev-block-size", required_argument, NULL, 'B' }, - { "defaults", required_argument, NULL, 'd' }, - { "num-jobs", required_argument, NULL, 'j' }, - { "queue-backlog", required_argument, NULL, 'Q' }, - { "comp-extra", required_argument, NULL, 'X' }, - { "no-skip", no_argument, NULL, 's' }, - { "no-xattr", no_argument, NULL, 'x' }, - { "no-keep-time", no_argument, NULL, 'k' }, - { "exportable", no_argument, NULL, 'e' }, - { "no-symlink-retarget", no_argument, NULL, 'S' }, - { "no-tail-packing", no_argument, NULL, 'T' }, - { "force", no_argument, NULL, 'f' }, - { "quiet", no_argument, NULL, 'q' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { NULL, 0, NULL, 0 }, -}; - -static const char *short_opts = "r:c:b:B:d:X:j:Q:sxekfqSThV"; - -static const char *usagestr = -"Usage: tar2sqfs [OPTIONS...] <sqfsfile>\n" -"\n" -"Read a tar archive from stdin and turn it into a squashfs filesystem image.\n" -"\n" -"Possible options:\n" -"\n" -" --root-becomes, -r <dir> The specified directory becomes the root.\n" -" Only its children are packed into the image\n" -" and its attributes (ownership, permissions,\n" -" xattrs, ...) are stored in the root inode.\n" -" If not set and a tarbal has an entry for './'\n" -" or '/', it becomes the root instead.\n" -" --no-symlink-retarget, -S If --root-becomes is used, link targets are\n" -" adjusted if they are prefixed by the root\n" -" path. If this flag is set, symlinks are left\n" -" untouched and only hard links are changed.\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" -" --no-skip, -s Abort if a tar record cannot be read instead\n" -" of skipping it.\n" -" --no-xattr, -x Do not copy extended attributes from archive.\n" -" --no-keep-time, -k Do not keep the time stamps stored in the\n" -" archive. Instead, set defaults on all files.\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"; - -bool dont_skip = false; -bool keep_time = true; -bool no_tail_pack = false; -bool no_symlink_retarget = false; -sqfs_writer_cfg_t cfg; -char *root_becomes = NULL; - -static void input_compressor_print_available(void) -{ - int i = XFRM_COMPRESSOR_MIN; - const char *name; - - fputs("\nSupported tar compression formats:\n", stdout); - - while (i <= XFRM_COMPRESSOR_MAX) { - name = xfrm_compressor_name_from_id(i); - - if (name != NULL) - printf("\t%s\n", name); - - ++i; - } - - fputs("\tuncompressed\n", stdout); - fputc('\n', stdout); -} - -void process_args(int argc, char **argv) -{ - bool have_compressor; - int i, ret; - - sqfs_writer_cfg_init(&cfg); - - for (;;) { - i = getopt_long(argc, argv, short_opts, long_opts, NULL); - if (i == -1) - break; - - switch (i) { - case 'S': - no_symlink_retarget = true; - break; - case 'T': - no_tail_pack = true; - break; - case 'b': - if (parse_size("Block size", &cfg.block_size, - optarg, 0)) { - exit(EXIT_FAILURE); - } - break; - case 'B': - if (parse_size("Device block size", &cfg.devblksize, - optarg, 0)) { - exit(EXIT_FAILURE); - } - if (cfg.devblksize < 1024) { - fputs("Device block size must be at " - "least 1024\n", stderr); - exit(EXIT_FAILURE); - } - break; - case 'c': - have_compressor = true; - ret = sqfs_compressor_id_from_name(optarg); - - if (ret < 0) { - have_compressor = false; -#ifdef WITH_LZO - if (cfg.comp_id == SQFS_COMP_LZO) - have_compressor = true; -#endif - } - - if (!have_compressor) { - fprintf(stderr, "Unsupported compressor '%s'\n", - optarg); - exit(EXIT_FAILURE); - } - - cfg.comp_id = ret; - break; - case 'j': - cfg.num_jobs = strtol(optarg, NULL, 0); - break; - case 'Q': - cfg.max_backlog = strtol(optarg, NULL, 0); - break; - case 'X': - cfg.comp_extra = optarg; - break; - case 'd': - cfg.fs_defaults = optarg; - break; - case 'x': - cfg.no_xattr = true; - break; - case 'k': - keep_time = false; - break; - case 'r': - free(root_becomes); - root_becomes = strdup(optarg); - if (root_becomes == NULL) { - perror("copying root directory name"); - exit(EXIT_FAILURE); - } - - if (canonicalize_name(root_becomes) != 0 || - strlen(root_becomes) == 0) { - fprintf(stderr, - "Invalid root directory '%s'.\n", - optarg); - goto fail_arg; - } - break; - case 's': - dont_skip = true; - break; - case 'e': - cfg.exportable = true; - break; - case 'f': - cfg.outmode |= SQFS_FILE_OPEN_OVERWRITE; - break; - case 'q': - cfg.quiet = true; - break; - case 'h': - printf(usagestr, SQFS_DEFAULT_BLOCK_SIZE, - SQFS_DEVBLK_SIZE); - compressor_print_available(); - input_compressor_print_available(); - exit(EXIT_SUCCESS); - case 'V': - print_version("tar2sqfs"); - exit(EXIT_SUCCESS); - default: - goto fail_arg; - } - } - - if (cfg.num_jobs < 1) - cfg.num_jobs = 1; - - if (cfg.max_backlog < 1) - cfg.max_backlog = 10 * cfg.num_jobs; - - if (cfg.comp_extra != NULL && strcmp(cfg.comp_extra, "help") == 0) { - compressor_print_help(cfg.comp_id); - exit(EXIT_SUCCESS); - } - - if (optind >= argc) { - fputs("Missing argument: squashfs image\n", stderr); - goto fail_arg; - } - - cfg.filename = argv[optind++]; - - if (optind < argc) { - fputs("Unknown extra arguments specified.\n", stderr); - goto fail_arg; - } - return; -fail_arg: - fputs("Try `tar2sqfs --help' for more information.\n", stderr); - exit(EXIT_FAILURE); -} diff --git a/bin/tar2sqfs/process_tarball.c b/bin/tar2sqfs/process_tarball.c deleted file mode 100644 index 6aaa24b..0000000 --- a/bin/tar2sqfs/process_tarball.c +++ /dev/null @@ -1,346 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * process_tarball.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "tar2sqfs.h" - -static int write_file(istream_t *input_file, sqfs_writer_t *sqfs, - const tar_header_decoded_t *hdr, - file_info_t *fi, sqfs_u64 filesize) -{ - const sparse_map_t *list; - int flags = 0, ret = 0; - sqfs_u64 offset, diff; - bool sparse_region; - ostream_t *out; - - if (no_tail_pack && filesize > cfg.block_size) - flags |= SQFS_BLK_DONT_FRAGMENT; - - out = data_writer_ostream_create(hdr->name, sqfs->data, &fi->inode, - flags); - - if (out == NULL) - return -1; - - list = hdr->sparse; - - for (offset = 0; offset < filesize; offset += diff) { - if (hdr->sparse != NULL) { - if (list == NULL) { - sparse_region = true; - diff = filesize - offset; - } else if (offset < list->offset) { - sparse_region = true; - diff = list->offset - offset; - } else if (offset - list->offset >= list->count) { - list = list->next; - diff = 0; - continue; - } else { - sparse_region = false; - diff = list->count - (offset - list->offset); - } - } else { - sparse_region = false; - diff = filesize - offset; - } - - if (diff > 0x7FFFFFFFUL) - diff = 0x7FFFFFFFUL; - - if (sparse_region) { - ret = ostream_append_sparse(out, diff); - } else { - ret = ostream_append_from_istream(out, input_file, - diff); - - if (ret == 0) { - fprintf(stderr, "%s: unexpected end-of-file\n", - hdr->name); - ret = -1; - } else if (ret > 0) { - diff = ret; - ret = 0; - } - } - - if (ret < 0) - break; - } - - ostream_flush(out); - sqfs_drop(out); - - if (ret) - return -1; - - return skip_padding(input_file, hdr->sparse == NULL ? - filesize : hdr->record_size); -} - -static int copy_xattr(sqfs_writer_t *sqfs, tree_node_t *node, - const tar_header_decoded_t *hdr) -{ - tar_xattr_t *xattr; - int ret; - - ret = sqfs_xattr_writer_begin(sqfs->xwr, 0); - if (ret) { - sqfs_perror(hdr->name, "beginning xattr block", ret); - return -1; - } - - for (xattr = hdr->xattr; xattr != NULL; xattr = xattr->next) { - if (sqfs_get_xattr_prefix_id(xattr->key) < 0) { - fprintf(stderr, "%s: squashfs does not " - "support xattr prefix of %s\n", - dont_skip ? "ERROR" : "WARNING", - xattr->key); - - if (dont_skip) - return -1; - continue; - } - - ret = sqfs_xattr_writer_add(sqfs->xwr, xattr->key, xattr->value, - xattr->value_len); - if (ret) { - sqfs_perror(hdr->name, "storing xattr key-value pair", - ret); - return -1; - } - } - - ret = sqfs_xattr_writer_end(sqfs->xwr, &node->xattr_idx); - if (ret) { - sqfs_perror(hdr->name, "completing xattr block", ret); - return -1; - } - - return 0; -} - -static int create_node_and_repack_data(istream_t *input_file, - sqfs_writer_t *sqfs, - tar_header_decoded_t *hdr) -{ - tree_node_t *node; - struct stat sb; - - if (hdr->is_hard_link) { - node = fstree_add_hard_link(&sqfs->fs, hdr->name, - hdr->link_target); - if (node == NULL) - goto fail_errno; - - if (!cfg.quiet) { - printf("Hard link %s -> %s\n", hdr->name, - hdr->link_target); - } - return 0; - } - - if (!keep_time) { - hdr->mtime = sqfs->fs.defaults.st_mtime; - } - - memset(&sb, 0, sizeof(sb)); - sb.st_mode = hdr->mode; - sb.st_uid = hdr->uid; - sb.st_gid = hdr->gid; - sb.st_rdev = hdr->devno; - sb.st_size = hdr->actual_size; - sb.st_mtime = hdr->mtime; - - node = fstree_add_generic(&sqfs->fs, hdr->name, - &sb, hdr->link_target); - if (node == NULL) - goto fail_errno; - - if (!cfg.quiet) - printf("Packing %s\n", hdr->name); - - if (!cfg.no_xattr) { - if (copy_xattr(sqfs, node, hdr)) - return -1; - } - - if (S_ISREG(hdr->mode)) { - if (write_file(input_file, sqfs, hdr, &node->data.file, - hdr->actual_size)) { - return -1; - } - } - - return 0; -fail_errno: - perror(hdr->name); - return -1; -} - -static int set_root_attribs(sqfs_writer_t *sqfs, - const tar_header_decoded_t *hdr) -{ - if (hdr->is_hard_link || !S_ISDIR(hdr->mode)) { - fprintf(stderr, "'%s' is not a directory!\n", hdr->name); - return -1; - } - - sqfs->fs.root->uid = hdr->uid; - sqfs->fs.root->gid = hdr->gid; - sqfs->fs.root->mode = hdr->mode; - - if (keep_time) - sqfs->fs.root->mod_time = hdr->mtime; - - if (!cfg.no_xattr) { - if (copy_xattr(sqfs, sqfs->fs.root, hdr)) - return -1; - } - - return 0; -} - -int process_tarball(istream_t *input_file, sqfs_writer_t *sqfs) -{ - bool skip, is_root, is_prefixed; - tar_header_decoded_t hdr; - sqfs_u64 offset, count; - sparse_map_t *m; - size_t rootlen; - char *target; - int ret; - - rootlen = root_becomes == NULL ? 0 : strlen(root_becomes); - - for (;;) { - ret = read_header(input_file, &hdr); - if (ret > 0) - break; - if (ret < 0) - return -1; - - if (hdr.mtime < 0) - hdr.mtime = 0; - - if ((sqfs_u64)hdr.mtime > 0x0FFFFFFFFUL) - hdr.mtime = 0x0FFFFFFFFUL; - - skip = false; - is_root = false; - is_prefixed = true; - - if (hdr.name == NULL || canonicalize_name(hdr.name) != 0) { - fprintf(stderr, "skipping '%s' (invalid name)\n", - hdr.name); - skip = true; - } else if (root_becomes != NULL) { - if (strncmp(hdr.name, root_becomes, rootlen) == 0) { - if (hdr.name[rootlen] == '\0') { - is_root = true; - } else if (hdr.name[rootlen] != '/') { - is_prefixed = false; - } - } else { - is_prefixed = false; - } - - if (is_prefixed && !is_root) { - memmove(hdr.name, hdr.name + rootlen + 1, - strlen(hdr.name + rootlen + 1) + 1); - } - - if (is_prefixed && hdr.name[0] == '\0') { - fputs("skipping entry with empty name\n", - stderr); - skip = true; - } - - if (hdr.link_target != NULL && - (hdr.is_hard_link || !no_symlink_retarget)) { - target = strdup(hdr.link_target); - if (target == NULL) { - fprintf(stderr, "packing '%s': %s\n", - hdr.name, strerror(errno)); - goto fail; - } - - if (canonicalize_name(target) == 0 && - !strncmp(target, root_becomes, rootlen) && - target[rootlen] == '/') { - memmove(hdr.link_target, - target + rootlen, - strlen(target + rootlen) + 1); - } - - free(target); - } - } else if (hdr.name[0] == '\0') { - is_root = true; - } - - if (!is_prefixed) { - if (skip_entry(input_file, hdr.record_size)) - goto fail; - clear_header(&hdr); - continue; - } - - if (is_root) { - if (set_root_attribs(sqfs, &hdr)) - goto fail; - clear_header(&hdr); - continue; - } - - if (!skip && hdr.unknown_record) { - fprintf(stderr, "%s: unknown entry type\n", hdr.name); - skip = true; - } - - if (!skip && hdr.sparse != NULL) { - offset = hdr.sparse->offset; - count = 0; - - for (m = hdr.sparse; m != NULL; m = m->next) { - if (m->offset < offset) { - skip = true; - break; - } - offset = m->offset + m->count; - count += m->count; - } - - if (count != hdr.record_size) - skip = true; - - if (skip) { - fprintf(stderr, "%s: broken sparse " - "file layout\n", hdr.name); - } - } - - if (skip) { - if (dont_skip) - goto fail; - if (skip_entry(input_file, hdr.record_size)) - goto fail; - - clear_header(&hdr); - continue; - } - - if (create_node_and_repack_data(input_file, sqfs, &hdr)) - goto fail; - - clear_header(&hdr); - } - - return 0; -fail: - clear_header(&hdr); - return -1; -} diff --git a/bin/tar2sqfs/src/options.c b/bin/tar2sqfs/src/options.c new file mode 100644 index 0000000..f2185a6 --- /dev/null +++ b/bin/tar2sqfs/src/options.c @@ -0,0 +1,257 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * options.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "tar2sqfs.h" + +static struct option long_opts[] = { + { "root-becomes", required_argument, NULL, 'r' }, + { "compressor", required_argument, NULL, 'c' }, + { "block-size", required_argument, NULL, 'b' }, + { "dev-block-size", required_argument, NULL, 'B' }, + { "defaults", required_argument, NULL, 'd' }, + { "num-jobs", required_argument, NULL, 'j' }, + { "queue-backlog", required_argument, NULL, 'Q' }, + { "comp-extra", required_argument, NULL, 'X' }, + { "no-skip", no_argument, NULL, 's' }, + { "no-xattr", no_argument, NULL, 'x' }, + { "no-keep-time", no_argument, NULL, 'k' }, + { "exportable", no_argument, NULL, 'e' }, + { "no-symlink-retarget", no_argument, NULL, 'S' }, + { "no-tail-packing", no_argument, NULL, 'T' }, + { "force", no_argument, NULL, 'f' }, + { "quiet", no_argument, NULL, 'q' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *short_opts = "r:c:b:B:d:X:j:Q:sxekfqSThV"; + +static const char *usagestr = +"Usage: tar2sqfs [OPTIONS...] <sqfsfile>\n" +"\n" +"Read a tar archive from stdin and turn it into a squashfs filesystem image.\n" +"\n" +"Possible options:\n" +"\n" +" --root-becomes, -r <dir> The specified directory becomes the root.\n" +" Only its children are packed into the image\n" +" and its attributes (ownership, permissions,\n" +" xattrs, ...) are stored in the root inode.\n" +" If not set and a tarbal has an entry for './'\n" +" or '/', it becomes the root instead.\n" +" --no-symlink-retarget, -S If --root-becomes is used, link targets are\n" +" adjusted if they are prefixed by the root\n" +" path. If this flag is set, symlinks are left\n" +" untouched and only hard links are changed.\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" +" --no-skip, -s Abort if a tar record cannot be read instead\n" +" of skipping it.\n" +" --no-xattr, -x Do not copy extended attributes from archive.\n" +" --no-keep-time, -k Do not keep the time stamps stored in the\n" +" archive. Instead, set defaults on all files.\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"; + +bool dont_skip = false; +bool keep_time = true; +bool no_tail_pack = false; +bool no_symlink_retarget = false; +sqfs_writer_cfg_t cfg; +char *root_becomes = NULL; + +static void input_compressor_print_available(void) +{ + int i = XFRM_COMPRESSOR_MIN; + const char *name; + + fputs("\nSupported tar compression formats:\n", stdout); + + while (i <= XFRM_COMPRESSOR_MAX) { + name = xfrm_compressor_name_from_id(i); + + if (name != NULL) + printf("\t%s\n", name); + + ++i; + } + + fputs("\tuncompressed\n", stdout); + fputc('\n', stdout); +} + +void process_args(int argc, char **argv) +{ + bool have_compressor; + int i, ret; + + sqfs_writer_cfg_init(&cfg); + + for (;;) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 'S': + no_symlink_retarget = true; + break; + case 'T': + no_tail_pack = true; + break; + case 'b': + if (parse_size("Block size", &cfg.block_size, + optarg, 0)) { + exit(EXIT_FAILURE); + } + break; + case 'B': + if (parse_size("Device block size", &cfg.devblksize, + optarg, 0)) { + exit(EXIT_FAILURE); + } + if (cfg.devblksize < 1024) { + fputs("Device block size must be at " + "least 1024\n", stderr); + exit(EXIT_FAILURE); + } + break; + case 'c': + have_compressor = true; + ret = sqfs_compressor_id_from_name(optarg); + + if (ret < 0) { + have_compressor = false; +#ifdef WITH_LZO + if (cfg.comp_id == SQFS_COMP_LZO) + have_compressor = true; +#endif + } + + if (!have_compressor) { + fprintf(stderr, "Unsupported compressor '%s'\n", + optarg); + exit(EXIT_FAILURE); + } + + cfg.comp_id = ret; + break; + case 'j': + cfg.num_jobs = strtol(optarg, NULL, 0); + break; + case 'Q': + cfg.max_backlog = strtol(optarg, NULL, 0); + break; + case 'X': + cfg.comp_extra = optarg; + break; + case 'd': + cfg.fs_defaults = optarg; + break; + case 'x': + cfg.no_xattr = true; + break; + case 'k': + keep_time = false; + break; + case 'r': + free(root_becomes); + root_becomes = strdup(optarg); + if (root_becomes == NULL) { + perror("copying root directory name"); + exit(EXIT_FAILURE); + } + + if (canonicalize_name(root_becomes) != 0 || + strlen(root_becomes) == 0) { + fprintf(stderr, + "Invalid root directory '%s'.\n", + optarg); + goto fail_arg; + } + break; + case 's': + dont_skip = true; + break; + case 'e': + cfg.exportable = true; + break; + case 'f': + cfg.outmode |= SQFS_FILE_OPEN_OVERWRITE; + break; + case 'q': + cfg.quiet = true; + break; + case 'h': + printf(usagestr, SQFS_DEFAULT_BLOCK_SIZE, + SQFS_DEVBLK_SIZE); + compressor_print_available(); + input_compressor_print_available(); + exit(EXIT_SUCCESS); + case 'V': + print_version("tar2sqfs"); + exit(EXIT_SUCCESS); + default: + goto fail_arg; + } + } + + if (cfg.num_jobs < 1) + cfg.num_jobs = 1; + + if (cfg.max_backlog < 1) + cfg.max_backlog = 10 * cfg.num_jobs; + + if (cfg.comp_extra != NULL && strcmp(cfg.comp_extra, "help") == 0) { + compressor_print_help(cfg.comp_id); + exit(EXIT_SUCCESS); + } + + if (optind >= argc) { + fputs("Missing argument: squashfs image\n", stderr); + goto fail_arg; + } + + cfg.filename = argv[optind++]; + + if (optind < argc) { + fputs("Unknown extra arguments specified.\n", stderr); + goto fail_arg; + } + return; +fail_arg: + fputs("Try `tar2sqfs --help' for more information.\n", stderr); + exit(EXIT_FAILURE); +} diff --git a/bin/tar2sqfs/src/process_tarball.c b/bin/tar2sqfs/src/process_tarball.c new file mode 100644 index 0000000..6aaa24b --- /dev/null +++ b/bin/tar2sqfs/src/process_tarball.c @@ -0,0 +1,346 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * process_tarball.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "tar2sqfs.h" + +static int write_file(istream_t *input_file, sqfs_writer_t *sqfs, + const tar_header_decoded_t *hdr, + file_info_t *fi, sqfs_u64 filesize) +{ + const sparse_map_t *list; + int flags = 0, ret = 0; + sqfs_u64 offset, diff; + bool sparse_region; + ostream_t *out; + + if (no_tail_pack && filesize > cfg.block_size) + flags |= SQFS_BLK_DONT_FRAGMENT; + + out = data_writer_ostream_create(hdr->name, sqfs->data, &fi->inode, + flags); + + if (out == NULL) + return -1; + + list = hdr->sparse; + + for (offset = 0; offset < filesize; offset += diff) { + if (hdr->sparse != NULL) { + if (list == NULL) { + sparse_region = true; + diff = filesize - offset; + } else if (offset < list->offset) { + sparse_region = true; + diff = list->offset - offset; + } else if (offset - list->offset >= list->count) { + list = list->next; + diff = 0; + continue; + } else { + sparse_region = false; + diff = list->count - (offset - list->offset); + } + } else { + sparse_region = false; + diff = filesize - offset; + } + + if (diff > 0x7FFFFFFFUL) + diff = 0x7FFFFFFFUL; + + if (sparse_region) { + ret = ostream_append_sparse(out, diff); + } else { + ret = ostream_append_from_istream(out, input_file, + diff); + + if (ret == 0) { + fprintf(stderr, "%s: unexpected end-of-file\n", + hdr->name); + ret = -1; + } else if (ret > 0) { + diff = ret; + ret = 0; + } + } + + if (ret < 0) + break; + } + + ostream_flush(out); + sqfs_drop(out); + + if (ret) + return -1; + + return skip_padding(input_file, hdr->sparse == NULL ? + filesize : hdr->record_size); +} + +static int copy_xattr(sqfs_writer_t *sqfs, tree_node_t *node, + const tar_header_decoded_t *hdr) +{ + tar_xattr_t *xattr; + int ret; + + ret = sqfs_xattr_writer_begin(sqfs->xwr, 0); + if (ret) { + sqfs_perror(hdr->name, "beginning xattr block", ret); + return -1; + } + + for (xattr = hdr->xattr; xattr != NULL; xattr = xattr->next) { + if (sqfs_get_xattr_prefix_id(xattr->key) < 0) { + fprintf(stderr, "%s: squashfs does not " + "support xattr prefix of %s\n", + dont_skip ? "ERROR" : "WARNING", + xattr->key); + + if (dont_skip) + return -1; + continue; + } + + ret = sqfs_xattr_writer_add(sqfs->xwr, xattr->key, xattr->value, + xattr->value_len); + if (ret) { + sqfs_perror(hdr->name, "storing xattr key-value pair", + ret); + return -1; + } + } + + ret = sqfs_xattr_writer_end(sqfs->xwr, &node->xattr_idx); + if (ret) { + sqfs_perror(hdr->name, "completing xattr block", ret); + return -1; + } + + return 0; +} + +static int create_node_and_repack_data(istream_t *input_file, + sqfs_writer_t *sqfs, + tar_header_decoded_t *hdr) +{ + tree_node_t *node; + struct stat sb; + + if (hdr->is_hard_link) { + node = fstree_add_hard_link(&sqfs->fs, hdr->name, + hdr->link_target); + if (node == NULL) + goto fail_errno; + + if (!cfg.quiet) { + printf("Hard link %s -> %s\n", hdr->name, + hdr->link_target); + } + return 0; + } + + if (!keep_time) { + hdr->mtime = sqfs->fs.defaults.st_mtime; + } + + memset(&sb, 0, sizeof(sb)); + sb.st_mode = hdr->mode; + sb.st_uid = hdr->uid; + sb.st_gid = hdr->gid; + sb.st_rdev = hdr->devno; + sb.st_size = hdr->actual_size; + sb.st_mtime = hdr->mtime; + + node = fstree_add_generic(&sqfs->fs, hdr->name, + &sb, hdr->link_target); + if (node == NULL) + goto fail_errno; + + if (!cfg.quiet) + printf("Packing %s\n", hdr->name); + + if (!cfg.no_xattr) { + if (copy_xattr(sqfs, node, hdr)) + return -1; + } + + if (S_ISREG(hdr->mode)) { + if (write_file(input_file, sqfs, hdr, &node->data.file, + hdr->actual_size)) { + return -1; + } + } + + return 0; +fail_errno: + perror(hdr->name); + return -1; +} + +static int set_root_attribs(sqfs_writer_t *sqfs, + const tar_header_decoded_t *hdr) +{ + if (hdr->is_hard_link || !S_ISDIR(hdr->mode)) { + fprintf(stderr, "'%s' is not a directory!\n", hdr->name); + return -1; + } + + sqfs->fs.root->uid = hdr->uid; + sqfs->fs.root->gid = hdr->gid; + sqfs->fs.root->mode = hdr->mode; + + if (keep_time) + sqfs->fs.root->mod_time = hdr->mtime; + + if (!cfg.no_xattr) { + if (copy_xattr(sqfs, sqfs->fs.root, hdr)) + return -1; + } + + return 0; +} + +int process_tarball(istream_t *input_file, sqfs_writer_t *sqfs) +{ + bool skip, is_root, is_prefixed; + tar_header_decoded_t hdr; + sqfs_u64 offset, count; + sparse_map_t *m; + size_t rootlen; + char *target; + int ret; + + rootlen = root_becomes == NULL ? 0 : strlen(root_becomes); + + for (;;) { + ret = read_header(input_file, &hdr); + if (ret > 0) + break; + if (ret < 0) + return -1; + + if (hdr.mtime < 0) + hdr.mtime = 0; + + if ((sqfs_u64)hdr.mtime > 0x0FFFFFFFFUL) + hdr.mtime = 0x0FFFFFFFFUL; + + skip = false; + is_root = false; + is_prefixed = true; + + if (hdr.name == NULL || canonicalize_name(hdr.name) != 0) { + fprintf(stderr, "skipping '%s' (invalid name)\n", + hdr.name); + skip = true; + } else if (root_becomes != NULL) { + if (strncmp(hdr.name, root_becomes, rootlen) == 0) { + if (hdr.name[rootlen] == '\0') { + is_root = true; + } else if (hdr.name[rootlen] != '/') { + is_prefixed = false; + } + } else { + is_prefixed = false; + } + + if (is_prefixed && !is_root) { + memmove(hdr.name, hdr.name + rootlen + 1, + strlen(hdr.name + rootlen + 1) + 1); + } + + if (is_prefixed && hdr.name[0] == '\0') { + fputs("skipping entry with empty name\n", + stderr); + skip = true; + } + + if (hdr.link_target != NULL && + (hdr.is_hard_link || !no_symlink_retarget)) { + target = strdup(hdr.link_target); + if (target == NULL) { + fprintf(stderr, "packing '%s': %s\n", + hdr.name, strerror(errno)); + goto fail; + } + + if (canonicalize_name(target) == 0 && + !strncmp(target, root_becomes, rootlen) && + target[rootlen] == '/') { + memmove(hdr.link_target, + target + rootlen, + strlen(target + rootlen) + 1); + } + + free(target); + } + } else if (hdr.name[0] == '\0') { + is_root = true; + } + + if (!is_prefixed) { + if (skip_entry(input_file, hdr.record_size)) + goto fail; + clear_header(&hdr); + continue; + } + + if (is_root) { + if (set_root_attribs(sqfs, &hdr)) + goto fail; + clear_header(&hdr); + continue; + } + + if (!skip && hdr.unknown_record) { + fprintf(stderr, "%s: unknown entry type\n", hdr.name); + skip = true; + } + + if (!skip && hdr.sparse != NULL) { + offset = hdr.sparse->offset; + count = 0; + + for (m = hdr.sparse; m != NULL; m = m->next) { + if (m->offset < offset) { + skip = true; + break; + } + offset = m->offset + m->count; + count += m->count; + } + + if (count != hdr.record_size) + skip = true; + + if (skip) { + fprintf(stderr, "%s: broken sparse " + "file layout\n", hdr.name); + } + } + + if (skip) { + if (dont_skip) + goto fail; + if (skip_entry(input_file, hdr.record_size)) + goto fail; + + clear_header(&hdr); + continue; + } + + if (create_node_and_repack_data(input_file, sqfs, &hdr)) + goto fail; + + clear_header(&hdr); + } + + return 0; +fail: + clear_header(&hdr); + return -1; +} diff --git a/bin/tar2sqfs/src/tar2sqfs.c b/bin/tar2sqfs/src/tar2sqfs.c new file mode 100644 index 0000000..9257fed --- /dev/null +++ b/bin/tar2sqfs/src/tar2sqfs.c @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * tar2sqfs.c + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#include "tar2sqfs.h" + +static int tar_probe(const sqfs_u8 *data, size_t size) +{ + size_t i, offset; + + if (size >= TAR_RECORD_SIZE) { + for (i = 0; i < TAR_RECORD_SIZE; ++i) { + if (data[i] != 0x00) + break; + } + + if (i == TAR_RECORD_SIZE) { + data += TAR_RECORD_SIZE; + size -= TAR_RECORD_SIZE; + } + } + + offset = offsetof(tar_header_t, magic); + + if (offset + 5 <= size) { + if (memcmp(data + offset, "ustar", 5) == 0) + return 1; + } + + return 0; +} + +static istream_t *magic_autowrap(istream_t *strm) +{ + xfrm_stream_t *xfrm = NULL; + istream_t *wrapper = NULL; + const sqfs_u8 *data; + size_t avail; + int ret; + + ret = istream_precache(strm); + if (ret != 0) + goto out; + + data = strm->buffer + strm->buffer_offset; + avail = strm->buffer_used - strm->buffer_offset; + + ret = tar_probe(data, avail); + if (ret > 0) + return strm; + + ret = xfrm_compressor_id_from_magic(data, avail); + if (ret <= 0) + return strm; + + xfrm = decompressor_stream_create(ret); + if (xfrm == NULL) + goto out; + + wrapper = istream_xfrm_create(strm, xfrm); +out: + sqfs_drop(strm); + sqfs_drop(xfrm); + return wrapper; +} + +int main(int argc, char **argv) +{ + int status = EXIT_FAILURE; + istream_t *input_file = NULL; + sqfs_writer_t sqfs; + + process_args(argc, argv); + + input_file = istream_open_stdin(); + if (input_file == NULL) + return EXIT_FAILURE; + + input_file = magic_autowrap(input_file); + if (input_file == NULL) + return EXIT_FAILURE; + + memset(&sqfs, 0, sizeof(sqfs)); + if (sqfs_writer_init(&sqfs, &cfg)) + goto out_if; + + if (process_tarball(input_file, &sqfs)) + goto out; + + if (fstree_post_process(&sqfs.fs)) + goto out; + + if (sqfs_writer_finish(&sqfs, &cfg)) + goto out; + + status = EXIT_SUCCESS; +out: + sqfs_writer_cleanup(&sqfs, status); +out_if: + sqfs_drop(input_file); + return status; +} diff --git a/bin/tar2sqfs/src/tar2sqfs.h b/bin/tar2sqfs/src/tar2sqfs.h new file mode 100644 index 0000000..a21774b --- /dev/null +++ b/bin/tar2sqfs/src/tar2sqfs.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * tar2sqfs.h + * + * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> + */ +#ifndef TAR2SQFS_H +#define TAR2SQFS_H + +#include "config.h" +#include "common.h" +#include "compat.h" + +#include "util/util.h" +#include "tar/tar.h" +#include "tar/format.h" +#include "xfrm/compress.h" +#include "io/xfrm.h" + +#include <stdlib.h> +#include <getopt.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> + +/* options.c */ +extern bool dont_skip; +extern bool keep_time; +extern bool no_tail_pack; +extern bool no_symlink_retarget; +extern sqfs_writer_cfg_t cfg; +extern char *root_becomes; + +void process_args(int argc, char **argv); + +/* process_tarball.c */ +int process_tarball(istream_t *input_file, sqfs_writer_t *sqfs); + +#endif /* TAR2SQFS_H */ diff --git a/bin/tar2sqfs/tar2sqfs.c b/bin/tar2sqfs/tar2sqfs.c deleted file mode 100644 index 9257fed..0000000 --- a/bin/tar2sqfs/tar2sqfs.c +++ /dev/null @@ -1,104 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * tar2sqfs.c - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#include "tar2sqfs.h" - -static int tar_probe(const sqfs_u8 *data, size_t size) -{ - size_t i, offset; - - if (size >= TAR_RECORD_SIZE) { - for (i = 0; i < TAR_RECORD_SIZE; ++i) { - if (data[i] != 0x00) - break; - } - - if (i == TAR_RECORD_SIZE) { - data += TAR_RECORD_SIZE; - size -= TAR_RECORD_SIZE; - } - } - - offset = offsetof(tar_header_t, magic); - - if (offset + 5 <= size) { - if (memcmp(data + offset, "ustar", 5) == 0) - return 1; - } - - return 0; -} - -static istream_t *magic_autowrap(istream_t *strm) -{ - xfrm_stream_t *xfrm = NULL; - istream_t *wrapper = NULL; - const sqfs_u8 *data; - size_t avail; - int ret; - - ret = istream_precache(strm); - if (ret != 0) - goto out; - - data = strm->buffer + strm->buffer_offset; - avail = strm->buffer_used - strm->buffer_offset; - - ret = tar_probe(data, avail); - if (ret > 0) - return strm; - - ret = xfrm_compressor_id_from_magic(data, avail); - if (ret <= 0) - return strm; - - xfrm = decompressor_stream_create(ret); - if (xfrm == NULL) - goto out; - - wrapper = istream_xfrm_create(strm, xfrm); -out: - sqfs_drop(strm); - sqfs_drop(xfrm); - return wrapper; -} - -int main(int argc, char **argv) -{ - int status = EXIT_FAILURE; - istream_t *input_file = NULL; - sqfs_writer_t sqfs; - - process_args(argc, argv); - - input_file = istream_open_stdin(); - if (input_file == NULL) - return EXIT_FAILURE; - - input_file = magic_autowrap(input_file); - if (input_file == NULL) - return EXIT_FAILURE; - - memset(&sqfs, 0, sizeof(sqfs)); - if (sqfs_writer_init(&sqfs, &cfg)) - goto out_if; - - if (process_tarball(input_file, &sqfs)) - goto out; - - if (fstree_post_process(&sqfs.fs)) - goto out; - - if (sqfs_writer_finish(&sqfs, &cfg)) - goto out; - - status = EXIT_SUCCESS; -out: - sqfs_writer_cleanup(&sqfs, status); -out_if: - sqfs_drop(input_file); - return status; -} diff --git a/bin/tar2sqfs/tar2sqfs.h b/bin/tar2sqfs/tar2sqfs.h deleted file mode 100644 index a21774b..0000000 --- a/bin/tar2sqfs/tar2sqfs.h +++ /dev/null @@ -1,39 +0,0 @@ -/* SPDX-License-Identifier: GPL-3.0-or-later */ -/* - * tar2sqfs.h - * - * Copyright (C) 2019 David Oberhollenzer <goliath@infraroot.at> - */ -#ifndef TAR2SQFS_H -#define TAR2SQFS_H - -#include "config.h" -#include "common.h" -#include "compat.h" - -#include "util/util.h" -#include "tar/tar.h" -#include "tar/format.h" -#include "xfrm/compress.h" -#include "io/xfrm.h" - -#include <stdlib.h> -#include <getopt.h> -#include <string.h> -#include <stdio.h> -#include <errno.h> - -/* options.c */ -extern bool dont_skip; -extern bool keep_time; -extern bool no_tail_pack; -extern bool no_symlink_retarget; -extern sqfs_writer_cfg_t cfg; -extern char *root_becomes; - -void process_args(int argc, char **argv); - -/* process_tarball.c */ -int process_tarball(istream_t *input_file, sqfs_writer_t *sqfs); - -#endif /* TAR2SQFS_H */ -- cgit v1.2.3