/* SPDX-License-Identifier: GPL-3.0-or-later */ /* * fill_files.c * * Copyright (C) 2019 David Oberhollenzer */ #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 uint32_t get_frag_idx(const sqfs_inode_generic_t *inode) { if (inode->base.type == SQFS_INODE_EXT_FILE) return inode->data.file_ext.fragment_idx; return inode->data.file.fragment_index; } static uint32_t get_frag_off(const sqfs_inode_generic_t *inode) { if (inode->base.type == SQFS_INODE_EXT_FILE) return inode->data.file_ext.fragment_offset; return inode->data.file.fragment_offset; } static uint64_t get_size(const sqfs_inode_generic_t *inode) { if (inode->base.type == SQFS_INODE_EXT_FILE) return inode->data.file_ext.file_size; return inode->data.file.file_size; } static uint64_t get_start(const sqfs_inode_generic_t *inode) { if (inode->base.type == SQFS_INODE_EXT_FILE) return inode->data.file_ext.blocks_start; return inode->data.file.blocks_start; } static bool has_fragment(const struct file_ent *ent) { if (get_size(ent->inode) % block_size == 0) return false; return get_frag_off(ent->inode) < block_size && (get_frag_idx(ent->inode) != 0xFFFFFFFF); } static int compare_files(const void *l, const void *r) { const struct file_ent *lhs = l, *rhs = r; /* 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 (has_fragment(lhs)) { if (!(has_fragment(rhs))) return -1; if (get_frag_idx(lhs->inode) < get_frag_idx(rhs->inode)) return -1; if (get_frag_idx(lhs->inode) > get_frag_idx(rhs->inode)) return 1; if (get_size(lhs->inode) < block_size) return (get_size(rhs->inode) < block_size) ? 0 : -1; if (get_size(rhs->inode) < block_size) return 1; goto order_by_start; } if (has_fragment(rhs)) return 1; /* order the rest by start block */ order_by_start: return get_start(lhs->inode) < get_start(rhs->inode) ? -1 : get_start(lhs->inode) > get_start(rhs->inode) ? 1 : 0; } static int add_file(const sqfs_tree_node_t *node) { size_t new_sz; char *path; void *new; 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; } path = sqfs_tree_node_get_path(node); if (path == NULL) { perror("assembling file path"); return -1; } if (canonicalize_name(path)) { fprintf(stderr, "Invalid file path '%s'\n", path); 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) 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 (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) { size_t i; int fd; for (i = 0; i < num_files; ++i) { fd = open(files[i].path, O_WRONLY); if (fd < 0) { fprintf(stderr, "unpacking %s: %s\n", files[i].path, strerror(errno)); return -1; } if (!(flags & UNPACK_QUIET)) printf("unpacking %s\n", files[i].path); if (sqfs_data_reader_dump(data, files[i].inode, fd, block_size, (flags & UNPACK_NO_SPARSE) == 0)) { close(fd); return -1; } close(fd); } 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; }