[PATCH v3 0/5] Add support for the SquashFS filesystem

Hello!
This series adds support for the SquashFS filesystem. For now, zlib is the only supported compression type. This is my first contribution to U-Boot as well as to a major Open Source project.
Changes in v3: - Replace CONFIG_IS_ENABLED by IS_ENABLED in fs/fs.c Changes in v2: - Replace sqfs_ls() by U-Boot's fs_ls_generic() - Add info. to MAINTAINERS - Fix build failures - Fix style problems
Best regards, Joao Marcos Costa
Joao Marcos Costa (5): fs/squashfs: new filesystem fs/squashfs: add filesystem commands include/u-boot, lib/zlib: add sources for zlib decompression fs/squashfs: add support for zlib decompression fs/fs.c: add symbolic link case to fs_ls_generic()
MAINTAINERS | 7 + cmd/Kconfig | 6 + cmd/Makefile | 1 + cmd/sqfs.c | 42 + common/spl/Kconfig | 9 + fs/Kconfig | 2 + fs/Makefile | 2 + fs/fs.c | 18 + fs/squashfs/Kconfig | 10 + fs/squashfs/Makefile | 7 + fs/squashfs/sqfs.c | 1521 +++++++++++++++++++++++++++++++ fs/squashfs/sqfs_decompressor.c | 53 ++ fs/squashfs/sqfs_decompressor.h | 58 ++ fs/squashfs/sqfs_dir.c | 107 +++ fs/squashfs/sqfs_filesystem.h | 300 ++++++ fs/squashfs/sqfs_inode.c | 142 +++ fs/squashfs/sqfs_utils.h | 49 + include/fs.h | 1 + include/squashfs.h | 25 + include/u-boot/zlib.h | 32 + lib/zlib/uncompr.c | 97 ++ lib/zlib/zlib.c | 1 + 22 files changed, 2490 insertions(+) create mode 100644 cmd/sqfs.c create mode 100644 fs/squashfs/Kconfig create mode 100644 fs/squashfs/Makefile create mode 100644 fs/squashfs/sqfs.c create mode 100644 fs/squashfs/sqfs_decompressor.c create mode 100644 fs/squashfs/sqfs_decompressor.h create mode 100644 fs/squashfs/sqfs_dir.c create mode 100644 fs/squashfs/sqfs_filesystem.h create mode 100644 fs/squashfs/sqfs_inode.c create mode 100644 fs/squashfs/sqfs_utils.h create mode 100644 include/squashfs.h create mode 100644 lib/zlib/uncompr.c

Add support for SquashFS filesystem. Right now, it does not support compression but support for zlib will be added in a follow-up commit.
Signed-off-by: Joao Marcos Costa joaomarcos.costa@bootlin.com --- Changes in v3: - Replace CONFIG_IS_ENABLED by IS_ENABLED in fs/fs.c Changes in v2: - Add info. to MAINTAINERS - Replace sqfs_ls() by U-Boot's fs_ls_generic() - Improve string parsing when resolving symlink targets - Replace cmd_tbl_t type by the cmd_tbl struct - Add missing <command.h> in cmd/sqfs.c - Fix build failures - Replace "struct disk_partition_t" by "disk_partition" - Remove CMD_RET* from the filesystem code - Simplify superblock handling by adding "struct squashfs_ctxt", which encloses the complete global context (superblock, cur_dev and cur_part_info). The struct members are set up during probe() and cleaned up during close(). MAINTAINERS | 6 + common/spl/Kconfig | 9 + fs/Kconfig | 2 + fs/Makefile | 2 + fs/fs.c | 15 + fs/squashfs/Kconfig | 10 + fs/squashfs/Makefile | 7 + fs/squashfs/sqfs.c | 1521 +++++++++++++++++++++++++++++++ fs/squashfs/sqfs_decompressor.c | 29 + fs/squashfs/sqfs_decompressor.h | 58 ++ fs/squashfs/sqfs_dir.c | 107 +++ fs/squashfs/sqfs_filesystem.h | 300 ++++++ fs/squashfs/sqfs_inode.c | 142 +++ fs/squashfs/sqfs_utils.h | 49 + include/fs.h | 1 + include/squashfs.h | 25 + 16 files changed, 2283 insertions(+) create mode 100644 fs/squashfs/Kconfig create mode 100644 fs/squashfs/Makefile create mode 100644 fs/squashfs/sqfs.c create mode 100644 fs/squashfs/sqfs_decompressor.c create mode 100644 fs/squashfs/sqfs_decompressor.h create mode 100644 fs/squashfs/sqfs_dir.c create mode 100644 fs/squashfs/sqfs_filesystem.h create mode 100644 fs/squashfs/sqfs_inode.c create mode 100644 fs/squashfs/sqfs_utils.h create mode 100644 include/squashfs.h
diff --git a/MAINTAINERS b/MAINTAINERS index 6316c6ca00..198fe2b89e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -944,6 +944,12 @@ S: Maintained F: drivers/spmi/ F: include/spmi/
+SQUASHFS +M: Joao Marcos Costa joaomarcos.costa@bootlin.com +S: Maintained +F: fs/squashfs/ +F: include/sqfs.h + TDA19988 HDMI ENCODER M: Liviu Dudau liviu.dudau@foss.arm.com S: Maintained diff --git a/common/spl/Kconfig b/common/spl/Kconfig index d09e52e88b..d7a32ca40f 100644 --- a/common/spl/Kconfig +++ b/common/spl/Kconfig @@ -577,6 +577,15 @@ config SPL_FS_EXT4 filesystem from within SPL. Support for the underlying block device (e.g. MMC or USB) must be enabled separately.
+config SPL_FS_SQUASHFS + bool "Support SquashFS filesystems" + select FS_SQUASHFS + help + Enable support for SquashFS filesystems with SPL. This permits + U-Boot (or Linux in Falcon mode) to be loaded from a SquashFS + filesystem from within SPL. Support for the underlying block + device (e.g. MMC or USB) must be enabled separately. + config SPL_FS_FAT bool "Support FAT filesystems" select FS_FAT diff --git a/fs/Kconfig b/fs/Kconfig index 1cb9831be8..620af7f044 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -22,4 +22,6 @@ source "fs/cramfs/Kconfig"
source "fs/yaffs2/Kconfig"
+source "fs/squashfs/Kconfig" + endmenu diff --git a/fs/Makefile b/fs/Makefile index 42e669c40c..937cbcf6e8 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_FS_LOADER) += fs.o obj-$(CONFIG_SPL_FS_FAT) += fat/ obj-$(CONFIG_SPL_FS_EXT4) += ext4/ obj-$(CONFIG_SPL_FS_CBFS) += cbfs/ +obj-$(CONFIG_SPL_FS_SQUASHFS) += squashfs/ else obj-y += fs.o
@@ -23,5 +24,6 @@ obj-$(CONFIG_SANDBOX) += sandbox/ obj-$(CONFIG_CMD_UBIFS) += ubifs/ obj-$(CONFIG_YAFFS2) += yaffs2/ obj-$(CONFIG_CMD_ZFS) += zfs/ +obj-$(CONFIG_FS_SQUASHFS) += squashfs/ endif obj-y += fs_internal.o diff --git a/fs/fs.c b/fs/fs.c index edd8adc21b..5b31a369f7 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -22,6 +22,7 @@ #include <div64.h> #include <linux/math64.h> #include <efi_loader.h> +#include <squashfs.h>
DECLARE_GLOBAL_DATA_PTR;
@@ -276,6 +277,20 @@ static struct fstype_info fstypes[] = { .mkdir = fs_mkdir_unsupported, .ln = fs_ln_unsupported, }, +#endif +#if IS_ENABLED(CONFIG_FS_SQUASHFS) + { + .fstype = FS_TYPE_SQUASHFS, + .name = "squashfs", + .probe = sqfs_probe, + .opendir = sqfs_opendir, + .readdir = sqfs_readdir, + .ls = fs_ls_generic, + .read = sqfs_read, + .size = sqfs_size, + .close = sqfs_close, + .closedir = sqfs_closedir, + }, #endif { .fstype = FS_TYPE_ANY, diff --git a/fs/squashfs/Kconfig b/fs/squashfs/Kconfig new file mode 100644 index 0000000000..b9772e5619 --- /dev/null +++ b/fs/squashfs/Kconfig @@ -0,0 +1,10 @@ +config FS_SQUASHFS + bool "Enable SquashFS filesystem support" + help + This provides support for reading images from SquashFS filesystem. + Squashfs is a compressed read-only filesystem for Linux. + It uses zlib, lz4, lzo, or xz compression to compress files, inodes + and directories. Squashfs is intended for general read-only + filesystem use, for archival use (i.e. in cases where a .tar.gz file + may be used), and in constrained block device/memory systems (e.g. + embedded systems) where low overhead is needed. diff --git a/fs/squashfs/Makefile b/fs/squashfs/Makefile new file mode 100644 index 0000000000..ba66ee821c --- /dev/null +++ b/fs/squashfs/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0+ +# + +obj-$(CONFIG_$(SPL_)FS_SQUASHFS) = sqfs.o \ + sqfs_inode.o \ + sqfs_dir.o \ + sqfs_decompressor.o diff --git a/fs/squashfs/sqfs.c b/fs/squashfs/sqfs.c new file mode 100644 index 0000000000..76019444d8 --- /dev/null +++ b/fs/squashfs/sqfs.c @@ -0,0 +1,1521 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Bootlin + * + * Author: Joao Marcos Costa joaomarcos.costa@bootlin.com + * + * sqfs.c: SquashFS filesystem implementation + */ + +#include <asm/unaligned.h> +#include <errno.h> +#include <fs.h> +#include <linux/types.h> +#include <linux/byteorder/little_endian.h> +#include <linux/byteorder/generic.h> +#include <memalign.h> +#include <stdlib.h> +#include <string.h> +#include <squashfs.h> +#include <part.h> + +#include "sqfs_decompressor.h" +#include "sqfs_filesystem.h" +#include "sqfs_utils.h" + +struct squashfs_ctxt { + struct disk_partition cur_part_info; + struct blk_desc *cur_dev; + struct squashfs_super_block *sblk; +}; + +static struct squashfs_ctxt ctxt; + +static int sqfs_disk_read(__u32 block, __u32 nr_blocks, void *buf) +{ + ulong ret; + + if (!ctxt.cur_dev) + return -1; + + ret = blk_dread(ctxt.cur_dev, ctxt.cur_part_info.start + block, + nr_blocks, buf); + + if (ret != nr_blocks) + return -1; + + return ret; +} + +static int sqfs_read_sblk(struct squashfs_super_block **sblk) +{ + *sblk = malloc_cache_aligned(ctxt.cur_dev->blksz); + if (!*sblk) + return -ENOMEM; + + if (sqfs_disk_read(0, 1, *sblk) != 1) { + free(*sblk); + return -EINVAL; + } + + return 0; +} + +static int sqfs_count_tokens(const char *filename) +{ + int token_count = 1, l; + + for (l = 1; l < strlen(filename); l++) { + if (filename[l] == '/') + token_count++; + } + + /* Ignore trailing '/' in path */ + if (filename[strlen(filename) - 1] == '/') + token_count--; + + if (!token_count) + token_count = 1; + + return token_count; +} + +/* + * Calculates how many blocks are needed for the buffer used in sqfs_disk_read. + * The memory section (e.g. inode table) start offset and its end (i.e. the next + * table start) must be specified. It also calculates the offset from which to + * start reading the buffer. + */ +static int sqfs_calc_n_blks(__le64 start, __le64 end, u64 *offset) +{ + u64 start_, table_size; + + table_size = le64_to_cpu(end) - le64_to_cpu(start); + start_ = le64_to_cpu(start) / ctxt.cur_dev->blksz; + *offset = le64_to_cpu(start) - (start_ * ctxt.cur_dev->blksz); + + return DIV_ROUND_UP(table_size + *offset, ctxt.cur_dev->blksz); +} + +/* + * Retrieves fragment block entry and returns true if the fragment block is + * compressed + */ +static int sqfs_frag_lookup(u32 inode_fragment_index, + struct squashfs_fragment_block_entry *e) +{ + u64 start, n_blks, src_len, table_offset, start_block; + unsigned char *metadata_buffer, *metadata, *table; + struct squashfs_fragment_block_entry *entries; + struct squashfs_super_block *sblk = ctxt.sblk; + unsigned long dest_len; + int block, offset, ret; + u16 header, comp_type; + + comp_type = le16_to_cpu(ctxt.sblk->compression); + + if (inode_fragment_index >= le32_to_cpu(sblk->fragments)) + return -EINVAL; + + start = le64_to_cpu(sblk->fragment_table_start) / ctxt.cur_dev->blksz; + n_blks = sqfs_calc_n_blks(sblk->fragment_table_start, + sblk->export_table_start, &table_offset); + + /* Allocate a proper sized buffer to store the fragment index table */ + table = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz); + if (!table) + return -ENOMEM; + + if (sqfs_disk_read(start, n_blks, table) < 0) { + free(table); + return -EINVAL; + } + + block = SQFS_FRAGMENT_INDEX(inode_fragment_index); + offset = SQFS_FRAGMENT_INDEX_OFFSET(inode_fragment_index); + + /* + * Get the start offset of the metadata block that contains the right + * fragment block entry + */ + start_block = get_unaligned((u64 *)&table[table_offset] + block); + + start = start_block / ctxt.cur_dev->blksz; + n_blks = sqfs_calc_n_blks(cpu_to_le64(start_block), + sblk->fragment_table_start, &table_offset); + + metadata_buffer = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz); + if (!metadata_buffer) { + ret = -ENOMEM; + goto free_table; + } + + if (sqfs_disk_read(start, n_blks, metadata_buffer) < 0) { + ret = -EINVAL; + goto free_buffer; + } + + /* Every metadata block starts with a 16-bit header */ + header = get_unaligned((u16 *)&metadata_buffer[table_offset]); + metadata = &metadata_buffer[table_offset + SQFS_HEADER_SIZE]; + + entries = malloc(SQFS_METADATA_BLOCK_SIZE); + if (!entries) { + ret = -ENOMEM; + goto free_buffer; + } + + if (SQFS_COMPRESSED_METADATA(header)) { + src_len = SQFS_METADATA_SIZE(header); + dest_len = SQFS_METADATA_BLOCK_SIZE; + ret = sqfs_decompress(comp_type, entries, &dest_len, metadata, + src_len); + if (ret) { + ret = -EINVAL; + goto free_entries; + } + } else { + memcpy(entries, metadata, dest_len); + } + + *e = entries[offset]; + ret = SQFS_COMPRESSED_BLOCK(e->size); + +free_entries: + free(entries); +free_buffer: + free(metadata_buffer); +free_table: + free(table); + return ret; +} + +/* + * The entry name is a flexible array member, and we don't know its size before + * actually reading the entry. So we need a first copy to retrieve this size so + * we can finally copy the whole struct. + */ +static int sqfs_read_entry(struct squashfs_directory_entry **dest, void *src) +{ + struct squashfs_directory_entry tmp; + + memcpy(&tmp, src, sizeof(tmp)); + /* + * name_size property is actually the string length - 1, so adding 2 + * compensates this difference and adds space for the trailling null + * byte. + */ + *dest = malloc(sizeof(tmp) + tmp.name_size + 2); + if (!*dest) + return -ENOMEM; + + memcpy(*dest, src, sizeof(tmp) + tmp.name_size + 1); + (*dest)->name[tmp.name_size + 1] = '\0'; + + return 0; +} + +/* Takes a token list and returns a single string with '/' as separator. */ +static char *sqfs_concat_tokens(char **token_list, int token_count) +{ + char *result; + int i, length = 0, offset = 0; + + for (i = 0; i < token_count; i++) + length += strlen(token_list[i]) + 1; + + result = malloc(length + 1); + result[length] = '\0'; + + for (i = 0; i < token_count; i++) { + strcpy(result + offset, token_list[i]); + offset += strlen(token_list[i]); + result[offset++] = '/'; + } + + return result; +} + +/* + * Differently from sqfs_concat_tokens, sqfs_join writes the result into a + * previously allocated string, and returns the number of bytes written. + */ +static int sqfs_join(char **strings, char *dest, int start, int end, + char separator) +{ + int i, offset = 0; + + for (i = start; i < end; i++) { + strcpy(dest + offset, strings[i]); + offset += strlen(strings[i]); + if (i < end - 1) + dest[offset++] = separator; + } + + return offset; +} + +/* + * Fills the given token list using its size (count) and a source string (str) + */ +static int sqfs_tokenize(char **tokens, int count, const char *str) +{ + char *aux, *strc; + int i, j; + + strc = strdup(str); + if (!strc) + return -ENOMEM; + + /* Allocate and fill base tokens list */ + if (!strcmp(strc, "/")) { + tokens[0] = strdup(strc); + if (!tokens[0]) { + free(strc); + return -ENOMEM; + } + } else { + for (j = 0; j < count; j++) { + aux = strtok(!j ? strc : NULL, "/"); + tokens[j] = strdup(aux); + if (!tokens[j]) { + for (i = 0; i < j; i++) + free(tokens[i]); + free(strc); + return -ENOMEM; + } + } + } + + free(strc); + + return 0; +} + +static int sqfs_get_tokens_length(char **tokens, int count) +{ + int length = 0, i; + + /* + * 1 is added to the result of strlen to consider the slash separator + * between the tokens + */ + + for (i = 0; i < count; i++) + length += strlen(tokens[i]) + 1; + + return length; +} + +/* + * Remove last 'updir + 1' tokens from the base path tokens list. This leaves us + * with a token list containing only the tokens needed to form the resolved + * path, and returns the decremented size of the token list. + */ +static int sqfs_clean_base_path(char **base, int count, int updir) +{ + int i; + + for (i = count - updir - 1; i < count; i++) + free(base[i]); + + return count - updir - 1; +} + +/* + * Given the base ("current dir.") path and the relative one, generate the + * absolute path. + */ +static char *sqfs_get_abs_path(const char *base, const char *rel) +{ + char **base_tokens, **rel_tokens, *resolved = NULL, *basec, *relc; + int ret, bc, rc, i, updir = 0, resolved_size = 0, offset = 0; + + /* create copies of parameters */ + basec = strdup(base); + if (!basec) + return NULL; + + relc = strdup(rel); + if (!relc) + goto free_basec; + + /* count tokens in paths */ + bc = sqfs_count_tokens(basec); + rc = sqfs_count_tokens(relc); + if (bc < 1 || rc < 1) + goto free_relc; + + base_tokens = malloc(bc * sizeof(char *)); + if (!base_tokens) + goto free_relc; + + rel_tokens = malloc(rc * sizeof(char *)); + if (!rel_tokens) + goto free_b_tokens; + + /* Allocate and fill tokens list */ + ret = sqfs_tokenize(base_tokens, bc, basec); + if (ret) + goto free_r_tokens; + + sqfs_tokenize(rel_tokens, rc, relc); + if (ret) + goto free_r_tokens; + + /* count '..' occurrences in target path */ + for (i = 0; i < rc; i++) { + if (!strcmp(rel_tokens[i], "..")) + updir++; + } + + bc = sqfs_clean_base_path(base_tokens, bc, updir); + if (bc < 0) + goto free_r_tokens; + + if (!bc) + resolved_size++; + + resolved_size += sqfs_get_tokens_length(base_tokens, bc) + + sqfs_get_tokens_length(rel_tokens, rc); + + resolved = malloc(resolved_size + 1); + if (!resolved) + goto free_r_tokens_loop; + + memset(resolved, '\0', resolved_size + 1); + offset += sqfs_join(base_tokens, resolved + offset, 0, bc, '/'); + resolved[offset++] = '/'; + offset += sqfs_join(rel_tokens, resolved + offset, updir, rc, '/'); + +free_r_tokens_loop: + for (i = 0; i < rc; i++) + free(rel_tokens[i]); + for (i = 0; i < bc; i++) + free(base_tokens[i]); +free_r_tokens: + free(rel_tokens); +free_b_tokens: + free(base_tokens); +free_relc: + free(relc); +free_basec: + free(basec); + + return resolved; +} + +static char *sqfs_resolve_symlink(struct squashfs_symlink_inode *sym, + unsigned char *table, const char *base_path) +{ + char *resolved, *target; + + target = malloc(sym->symlink_size + 1); + if (!target) + return NULL; + + target[sym->symlink_size] = '\0'; + /* Get target name (relative path) */ + strncpy(target, (char *)table + sizeof(*sym), sym->symlink_size); + + /* Relative -> absolute path conversion */ + resolved = sqfs_get_abs_path(base_path, target); + + free(target); + + return resolved; +} + +/* + * m_list contains each metadata block's position, and m_count is the number of + * elements of m_list. Those metadata blocks come from the compressed directory + * table. + */ +static int sqfs_search_dir(struct squashfs_dir_stream *dirs, char **token_list, + int token_count, u32 *m_list, int m_count) +{ + char *path, *target, **sym_tokens; + int j, ret, new_inode_number, offset; + struct squashfs_super_block *sblk = ctxt.sblk; + struct squashfs_symlink_inode sym; + struct squashfs_ldir_inode ldir; + struct squashfs_dir_inode dir; + struct fs_dir_stream *dirsp; + struct fs_dirent *dent; + unsigned char *table; + + dirsp = (struct fs_dir_stream *)dirs; + + /* Start by root inode */ + table = sqfs_find_inode(dirs->inode_table, le32_to_cpu(sblk->inodes), + sblk->inodes, sblk->block_size); + + /* root is a regular directory, not an extended one */ + memcpy(&dir, table, sizeof(dir)); + + /* get directory offset in directory table */ + offset = sqfs_dir_offset(table, m_list, m_count); + dirs->table = &dirs->dir_table[offset]; + + /* Setup directory header */ + dirs->dir_header = malloc(SQFS_DIR_HEADER_SIZE); + if (!dirs->dir_header) + return -ENOMEM; + + memcpy(dirs->dir_header, dirs->table, SQFS_DIR_HEADER_SIZE); + + /* Initialize squashfs_dir_stream members */ + dirs->table += SQFS_DIR_HEADER_SIZE; + dirs->size = le16_to_cpu(dir.file_size) - SQFS_DIR_HEADER_SIZE; + dirs->entry_count = dirs->dir_header->count + 1; + + /* No path given -> root directory */ + if (!strcmp(token_list[0], "/")) { + dirs->table = &dirs->dir_table[offset]; + memcpy(&dirs->i_dir, &dir, sizeof(dir)); + return 0; + } + + for (j = 0; j < token_count; j++) { + if (!sqfs_is_dir(dir.inode_type)) { + printf("** Cannot find directory. **\n"); + return -EINVAL; + } + + while (!sqfs_readdir(dirsp, &dent)) { + ret = strcmp(dent->name, token_list[j]); + if (!ret) + break; + free(dirs->entry); + } + + if (ret) { + printf("** Cannot find directory. **\n"); + free(dirs->entry); + return -EINVAL; + } + + /* Redefine inode as the found token */ + new_inode_number = dirs->entry->inode_offset + + dirs->dir_header->inode_number; + + /* Get reference to inode in the inode table */ + table = sqfs_find_inode(dirs->inode_table, new_inode_number, + sblk->inodes, sblk->block_size); + memcpy(&dir, table, sizeof(dir)); + + /* Check for symbolic link and inode type sanity */ + if (le16_to_cpu(dir.inode_type) == SQFS_SYMLINK_TYPE) { + memcpy(&sym, table, sizeof(sym)); + path = sqfs_concat_tokens(token_list, token_count); + target = sqfs_resolve_symlink(&sym, table, path); + token_count = sqfs_count_tokens(target); + if (token_count < 0) + return -EINVAL; + + sym_tokens = malloc(token_count * sizeof(char *)); + if (!sym_tokens) + return -EINVAL; + + /* Allocate and fill tokens list */ + ret = sqfs_tokenize(sym_tokens, token_count, target); + if (ret) + return -EINVAL; + free(dirs->entry); + + ret = sqfs_search_dir(dirs, sym_tokens, token_count, + m_list, m_count); + return ret; + } else if (!sqfs_is_dir(dir.inode_type)) { + printf("** Cannot find directory. **\n"); + free(dirs->entry); + return -EINVAL; + } + + /* Check if it is an extended dir. */ + if (dir.inode_type == SQFS_LDIR_TYPE) + memcpy(&ldir, table, sizeof(ldir)); + + /* Get dir. offset into the directory table */ + offset = sqfs_dir_offset(table, m_list, m_count); + dirs->table = &dirs->dir_table[offset]; + + /* Copy directory header */ + memcpy(dirs->dir_header, &dirs->dir_table[offset], + SQFS_DIR_HEADER_SIZE); + + /* Check for empty directory */ + if (sqfs_is_empty_dir(table)) { + printf("Empty directory.\n"); + free(dirs->entry); + return SQFS_EMPTY_DIR; + } + + dirs->table += SQFS_DIR_HEADER_SIZE; + dirs->size = le16_to_cpu(dir.file_size); + dirs->entry_count = dirs->dir_header->count + 1; + dirs->size -= SQFS_DIR_HEADER_SIZE; + free(dirs->entry); + } + + offset = sqfs_dir_offset(table, m_list, m_count); + dirs->table = &dirs->dir_table[offset]; + + if (dir.inode_type == SQFS_DIR_TYPE) + memcpy(&dirs->i_dir, &dir, sizeof(dir)); + else + memcpy(&dirs->i_ldir, &ldir, sizeof(ldir)); + + return 0; +} + +/* + * Inode and directory tables are stored as a series of metadata blocks, and + * given the compressed size of this table, we can calculate how much metadata + * blocks are needed to store the result of the decompression, since a + * decompressed metadata block should have a size of 8KiB. + */ +static int sqfs_count_metablks(void *table, u32 offset, int table_size) +{ + int count = 0, cur_size = 0, ret; + u32 data_size; + bool comp; + + do { + ret = sqfs_read_metablock(table, offset + cur_size, &comp, + &data_size); + if (ret) + return -EINVAL; + cur_size += data_size + SQFS_HEADER_SIZE; + count++; + } while (cur_size < table_size); + + return count; +} + +/* + * Storing the metadata blocks header's positions will be useful while looking + * for an entry in the directory table, using the reference (index and offset) + * given by its inode. + */ +static int sqfs_get_metablk_pos(u32 *pos_list, void *table, u32 offset, + int metablks_count) +{ + u32 data_size, cur_size = 0; + int j, ret = 0; + bool comp; + + if (!metablks_count) + return -EINVAL; + + for (j = 0; j < metablks_count; j++) { + ret = sqfs_read_metablock(table, offset + cur_size, &comp, + &data_size); + if (ret) + return -EINVAL; + + cur_size += data_size + SQFS_HEADER_SIZE; + pos_list[j] = cur_size; + } + + return ret; +} + +static int sqfs_read_inode_table(unsigned char **inode_table) +{ + u64 start, n_blks, table_offset, table_size; + int j, ret = 0, metablks_count, comp_type; + struct squashfs_super_block *sblk = ctxt.sblk; + unsigned char *src_table, *itb; + u32 src_len, dest_offset = 0; + unsigned long dest_len; + bool compressed; + + comp_type = sblk->compression; + table_size = le64_to_cpu(sblk->directory_table_start - + sblk->inode_table_start); + start = sblk->inode_table_start / ctxt.cur_dev->blksz; + n_blks = sqfs_calc_n_blks(sblk->inode_table_start, + sblk->directory_table_start, &table_offset); + + /* Allocate a proper sized buffer (itb) to store the inode table */ + itb = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz); + if (!itb) + return -ENOMEM; + + if (sqfs_disk_read(start, n_blks, itb) < 0) { + ret = -EINVAL; + goto free_itb; + } + + /* Parse inode table (metadata block) header */ + ret = sqfs_read_metablock(itb, table_offset, &compressed, &src_len); + if (ret) { + ret = -EINVAL; + goto free_itb; + } + + /* Calculate size to store the whole decompressed table */ + metablks_count = sqfs_count_metablks(itb, table_offset, table_size); + if (metablks_count < 1) { + ret = -EINVAL; + goto free_itb; + } + + *inode_table = malloc(metablks_count * SQFS_METADATA_BLOCK_SIZE); + if (!*inode_table) { + ret = -ENOMEM; + goto free_itb; + } + + src_table = itb + table_offset + SQFS_HEADER_SIZE; + + /* Extract compressed Inode table */ + for (j = 0; j < metablks_count; j++) { + sqfs_read_metablock(itb, table_offset, &compressed, &src_len); + if (compressed) { + dest_len = SQFS_METADATA_BLOCK_SIZE; + ret = sqfs_decompress(comp_type, *inode_table + + dest_offset, &dest_len, + src_table, src_len); + if (ret) { + free(inode_table); + goto free_itb; + } + + } else { + memcpy(*inode_table + (j * SQFS_METADATA_BLOCK_SIZE), + src_table, src_len); + } + + /* + * Offsets to the decompression destination, to the metadata + * buffer 'itb' and to the decompression source, respectively. + */ + dest_offset += dest_len; + table_offset += src_len + SQFS_HEADER_SIZE; + src_table += src_len + SQFS_HEADER_SIZE; + } + +free_itb: + free(itb); + + return ret; +} + +static int sqfs_read_directory_table(unsigned char **dir_table, u32 **pos_list) +{ + u64 start, n_blks, table_offset, table_size; + int j, ret = 0, metablks_count = -1, comp_type; + struct squashfs_super_block *sblk = ctxt.sblk; + unsigned char *src_table, *dtb; + u32 src_len, dest_offset = 0; + unsigned long dest_len; + bool compressed; + + comp_type = sblk->compression; + + /* DIRECTORY TABLE */ + table_size = le64_to_cpu(sblk->fragment_table_start - + sblk->directory_table_start); + start = sblk->directory_table_start / ctxt.cur_dev->blksz; + n_blks = sqfs_calc_n_blks(sblk->directory_table_start, + sblk->fragment_table_start, &table_offset); + + /* Allocate a proper sized buffer (dtb) to store the directory table */ + dtb = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz); + if (!dtb) + return -ENOMEM; + + if (sqfs_disk_read(start, n_blks, dtb) < 0) + goto free_dtb; + + /* Parse directory table (metadata block) header */ + ret = sqfs_read_metablock(dtb, table_offset, &compressed, &src_len); + if (ret) + goto free_dtb; + + /* Calculate total size to store the whole decompressed table */ + metablks_count = sqfs_count_metablks(dtb, table_offset, table_size); + if (metablks_count < 1) + goto free_dtb; + + *dir_table = malloc(metablks_count * SQFS_METADATA_BLOCK_SIZE); + if (!*dir_table) + goto free_dtb; + + *pos_list = malloc(metablks_count * sizeof(u32)); + if (!*pos_list) { + free(*dir_table); + goto free_dtb; + } + + ret = sqfs_get_metablk_pos(*pos_list, dtb, table_offset, + metablks_count); + if (ret) { + metablks_count = -1; + free(*dir_table); + free(*pos_list); + goto free_dtb; + } + + src_table = dtb + table_offset + SQFS_HEADER_SIZE; + + /* Extract compressed Directory table */ + dest_offset = 0; + for (j = 0; j < metablks_count; j++) { + sqfs_read_metablock(dtb, table_offset, &compressed, &src_len); + if (compressed) { + dest_len = SQFS_METADATA_BLOCK_SIZE; + ret = sqfs_decompress(comp_type, *dir_table + + (j * SQFS_METADATA_BLOCK_SIZE), + &dest_len, src_table, src_len); + if (ret) { + metablks_count = -1; + free(*dir_table); + goto free_dtb; + } + + if (dest_len < SQFS_METADATA_BLOCK_SIZE) { + dest_offset += dest_len; + break; + } + } else { + memcpy(*dir_table + (j * SQFS_METADATA_BLOCK_SIZE), + src_table, src_len); + } + + /* + * Offsets to the decompression destination, to the metadata + * buffer 'dtb' and to the decompression source, respectively. + */ + dest_offset += dest_len; + table_offset += src_len + SQFS_HEADER_SIZE; + src_table += src_len + SQFS_HEADER_SIZE; + } + +free_dtb: + free(dtb); + + return metablks_count; +} + +int sqfs_opendir(const char *filename, struct fs_dir_stream **dirsp) +{ + unsigned char *inode_table = NULL, *dir_table = NULL; + int j, token_count, ret = 0, metablks_count; + struct squashfs_dir_stream *dirs; + char **token_list, *path; + u32 *pos_list = NULL; + + dirs = malloc(sizeof(*dirs)); + if (!dirs) + return -EINVAL; + + ret = sqfs_read_inode_table(&inode_table); + if (ret) + return -EINVAL; + + metablks_count = sqfs_read_directory_table(&dir_table, &pos_list); + if (metablks_count < 1) + return -EINVAL; + + /* Tokenize filename */ + token_count = sqfs_count_tokens(filename); + if (token_count < 0) + return -EINVAL; + + path = strdup(filename); + if (!path) + return -ENOMEM; + + token_list = malloc(token_count * sizeof(char *)); + if (!token_list) { + ret = -EINVAL; + goto free_path; + } + + /* Allocate and fill tokens list */ + ret = sqfs_tokenize(token_list, token_count, path); + if (ret) + goto free_tokens; + /* + * ldir's (extended directory) size is greater than dir, so it works as + * a general solution for the malloc size, since 'i' is a union. + */ + dirs->inode_table = inode_table; + dirs->dir_table = dir_table; + ret = sqfs_search_dir(dirs, token_list, token_count, pos_list, + metablks_count); + if (ret) + goto free_tokens; + + if (le16_to_cpu(dirs->i_dir.inode_type) == SQFS_DIR_TYPE) + dirs->size = le16_to_cpu(dirs->i_dir.file_size); + else + dirs->size = le32_to_cpu(dirs->i_ldir.file_size); + + /* Setup directory header */ + memcpy(dirs->dir_header, dirs->table, SQFS_DIR_HEADER_SIZE); + dirs->entry_count = dirs->dir_header->count + 1; + dirs->size -= SQFS_DIR_HEADER_SIZE; + + /* Setup entry */ + dirs->entry = NULL; + dirs->table += SQFS_DIR_HEADER_SIZE; + + *dirsp = (struct fs_dir_stream *)dirs; + +free_tokens: + for (j = 0; j < token_count; j++) + free(token_list[j]); + free(token_list); + free(pos_list); +free_path: + free(path); + + return ret; +} + +int sqfs_readdir(struct fs_dir_stream *fs_dirs, struct fs_dirent **dentp) +{ + struct squashfs_dir_stream *dirs; + struct squashfs_lreg_inode lreg; + struct squashfs_base_inode base; + struct squashfs_reg_inode reg; + struct fs_dirent *dent; + int offset = 0, ret; + + dirs = (struct squashfs_dir_stream *)fs_dirs; + if (!dirs->size) { + *dentp = NULL; + return -SQFS_STOP_READDIR; + } + + dent = &dirs->dentp; + + if (!dirs->entry_count) { + if (dirs->size > SQFS_DIR_HEADER_SIZE) { + dirs->size -= SQFS_DIR_HEADER_SIZE; + } else { + *dentp = NULL; + dirs->size = 0; + return -SQFS_STOP_READDIR; + } + + if (dirs->size > SQFS_EMPTY_FILE_SIZE) { + /* Read follow-up (emitted) dir. header */ + memcpy(dirs->dir_header, dirs->table, + SQFS_DIR_HEADER_SIZE); + dirs->entry_count = dirs->dir_header->count + 1; + ret = sqfs_read_entry(&dirs->entry, dirs->table + + SQFS_DIR_HEADER_SIZE); + if (ret) + return -SQFS_STOP_READDIR; + + dirs->table += SQFS_DIR_HEADER_SIZE; + } + } else { + ret = sqfs_read_entry(&dirs->entry, dirs->table); + if (ret) + return -SQFS_STOP_READDIR; + } + + memcpy(&base, &dirs->inode_table[dirs->entry->offset], sizeof(base)); + + /* Set entry type and size */ + switch (dirs->entry->type) { + case SQFS_DIR_TYPE: + case SQFS_LDIR_TYPE: + dent->type = FS_DT_DIR; + break; + case SQFS_REG_TYPE: + case SQFS_LREG_TYPE: + /* + * Entries do not differentiate extended from regular types, so + * it needs to be verified manually. + */ + if (base.inode_type == SQFS_LREG_TYPE) { + memcpy(&lreg, &dirs->inode_table[dirs->entry->offset], + sizeof(lreg)); + dent->size = (loff_t)le64_to_cpu(lreg.file_size); + } else { + memcpy(®, &dirs->inode_table[dirs->entry->offset], + sizeof(reg)); + dent->size = (loff_t)le32_to_cpu(reg.file_size); + } + + dent->type = FS_DT_REG; + break; + case SQFS_BLKDEV_TYPE: + case SQFS_CHRDEV_TYPE: + case SQFS_LBLKDEV_TYPE: + case SQFS_LCHRDEV_TYPE: + case SQFS_FIFO_TYPE: + case SQFS_SOCKET_TYPE: + case SQFS_LFIFO_TYPE: + case SQFS_LSOCKET_TYPE: + dent->type = SQFS_MISC_ENTRY_TYPE; + break; + case SQFS_SYMLINK_TYPE: + case SQFS_LSYMLINK_TYPE: + dent->type = FS_DT_LNK; + break; + default: + /* + * This macro evaluates to 0, which usually means a successful + * execution, but in this case it returns 0 to stop the while + * loop. + */ + return -SQFS_STOP_READDIR; + } + + /* Set entry name */ + strncpy(dent->name, dirs->entry->name, dirs->entry->name_size + 1); + dent->name[dirs->entry->name_size + 1] = '\0'; + + offset = dirs->entry->name_size + 1 + SQFS_ENTRY_BASE_LENGTH; + dirs->entry_count--; + + /* Decrement size to be read */ + if (dirs->size > offset) + dirs->size -= offset; + else + dirs->size = 0; + + /* Keep a reference to the current entry before incrementing it */ + dirs->table += offset; + + *dentp = dent; + + return 0; +} + +int sqfs_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition) +{ + struct squashfs_super_block *sblk; + int ret; + + ctxt.cur_dev = fs_dev_desc; + ctxt.cur_part_info = *fs_partition; + + ret = sqfs_read_sblk(&sblk); + if (ret) + return ret; + + /* Make sure it has a valid SquashFS magic number*/ + if (sblk->s_magic != SQFS_MAGIC_NUMBER) { + printf("Bad magic number for SquashFS image.\n"); + ctxt.cur_dev = NULL; + return -EINVAL; + } + + ctxt.sblk = sblk; + + return 0; +} + +static char *sqfs_basename(char *path) +{ + char *fname; + + fname = path + strlen(path) - 1; + while (fname >= path) { + if (*fname == '/') { + fname++; + break; + } + + fname--; + } + + return fname; +} + +static char *sqfs_dirname(char *path) +{ + char *fname; + + fname = sqfs_basename(path); + --fname; + *fname = '\0'; + + return path; +} + +/* + * Takes a path to file and splits it in two parts: the filename itself and the + * directory's path, e.g.: + * path: /path/to/file.txt + * file: file.txt + * dir: /path/to + */ +static int sqfs_split_path(char **file, char **dir, const char *path) +{ + char *dirc, *basec, *bname, *dname, *tmp_path; + int ret = 0; + + /* check for first slash in path*/ + if (path[0] == '/') { + tmp_path = strdup(path); + if (!tmp_path) + return -ENOMEM; + } else { + tmp_path = malloc(strlen(path) + 2); + if (!tmp_path) + return -ENOMEM; + tmp_path[0] = '/'; + strcpy(tmp_path + 1, path); + } + + /* String duplicates */ + dirc = strdup(tmp_path); + if (!dirc) { + ret = -ENOMEM; + goto free_tmp; + } + + basec = strdup(tmp_path); + if (!basec) { + ret = -ENOMEM; + goto free_dirc; + } + + dname = sqfs_dirname(dirc); + bname = sqfs_basename(basec); + + *file = strdup(bname); + + if (!*file) { + ret = -ENOMEM; + goto free_basec; + } + + if (*dname == '\0') { + *dir = malloc(2); + if (!*dir) { + ret = -ENOMEM; + goto free_basec; + } + + (*dir)[0] = '/'; + (*dir)[1] = '\0'; + } else { + *dir = strdup(dname); + if (!*dir) { + ret = -ENOMEM; + goto free_basec; + } + } + +free_basec: + free(basec); +free_dirc: + free(dirc); +free_tmp: + free(tmp_path); + + return ret; +} + +static int sqfs_get_regfile_info(struct squashfs_reg_inode *reg, + struct squashfs_file_info *finfo, + struct squashfs_fragment_block_entry *fentry, + __le32 blksz) +{ + int datablk_count = 0, ret; + + finfo->size = le32_to_cpu(reg->file_size); + finfo->offset = le32_to_cpu(reg->offset); + finfo->start = le32_to_cpu(reg->start_block); + finfo->frag = SQFS_IS_FRAGMENTED(le32_to_cpu(reg->fragment)); + + if (finfo->frag) { + datablk_count = finfo->size / le32_to_cpu(blksz); + ret = sqfs_frag_lookup(reg->fragment, fentry); + if (ret < 0) + return -EINVAL; + finfo->comp = true; + } else { + datablk_count = DIV_ROUND_UP(finfo->size, le32_to_cpu(blksz)); + } + + finfo->blk_sizes = malloc(datablk_count * sizeof(u32)); + if (!finfo->blk_sizes) + return -ENOMEM; + + return datablk_count; +} + +static int sqfs_get_lregfile_info(struct squashfs_lreg_inode *lreg, + struct squashfs_file_info *finfo, + struct squashfs_fragment_block_entry *fentry, + __le32 blksz) +{ + int datablk_count = 0, ret; + + finfo->size = le64_to_cpu(lreg->file_size); + finfo->offset = le32_to_cpu(lreg->offset); + finfo->start = le64_to_cpu(lreg->start_block); + finfo->frag = SQFS_IS_FRAGMENTED(le32_to_cpu(lreg->fragment)); + + if (finfo->frag) { + datablk_count = finfo->size / le32_to_cpu(blksz); + ret = sqfs_frag_lookup(lreg->fragment, fentry); + if (ret < 0) + return -EINVAL; + finfo->comp = true; + } else { + datablk_count = DIV_ROUND_UP(finfo->size, le32_to_cpu(blksz)); + } + + finfo->blk_sizes = malloc(datablk_count * sizeof(u32)); + if (!finfo->blk_sizes) + return -ENOMEM; + + return datablk_count; +} + +int sqfs_read(const char *filename, void *buf, loff_t offset, loff_t len, + loff_t *actread) +{ + char *dir, *fragment_block, *datablock = NULL, *data_buffer = NULL; + char *fragment, *file, *resolved, *data; + u64 start, n_blks, table_size, data_offset, table_offset; + int ret, j, i_number, comp_type, datablk_count = 0; + struct squashfs_super_block *sblk = ctxt.sblk; + struct squashfs_fragment_block_entry frag_entry; + struct squashfs_file_info finfo = {0}; + struct squashfs_symlink_inode symlink; + struct fs_dir_stream *dirsp = NULL; + struct squashfs_dir_stream *dirs; + struct squashfs_lreg_inode lreg; + struct squashfs_base_inode base; + struct squashfs_reg_inode reg; + unsigned long dest_len; + struct fs_dirent *dent; + unsigned char *ipos; + + *actread = 0; + + comp_type = le16_to_cpu(sblk->compression); + + /* + * sqfs_opendir will uncompress inode and directory tables, and will + * return a pointer to the directory that contains the requested file. + */ + sqfs_split_path(&file, &dir, filename); + ret = sqfs_opendir(dir, &dirsp); + if (ret) { + sqfs_closedir(dirsp); + goto free_paths; + } + + dirs = (struct squashfs_dir_stream *)dirsp; + + /* For now, only regular files are able to be loaded */ + while (!sqfs_readdir(dirsp, &dent)) { + ret = strcmp(dent->name, file); + if (!ret) + break; + + free(dirs->entry); + } + + if (ret) { + printf("File not found.\n"); + *actread = 0; + sqfs_closedir(dirsp); + ret = -ENOENT; + goto free_paths; + } + + i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset; + ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes, + sblk->block_size); + + memcpy(&base, ipos, sizeof(base)); + switch (base.inode_type) { + case SQFS_REG_TYPE: + memcpy(®, ipos, sizeof(reg)); + datablk_count = sqfs_get_regfile_info(®, &finfo, &frag_entry, + sblk->block_size); + if (datablk_count < 0) { + ret = -EINVAL; + goto free_paths; + } + memcpy(finfo.blk_sizes, ipos + sizeof(reg), + datablk_count * sizeof(u32)); + break; + case SQFS_LREG_TYPE: + memcpy(&lreg, ipos, sizeof(lreg)); + datablk_count = sqfs_get_lregfile_info(&lreg, &finfo, + &frag_entry, + sblk->block_size); + if (datablk_count < 0) { + ret = -EINVAL; + goto free_paths; + } + memcpy(finfo.blk_sizes, ipos + sizeof(lreg), + datablk_count * sizeof(u32)); + break; + case SQFS_SYMLINK_TYPE: + case SQFS_LSYMLINK_TYPE: + memcpy(&symlink, ipos, sizeof(symlink)); + resolved = sqfs_resolve_symlink(&symlink, ipos, filename); + ret = sqfs_read(resolved, buf, offset, len, actread); + free(resolved); + goto free_paths; + case SQFS_BLKDEV_TYPE: + case SQFS_CHRDEV_TYPE: + case SQFS_LBLKDEV_TYPE: + case SQFS_LCHRDEV_TYPE: + case SQFS_FIFO_TYPE: + case SQFS_SOCKET_TYPE: + case SQFS_LFIFO_TYPE: + case SQFS_LSOCKET_TYPE: + default: + printf("Unsupported entry type\n"); + ret = -EINVAL; + goto free_paths; + } + + /* If the user specifies a length, check its sanity */ + if (len) { + if (len > finfo.size) { + ret = -EINVAL; + goto free_paths; + } + + finfo.size = len; + } + + if (datablk_count) { + data_offset = finfo.start; + datablock = malloc(sblk->block_size); + if (!datablock) { + ret = -ENOMEM; + goto free_paths; + } + } + + for (j = 0; j < datablk_count; j++) { + start = data_offset / ctxt.cur_dev->blksz; + table_size = SQFS_BLOCK_SIZE(finfo.blk_sizes[j]); + table_offset = data_offset - (start * ctxt.cur_dev->blksz); + n_blks = DIV_ROUND_UP(table_size + table_offset, + ctxt.cur_dev->blksz); + + data_buffer = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz); + + if (!data_buffer) { + ret = -ENOMEM; + goto free_datablk; + } + + ret = sqfs_disk_read(start, n_blks, data_buffer); + if (ret < 0) { + /* + * Possible causes: too many data blocks or too large + * SquashFS block size. Tip: re-compile the SquashFS + * image with mksquashfs's -b <block_size> option. + */ + printf("Error: too many data blocks to be read.\n"); + goto free_buffer; + } + + data = data_buffer + table_offset; + + /* Load the data */ + if (SQFS_COMPRESSED_BLOCK(finfo.blk_sizes[j])) { + dest_len = sblk->block_size; + ret = sqfs_decompress(comp_type, datablock, &dest_len, + data, table_size); + if (ret) + goto free_buffer; + + memcpy(buf + offset + *actread, datablock, dest_len); + *actread += dest_len; + } else { + memcpy(buf + offset + *actread, data, table_size); + *actread += table_size; + } + + data_offset += table_size; + } + + free(finfo.blk_sizes); + + /* + * There is no need to continue if the file is not fragmented. + */ + if (!finfo.frag) { + ret = 0; + goto free_buffer; + } + + start = frag_entry.start / ctxt.cur_dev->blksz; + table_size = SQFS_BLOCK_SIZE(frag_entry.size); + table_offset = frag_entry.start - (start * ctxt.cur_dev->blksz); + n_blks = DIV_ROUND_UP(table_size + table_offset, ctxt.cur_dev->blksz); + + fragment = malloc_cache_aligned(n_blks * ctxt.cur_dev->blksz); + + if (!fragment) { + ret = -ENOMEM; + goto free_buffer; + } + + ret = sqfs_disk_read(start, n_blks, fragment); + if (ret < 0) + goto free_fragment; + + /* File compressed and fragmented */ + if (finfo.frag && finfo.comp) { + dest_len = sblk->block_size; + fragment_block = malloc(sblk->block_size); + if (!fragment_block) { + ret = -ENOMEM; + goto free_fragment; + } + + ret = sqfs_decompress(comp_type, fragment_block, &dest_len, + (void *)fragment + table_offset, + frag_entry.size); + if (ret) { + free(fragment_block); + goto free_fragment; + } + + for (j = offset + *actread; j < finfo.size; j++) { + memcpy(buf + j, &fragment_block[finfo.offset + j], 1); + (*actread)++; + } + + free(fragment_block); + + } else if (finfo.frag && !finfo.comp) { + fragment_block = (void *)fragment + table_offset; + + for (j = offset + *actread; j < finfo.size; j++) { + memcpy(buf + j, &fragment_block[finfo.offset + j], 1); + (*actread)++; + } + } + +free_fragment: + free(fragment); +free_buffer: + if (datablk_count) + free(data_buffer); +free_datablk: + if (datablk_count) + free(datablock); +free_paths: + free(file); + free(dir); + + return ret; +} + +int sqfs_size(const char *filename, loff_t *size) +{ + struct squashfs_super_block *sblk = ctxt.sblk; + struct squashfs_symlink_inode symlink; + struct fs_dir_stream *dirsp = NULL; + struct squashfs_base_inode base; + struct squashfs_dir_stream *dirs; + struct squashfs_lreg_inode lreg; + struct squashfs_reg_inode reg; + char *dir, *file, *resolved; + struct fs_dirent *dent; + unsigned char *ipos; + int ret, i_number; + + sqfs_split_path(&file, &dir, filename); + /* + * sqfs_opendir will uncompress inode and directory tables, and will + * return a pointer to the directory that contains the requested file. + */ + ret = sqfs_opendir(dir, &dirsp); + if (ret) { + sqfs_closedir(dirsp); + goto free_strings; + } + + dirs = (struct squashfs_dir_stream *)dirsp; + + while (!sqfs_readdir(dirsp, &dent)) { + ret = strcmp(dent->name, file); + if (!ret) + break; + free(dirs->entry); + } + + if (ret) { + printf("File not found.\n"); + sqfs_closedir(dirsp); + goto free_strings; + } + + i_number = dirs->dir_header->inode_number + dirs->entry->inode_offset; + ipos = sqfs_find_inode(dirs->inode_table, i_number, sblk->inodes, + sblk->block_size); + free(dirs->entry); + + memcpy(&base, ipos, sizeof(base)); + switch (base.inode_type) { + case SQFS_REG_TYPE: + memcpy(®, ipos, sizeof(reg)); + *size = reg.file_size; + break; + case SQFS_LREG_TYPE: + memcpy(&lreg, ipos, sizeof(lreg)); + *size = lreg.file_size; + break; + case SQFS_SYMLINK_TYPE: + case SQFS_LSYMLINK_TYPE: + memcpy(&symlink, ipos, sizeof(symlink)); + resolved = sqfs_resolve_symlink(&symlink, ipos, filename); + ret = sqfs_size(resolved, size); + free(resolved); + break; + case SQFS_BLKDEV_TYPE: + case SQFS_CHRDEV_TYPE: + case SQFS_LBLKDEV_TYPE: + case SQFS_LCHRDEV_TYPE: + case SQFS_FIFO_TYPE: + case SQFS_SOCKET_TYPE: + case SQFS_LFIFO_TYPE: + case SQFS_LSOCKET_TYPE: + default: + printf("Unable to recover entry's size.\n"); + *size = 0; + ret = -EINVAL; + break; + } + +free_strings: + free(dir); + free(file); + + sqfs_closedir(dirsp); + + return ret; +} + +void sqfs_close(void) +{ + free(ctxt.sblk); + ctxt.cur_dev = NULL; +} + +void sqfs_closedir(struct fs_dir_stream *dirs) +{ + struct squashfs_dir_stream *sqfs_dirs; + + sqfs_dirs = (struct squashfs_dir_stream *)dirs; + free(sqfs_dirs->inode_table); + free(sqfs_dirs->dir_table); + free(sqfs_dirs->dir_header); +} diff --git a/fs/squashfs/sqfs_decompressor.c b/fs/squashfs/sqfs_decompressor.c new file mode 100644 index 0000000000..a899a5704b --- /dev/null +++ b/fs/squashfs/sqfs_decompressor.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Bootlin + * + * Author: Joao Marcos Costa joaomarcos.costa@bootlin.com + */ + +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "sqfs_decompressor.h" +#include "sqfs_filesystem.h" +#include "sqfs_utils.h" + +int sqfs_decompress(u16 comp_type, void *dest, unsigned long *dest_len, + void *source, u32 lenp) +{ + int ret = 0; + + switch (comp_type) { + default: + printf("Error: unknown compression type.\n"); + return -EINVAL; + } + + return ret; +} diff --git a/fs/squashfs/sqfs_decompressor.h b/fs/squashfs/sqfs_decompressor.h new file mode 100644 index 0000000000..378965dda8 --- /dev/null +++ b/fs/squashfs/sqfs_decompressor.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Bootlin + * + * Author: Joao Marcos Costa joaomarcos.costa@bootlin.com + */ + +#ifndef SQFS_DECOMPRESSOR_H +#define SQFS_DECOMPRESSOR_H + +#include <stdint.h> + +#define SQFS_COMP_ZLIB 1 +#define SQFS_COMP_LZMA 2 +#define SQFS_COMP_LZO 3 +#define SQFS_COMP_XZ 4 +#define SQFS_COMP_LZ4 5 +#define SQFS_COMP_ZSTD 6 + +/* LZMA does not support any compression options */ + +struct squashfs_gzip_opts { + u32 compression_level; + u16 window_size; + u16 strategies; +}; + +struct squashfs_xz_opts { + u32 dictionary_size; + u32 executable_filters; +}; + +struct squashfs_lz4_opts { + u32 version; + u32 flags; +}; + +struct squashfs_zstd_opts { + u32 compression_level; +}; + +struct squashfs_lzo_opts { + u32 algorithm; + u32 level; +}; + +union squashfs_compression_opts { + struct squashfs_gzip_opts *gzip; + struct squashfs_xz_opts *xz; + struct squashfs_lz4_opts *lz4; + struct squashfs_zstd_opts *zstd; + struct squashfs_lzo_opts *lzo; +}; + +int sqfs_decompress(u16 comp_type, void *dest, unsigned long *dest_len, + void *source, u32 lenp); + +#endif /* SQFS_DECOMPRESSOR_H */ diff --git a/fs/squashfs/sqfs_dir.c b/fs/squashfs/sqfs_dir.c new file mode 100644 index 0000000000..4a72191f88 --- /dev/null +++ b/fs/squashfs/sqfs_dir.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Bootlin + * + * Author: Joao Marcos Costa joaomarcos.costa@bootlin.com + */ + +#include <errno.h> +#include <linux/types.h> +#include <linux/byteorder/little_endian.h> +#include <linux/byteorder/generic.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "sqfs_filesystem.h" +#include "sqfs_utils.h" + +bool sqfs_is_dir(__le16 type) +{ + return (le16_to_cpu(type) == SQFS_DIR_TYPE) || + (le16_to_cpu(type) == SQFS_LDIR_TYPE); +} + +/* + * Receives a pointer (void *) to a position in the inode table containing the + * directory's inode. Returns directory inode offset into the directory table. + * m_list contains each metadata block's position, and m_count is the number of + * elements of m_list. Those metadata blocks come from the compressed directory + * table. + */ +int sqfs_dir_offset(void *dir_i, u32 *m_list, int m_count) +{ + struct squashfs_base_inode base; + struct squashfs_ldir_inode ldir; + struct squashfs_dir_inode dir; + u32 start_block; + u16 offset; + int j; + + memcpy(&base, dir_i, sizeof(base)); + + switch (base.inode_type) { + case SQFS_DIR_TYPE: + memcpy(&dir, dir_i, sizeof(dir)); + start_block = le32_to_cpu(dir.start_block); + offset = le16_to_cpu(dir.offset); + break; + case SQFS_LDIR_TYPE: + memcpy(&ldir, dir_i, sizeof(ldir)); + start_block = le32_to_cpu(ldir.start_block); + offset = le16_to_cpu(ldir.offset); + break; + default: + printf("Error: this is not a directory.\n"); + return -EINVAL; + } + + for (j = 0; j < m_count; j++) { + if (m_list[j] == start_block) + return (++j * SQFS_METADATA_BLOCK_SIZE) + offset; + } + + if (start_block == 0) + return offset; + + printf("Error: invalid inode reference to directory table.\n"); + + return -EINVAL; +} + +bool sqfs_is_empty_dir(void *dir_i) +{ + struct squashfs_base_inode *base; + struct squashfs_ldir_inode *ldir; + struct squashfs_dir_inode *dir; + u32 file_size; + + base = malloc(sizeof(*base)); + if (!base) + return errno; + + memcpy(base, dir_i, sizeof(*base)); + + switch (le16_to_cpu(base->inode_type)) { + case SQFS_DIR_TYPE: + dir = malloc(sizeof(*dir)); + memcpy(dir, dir_i, sizeof(*dir)); + file_size = le16_to_cpu(dir->file_size); + free(dir); + break; + case SQFS_LDIR_TYPE: + ldir = malloc(sizeof(*ldir)); + memcpy(ldir, dir_i, sizeof(*ldir)); + file_size = le32_to_cpu(ldir->file_size); + free(ldir); + break; + default: + printf("Error: this is not a directory.\n"); + free(base); + return false; + } + + free(base); + + return file_size == SQFS_EMPTY_FILE_SIZE; +} diff --git a/fs/squashfs/sqfs_filesystem.h b/fs/squashfs/sqfs_filesystem.h new file mode 100644 index 0000000000..cbdaa9f1e4 --- /dev/null +++ b/fs/squashfs/sqfs_filesystem.h @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Bootlin + * + * Author: Joao Marcos Costa joaomarcos.costa@bootlin.com + */ + +#ifndef SQFS_FILESYSTEM_H +#define SQFS_FILESYSTEM_H + +#include <asm/unaligned.h> +#include <stdint.h> +#include <fs.h> + +#define SQFS_UNCOMPRESSED_DATA 0x0002 +#define SQFS_MAGIC_NUMBER 0x73717368 +/* The three first members of squashfs_dir_index make a total of 12 bytes */ +#define SQFS_DIR_INDEX_BASE_LENGTH 12 +/* size of metadata (inode and directory) blocks */ +#define SQFS_METADATA_BLOCK_SIZE 8192 +/* Max. number of fragment entries in a metadata block is 512 */ +#define SQFS_MAX_ENTRIES 512 +/* Metadata blocks start by a 2-byte length header */ +#define SQFS_HEADER_SIZE 2 +#define SQFS_LREG_INODE_MIN_SIZE 56 +#define SQFS_DIR_HEADER_SIZE 12 +#define SQFS_MISC_ENTRY_TYPE -1 +#define SQFS_EMPTY_FILE_SIZE 3 +#define SQFS_STOP_READDIR 1 +#define SQFS_EMPTY_DIR -1 +/* + * A directory entry object has a fixed length of 8 bytes, corresponding to its + * first four members, plus the size of the entry name, which is equal to + * 'entry_name' + 1 bytes. + */ +#define SQFS_ENTRY_BASE_LENGTH 8 +/* Inode types */ +#define SQFS_DIR_TYPE 1 +#define SQFS_REG_TYPE 2 +#define SQFS_SYMLINK_TYPE 3 +#define SQFS_BLKDEV_TYPE 4 +#define SQFS_CHRDEV_TYPE 5 +#define SQFS_FIFO_TYPE 6 +#define SQFS_SOCKET_TYPE 7 +#define SQFS_LDIR_TYPE 8 +#define SQFS_LREG_TYPE 9 +#define SQFS_LSYMLINK_TYPE 10 +#define SQFS_LBLKDEV_TYPE 11 +#define SQFS_LCHRDEV_TYPE 12 +#define SQFS_LFIFO_TYPE 13 +#define SQFS_LSOCKET_TYPE 14 + +struct squashfs_super_block { + __le32 s_magic; + __le32 inodes; + __le32 mkfs_time; + __le32 block_size; + __le32 fragments; + __le16 compression; + __le16 block_log; + __le16 flags; + __le16 no_ids; + __le16 s_major; + __le16 s_minor; + __le64 root_inode; + __le64 bytes_used; + __le64 id_table_start; + __le64 xattr_id_table_start; + __le64 inode_table_start; + __le64 directory_table_start; + __le64 fragment_table_start; + __le64 export_table_start; +}; + +struct squashfs_directory_index { + u32 index; + u32 start; + u32 size; + char name[0]; +}; + +struct squashfs_base_inode { + __le16 inode_type; + __le16 mode; + __le16 uid; + __le16 guid; + __le32 mtime; + __le32 inode_number; +}; + +struct squashfs_ipc_inode { + __le16 inode_type; + __le16 mode; + __le16 uid; + __le16 guid; + __le32 mtime; + __le32 inode_number; + __le32 nlink; +}; + +struct squashfs_lipc_inode { + __le16 inode_type; + __le16 mode; + __le16 uid; + __le16 guid; + __le32 mtime; + __le32 inode_number; + __le32 nlink; + __le32 xattr; +}; + +struct squashfs_dev_inode { + __le16 inode_type; + __le16 mode; + __le16 uid; + __le16 guid; + __le32 mtime; + __le32 inode_number; + __le32 nlink; + __le32 rdev; +}; + +struct squashfs_ldev_inode { + __le16 inode_type; + __le16 mode; + __le16 uid; + __le16 guid; + __le32 mtime; + __le32 inode_number; + __le32 nlink; + __le32 rdev; + __le32 xattr; +}; + +struct squashfs_symlink_inode { + __le16 inode_type; + __le16 mode; + __le16 uid; + __le16 guid; + __le32 mtime; + __le32 inode_number; + __le32 nlink; + __le32 symlink_size; + char symlink[0]; +}; + +struct squashfs_reg_inode { + __le16 inode_type; + __le16 mode; + __le16 uid; + __le16 guid; + __le32 mtime; + __le32 inode_number; + __le32 start_block; + __le32 fragment; + __le32 offset; + __le32 file_size; + __le32 block_list[0]; +}; + +struct squashfs_lreg_inode { + __le16 inode_type; + __le16 mode; + __le16 uid; + __le16 guid; + __le32 mtime; + __le32 inode_number; + __le64 start_block; + __le64 file_size; + __le64 sparse; + __le32 nlink; + __le32 fragment; + __le32 offset; + __le32 xattr; + __le32 block_list[0]; +}; + +struct squashfs_dir_inode { + __le16 inode_type; + __le16 mode; + __le16 uid; + __le16 guid; + __le32 mtime; + __le32 inode_number; + __le32 start_block; + __le32 nlink; + __le16 file_size; + __le16 offset; + __le32 parent_inode; +}; + +struct squashfs_ldir_inode { + __le16 inode_type; + __le16 mode; + __le16 uid; + __le16 guid; + __le32 mtime; + __le32 inode_number; + __le32 nlink; + __le32 file_size; + __le32 start_block; + __le32 parent_inode; + __le16 i_count; + __le16 offset; + __le32 xattr; + struct squashfs_directory_index index[0]; +}; + +union squashfs_inode { + struct squashfs_base_inode *base; + struct squashfs_dev_inode *dev; + struct squashfs_ldev_inode *ldev; + struct squashfs_symlink_inode *symlink; + struct squashfs_reg_inode *reg; + struct squashfs_lreg_inode *lreg; + struct squashfs_dir_inode *dir; + struct squashfs_ldir_inode *ldir; + struct squashfs_ipc_inode *ipc; + struct squashfs_lipc_inode *lipc; +}; + +struct squashfs_directory_entry { + u16 offset; + u16 inode_offset; + u16 type; + u16 name_size; + char name[0]; +}; + +struct squashfs_directory_header { + u32 count; + u32 start; + u32 inode_number; +}; + +struct squashfs_fragment_block_entry { + u64 start; + u32 size; + u32 _unused; +}; + +struct squashfs_dir_stream { + struct fs_dir_stream fs_dirs; + struct fs_dirent dentp; + /* + * 'size' is the uncompressed size of the entire listing, including + * headers. 'entry_count' is the number of entries following a + * specific header. Both variables are decremented in sqfs_readdir() so + * the function knows when the end of the directory is reached. + */ + size_t size; + int entry_count; + /* SquashFS structures */ + struct squashfs_directory_header *dir_header; + struct squashfs_directory_entry *entry; + /* + * 'table' points to a position into the directory table. Both 'table' + * and 'inode' are defined for the first time in sqfs_opendir(). + * 'table's value changes in sqfs_readdir(). + */ + unsigned char *table; + union squashfs_inode i; + struct squashfs_dir_inode i_dir; + struct squashfs_ldir_inode i_ldir; + /* + * References to the tables' beginnings. They are assigned in + * sqfs_opendir() and freed in sqfs_closedir(). + */ + unsigned char *inode_table; + unsigned char *dir_table; +}; + +struct squashfs_file_info { + /* File size in bytes (uncompressed) */ + size_t size; + /* Reference to list of data blocks's sizes */ + u32 *blk_sizes; + /* Offset into the fragment block */ + u32 offset; + /* Offset in which the data blocks begin */ + u64 start; + /* Is file fragmented? */ + bool frag; + /* Compressed fragment */ + bool comp; +}; + +void *sqfs_find_inode(void *inode_table, int inode_number, __le32 inode_count, + __le32 block_size); + +int sqfs_dir_offset(void *dir_i, u32 *m_list, int m_count); + +int sqfs_read_metablock(unsigned char *file_mapping, int offset, + bool *compressed, u32 *data_size); + +bool sqfs_is_empty_dir(void *dir_i); + +bool sqfs_is_dir(__le16 type); + +#endif /* SQFS_FILESYSTEM_H */ diff --git a/fs/squashfs/sqfs_inode.c b/fs/squashfs/sqfs_inode.c new file mode 100644 index 0000000000..dbde74a750 --- /dev/null +++ b/fs/squashfs/sqfs_inode.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Bootlin + * + * Author: Joao Marcos Costa joaomarcos.costa@bootlin.com + */ + +#include <asm/unaligned.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "sqfs_decompressor.h" +#include "sqfs_filesystem.h" +#include "sqfs_utils.h" + +/* + * Given the uncompressed inode table, the inode to be found and the number of + * inodes in the table, return inode position in case of success. + */ +void *sqfs_find_inode(void *inode_table, int inode_number, __le32 inode_count, + __le32 block_size) +{ + int k, l, blk_list_size = 0, offset = 0, index_list_size = 0; + struct squashfs_symlink_inode symlink; + struct squashfs_base_inode base; + struct squashfs_ldev_inode ldev; + struct squashfs_lreg_inode lreg; + struct squashfs_ldir_inode ldir; + struct squashfs_lipc_inode lipc; + struct squashfs_dev_inode dev; + struct squashfs_reg_inode reg; + struct squashfs_dir_inode dir; + struct squashfs_ipc_inode ipc; + + if (!inode_table) { + printf("%s: Invalid pointer to inode table.\n", __func__); + return NULL; + } + + for (k = 0; k < le32_to_cpu(inode_count); k++) { + memcpy(&base, inode_table + offset, sizeof(base)); + if (le32_to_cpu(base.inode_number) == inode_number) + return (void *)(inode_table + offset); + + switch (base.inode_type) { + case SQFS_DIR_TYPE: + memcpy(&dir, inode_table + offset, sizeof(dir)); + offset += sizeof(dir); + break; + case SQFS_REG_TYPE: + memcpy(®, inode_table + offset, sizeof(reg)); + if (SQFS_IS_FRAGMENTED(reg.fragment)) { + blk_list_size = (reg.file_size / block_size); + } else { + blk_list_size = DIV_ROUND_UP(reg.file_size, + block_size); + } + + offset += sizeof(reg) + blk_list_size * sizeof(u32); + break; + case SQFS_LDIR_TYPE: + memcpy(&ldir, inode_table + offset, sizeof(ldir)); + if (ldir.i_count == 0) { + offset += sizeof(ldir); + break; + } + + for (l = 0; l < ldir.i_count + 1; l++) + index_list_size += ldir.index[l].size + 1; + + offset += sizeof(ldir) + index_list_size + + (ldir.i_count + 1) * SQFS_DIR_INDEX_BASE_LENGTH; + index_list_size = 0; + break; + case SQFS_LREG_TYPE: + memcpy(&lreg, inode_table + offset, sizeof(lreg)); + if (lreg.fragment == 0xFFFFFFFF) { + blk_list_size = DIV_ROUND_UP(lreg.file_size, + block_size); + } else { + blk_list_size = (lreg.file_size / block_size); + } + + offset += sizeof(lreg) + blk_list_size * sizeof(u32); + break; + case SQFS_SYMLINK_TYPE: + case SQFS_LSYMLINK_TYPE: + memcpy(&symlink, inode_table + offset, sizeof(symlink)); + offset += sizeof(symlink) + symlink.symlink_size; + break; + case SQFS_BLKDEV_TYPE: + case SQFS_CHRDEV_TYPE: + memcpy(&dev, inode_table + offset, sizeof(dev)); + offset += sizeof(dev); + break; + case SQFS_LBLKDEV_TYPE: + case SQFS_LCHRDEV_TYPE: + memcpy(&ldev, inode_table + offset, sizeof(ldev)); + offset += sizeof(ldev); + break; + case SQFS_FIFO_TYPE: + case SQFS_SOCKET_TYPE: + memcpy(&ipc, inode_table + offset, sizeof(ipc)); + offset += sizeof(ipc); + break; + case SQFS_LFIFO_TYPE: + case SQFS_LSOCKET_TYPE: + memcpy(&lipc, inode_table + offset, sizeof(lipc)); + offset += sizeof(lipc); + break; + default: + printf("Error while searching inode: unknown type.\n"); + return NULL; + } + } + + printf("Inode not found.\n"); + + return NULL; +} + +int sqfs_read_metablock(unsigned char *file_mapping, int offset, + bool *compressed, u32 *data_size) +{ + unsigned char *data; + u16 header; + + data = file_mapping + offset; + header = get_unaligned((u16 *)data); + *compressed = SQFS_COMPRESSED_METADATA(header); + *data_size = SQFS_METADATA_SIZE(header); + + if (*data_size > SQFS_METADATA_BLOCK_SIZE) { + printf("Invalid metatada block size: %d bytes.\n", *data_size); + return -EINVAL; + } + + return 0; +} diff --git a/fs/squashfs/sqfs_utils.h b/fs/squashfs/sqfs_utils.h new file mode 100644 index 0000000000..1260abe22b --- /dev/null +++ b/fs/squashfs/sqfs_utils.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Bootlin + * + * Author: Joao Marcos Costa joaomarcos.costa@bootlin.com + */ + +#ifndef SQFS_UTILS_H +#define SQFS_UTILS_H + +#include <linux/bitops.h> +#include <linux/kernel.h> +#include <stdbool.h> + +#define SQFS_FRAGMENT_INDEX_OFFSET(A) ((A) % SQFS_MAX_ENTRIES) +#define SQFS_FRAGMENT_INDEX(A) ((A) / SQFS_MAX_ENTRIES) +#define SQFS_BLOCK_SIZE(A) ((A) & GENMASK(23, 0)) +#define SQFS_CHECK_FLAG(flag, bit) (((flag) >> (bit)) & 1) +/* Useful for both fragment and data blocks */ +#define SQFS_COMPRESSED_BLOCK(A) (!((A) & BIT(24))) +/* SQFS_COMPRESSED_DATA strictly used with super block's 'flags' member */ +#define SQFS_COMPRESSED_DATA(A) (!((A) & 0x0002)) +#define SQFS_IS_FRAGMENTED(A) ((A) != 0xFFFFFFFF) +/* + * These two macros work as getters for a metada block header, retrieving the + * data size and if it is compressed/uncompressed + */ +#define SQFS_COMPRESSED_METADATA(A) (!((A) & BIT(15))) +#define SQFS_METADATA_SIZE(A) ((A) & GENMASK(14, 0)) + +struct squashfs_super_block_flags { + /* check: unused + * uncompressed_ids: not supported + */ + bool uncompressed_inodes; + bool uncompressed_data; + bool check; + bool uncompressed_frags; + bool no_frags; + bool always_frags; + bool duplicates; + bool exportable; + bool uncompressed_xattrs; + bool no_xattrs; + bool compressor_options; + bool uncompressed_ids; +}; + +#endif /* SQFS_UTILS_H */ diff --git a/include/fs.h b/include/fs.h index b08b1f40c5..0794b50d10 100644 --- a/include/fs.h +++ b/include/fs.h @@ -15,6 +15,7 @@ struct cmd_tbl; #define FS_TYPE_SANDBOX 3 #define FS_TYPE_UBIFS 4 #define FS_TYPE_BTRFS 5 +#define FS_TYPE_SQUASHFS 6
struct blk_desc;
diff --git a/include/squashfs.h b/include/squashfs.h new file mode 100644 index 0000000000..819cf8c2da --- /dev/null +++ b/include/squashfs.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2020 Bootlin + * + * Author: Joao Marcos Costa joaomarcos.costa@bootlin.com + * + * squashfs.h: SquashFS filesystem implementation. + */ + +#ifndef _SQFS_H_ +#define _SQFS_H_ + +struct disk_partition; + +int sqfs_opendir(const char *filename, struct fs_dir_stream **dirsp); +int sqfs_readdir(struct fs_dir_stream *dirs, struct fs_dirent **dentp); +int sqfs_probe(struct blk_desc *fs_dev_desc, + struct disk_partition *fs_partition); +int sqfs_read(const char *filename, void *buf, loff_t offset, + loff_t len, loff_t *actread); +int sqfs_size(const char *filename, loff_t *size); +void sqfs_close(void); +void sqfs_closedir(struct fs_dir_stream *dirs); + +#endif /* SQFS_H */

Add 'ls' and 'load' commands.
Signed-off-by: Joao Marcos Costa joaomarcos.costa@bootlin.com --- Changes in v3: - No changes since v2. Changes in v2: - Add info. to MAINTAINERS - Add 'static' keyword to do_sqfs_*(). These functions' prototypes are no longer needed in include/fs.h. - Change commit message. MAINTAINERS | 1 + cmd/Kconfig | 6 ++++++ cmd/Makefile | 1 + cmd/sqfs.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 cmd/sqfs.c
diff --git a/MAINTAINERS b/MAINTAINERS index 198fe2b89e..9a5f330148 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -949,6 +949,7 @@ M: Joao Marcos Costa joaomarcos.costa@bootlin.com S: Maintained F: fs/squashfs/ F: include/sqfs.h +F: cmd/sqfs.c
TDA19988 HDMI ENCODER M: Liviu Dudau liviu.dudau@foss.arm.com diff --git a/cmd/Kconfig b/cmd/Kconfig index bfe6c163dc..c8f3d34b88 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -2059,6 +2059,12 @@ config CMD_FAT help Support for the FAT fs
+config CMD_SQUASHFS + bool "SquashFS command support" + select FS_SQUASHFS + help + Enables SquashFS filesystem commands (e.g. load, ls). + config CMD_FS_GENERIC bool "filesystem commands" help diff --git a/cmd/Makefile b/cmd/Makefile index 7952138dc2..ef2da1c928 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_CMD_EXT4) += ext4.o obj-$(CONFIG_CMD_EXT2) += ext2.o obj-$(CONFIG_CMD_FAT) += fat.o obj-$(CONFIG_CMD_FDT) += fdt.o +obj-$(CONFIG_CMD_SQUASHFS) += sqfs.o obj-$(CONFIG_CMD_FLASH) += flash.o obj-$(CONFIG_CMD_FPGA) += fpga.o obj-$(CONFIG_CMD_FPGAD) += fpgad.o diff --git a/cmd/sqfs.c b/cmd/sqfs.c new file mode 100644 index 0000000000..107038c4cf --- /dev/null +++ b/cmd/sqfs.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Bootlin + * + * Author: Joao Marcos Costa joaomarcos.costa@bootlin.com + * + * squashfs.c: implements SquashFS related commands + */ + +#include <command.h> +#include <fs.h> +#include <squashfs.h> + +static int do_sqfs_ls(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + return do_ls(cmdtp, flag, argc, argv, FS_TYPE_SQUASHFS); +} + +U_BOOT_CMD(sqfsls, 4, 1, do_sqfs_ls, + "List files in directory. Default: root (/).", + "<interface> [<dev[:part]>] [directory]\n" + " - list files from 'dev' on 'interface' in 'directory'\n" +); + +static int do_sqfs_load(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[]) +{ + return do_load(cmdtp, flag, argc, argv, FS_TYPE_SQUASHFS); +} + +U_BOOT_CMD(sqfsload, 7, 0, do_sqfs_load, + "load binary file from a SquashFS filesystem", + "<interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]\n" + " - Load binary file 'filename' from 'dev' on 'interface'\n" + " to address 'addr' from SquashFS filesystem.\n" + " 'pos' gives the file position to start loading from.\n" + " If 'pos' is omitted, 0 is used. 'pos' requires 'bytes'.\n" + " 'bytes' gives the size to load. If 'bytes' is 0 or omitted,\n" + " the load stops on end of file.\n" + " If either 'pos' or 'bytes' are not aligned to\n" + " ARCH_DMA_MINALIGN then a misaligned buffer warning will\n" + " be printed and performance will suffer for the load." +);

Add zlib (v1.2.11) uncompr() function to U-Boot. SquashFS depends on this function to decompress data from a raw disk image. The actual support for zlib into SquashFS sources will be added in a follow-up commit.
Signed-off-by: Joao Marcos Costa joaomarcos.costa@bootlin.com --- Changes in v3: - No changes since v2. Changes in v2: - Changed commit title and message, which were wrong and/or misleading in v1. include/u-boot/zlib.h | 32 ++++++++++++++ lib/zlib/uncompr.c | 97 +++++++++++++++++++++++++++++++++++++++++++ lib/zlib/zlib.c | 1 + 3 files changed, 130 insertions(+) create mode 100644 lib/zlib/uncompr.c
diff --git a/include/u-boot/zlib.h b/include/u-boot/zlib.h index e23ceb50ca..a33cc8780d 100644 --- a/include/u-boot/zlib.h +++ b/include/u-boot/zlib.h @@ -110,6 +110,12 @@ extern "C" { # define voidp z_voidp #endif
+#if defined(ZLIB_CONST) && !defined(z_const) +# define z_const const +#else +# define z_const +#endif + #if defined(__MSDOS__) && !defined(MSDOS) # define MSDOS #endif @@ -710,6 +716,32 @@ ZEXTERN uInt ZEXPORT crc32 OF((uInt crc, const Bytef *buf, uInt len)); if (crc != original_crc) error(); */
+ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total size + of the destination buffer, which must be large enough to hold the entire + uncompressed data. (The size of the uncompressed data must have been saved + previously by the compressor and transmitted to the decompressor by some + mechanism outside the scope of this compression library.) Upon exit, destLen + is the actual size of the uncompressed data. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. In + the case where there is not enough room, uncompress() will fill the output + buffer with the uncompressed data up to that point. +*/ + +ZEXTERN int ZEXPORT uncompress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong *sourceLen)); +/* + Same as uncompress, except that sourceLen is a pointer, where the + length of the source is *sourceLen. On return, *sourceLen is the number of + source bytes consumed. +*/ + ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, const char *version, int stream_size)); #define inflateInit(strm) \ diff --git a/lib/zlib/uncompr.c b/lib/zlib/uncompr.c new file mode 100644 index 0000000000..21e93933b2 --- /dev/null +++ b/lib/zlib/uncompr.c @@ -0,0 +1,97 @@ +/* uncompr.c -- decompress a memory buffer + * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +/* =========================================================================== + Decompresses the source buffer into the destination buffer. *sourceLen is + the byte length of the source buffer. Upon entry, *destLen is the total size + of the destination buffer, which must be large enough to hold the entire + uncompressed data. (The size of the uncompressed data must have been saved + previously by the compressor and transmitted to the decompressor by some + mechanism outside the scope of this compression library.) Upon exit, + *destLen is the size of the decompressed data and *sourceLen is the number + of source bytes consumed. Upon return, source + *sourceLen points to the + first unused input byte. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, or + Z_DATA_ERROR if the input data was corrupted, including if the input data is + an incomplete zlib stream. +*/ +int ZEXPORT uncompress2(dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong *sourceLen; + +{ + z_stream stream; + int err; + const uInt max = (uInt)-1; + uLong len, left; + /* for detection of incomplete stream when *destLen == 0 */ + Byte buf[1]; + + len = *sourceLen; + if (*destLen) { + left = *destLen; + *destLen = 0; + } else { + left = 1; + dest = buf; + } + + stream.next_in = (z_const Bytef *)source; + stream.avail_in = 0; + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + err = inflateInit(&stream); + if (err != Z_OK) + return err; + + stream.next_out = dest; + stream.avail_out = 0; + + do { + if (stream.avail_out == 0) { + stream.avail_out = left > (uLong)max ? max : (uInt)left; + left -= stream.avail_out; + } + + if (stream.avail_in == 0) { + stream.avail_in = len > (uLong)max ? max : (uInt)len; + len -= stream.avail_in; + } + + err = inflate(&stream, Z_NO_FLUSH); + } while (err == Z_OK); + + *sourceLen -= len + stream.avail_in; + if (dest != buf) + *destLen = stream.total_out; + else if (stream.total_out && err == Z_BUF_ERROR) + left = 1; + + inflateEnd(&stream); + return err == Z_STREAM_END ? Z_OK : + err == Z_NEED_DICT ? Z_DATA_ERROR : + err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR : + err; +} + +int ZEXPORT uncompress(dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong sourceLen; +{ + return uncompress2(dest, destLen, source, &sourceLen); +} diff --git a/lib/zlib/zlib.c b/lib/zlib/zlib.c index 7e1570292c..05a2734086 100644 --- a/lib/zlib/zlib.c +++ b/lib/zlib/zlib.c @@ -30,3 +30,4 @@ #include "inflate.c" #include "zutil.c" #include "adler32.c" +#include "uncompr.c"

On Tue, Jul 21, 2020 at 11:22:57AM +0200, Joao Marcos Costa wrote:
Add zlib (v1.2.11) uncompr() function to U-Boot. SquashFS depends on this function to decompress data from a raw disk image. The actual support for zlib into SquashFS sources will be added in a follow-up commit.
Signed-off-by: Joao Marcos Costa joaomarcos.costa@bootlin.com
Changes in v3:
- No changes since v2.
Changes in v2:
- Changed commit title and message, which were wrong and/or misleading
in v1. include/u-boot/zlib.h | 32 ++++++++++++++ lib/zlib/uncompr.c | 97 +++++++++++++++++++++++++++++++++++++++++++ lib/zlib/zlib.c | 1 + 3 files changed, 130 insertions(+) create mode 100644 lib/zlib/uncompr.c
Can you confirm that on a platform where we aren't enabling squashfs the new zlib functionality ends up being discarded at link time? Thanks!

On Wed, 29 Jul 2020 11:27:45 -0400 Tom Rini trini@konsulko.com wrote:
On Tue, Jul 21, 2020 at 11:22:57AM +0200, Joao Marcos Costa wrote:
Add zlib (v1.2.11) uncompr() function to U-Boot. SquashFS depends on this function to decompress data from a raw disk image. The actual support for zlib into SquashFS sources will be added in a follow-up commit.
Signed-off-by: Joao Marcos Costa joaomarcos.costa@bootlin.com
Changes in v3:
- No changes since v2.
Changes in v2:
- Changed commit title and message, which were wrong and/or
misleading in v1. include/u-boot/zlib.h | 32 ++++++++++++++ lib/zlib/uncompr.c | 97 +++++++++++++++++++++++++++++++++++++++++++ lib/zlib/zlib.c | 1 + 3 files changed, 130 insertions(+) create mode 100644 lib/zlib/uncompr.c
Can you confirm that on a platform where we aren't enabling squashfs the new zlib functionality ends up being discarded at link time? Thanks!
I dumped lib/zlib/zlib.o with readelf and the new functionality is not discarded, even though the SquashFS support is not enabled, but this issue will be fixed in the next version (v4). Thank you!

On Wed, Jul 29, 2020 at 06:00:57PM +0200, Joao Marcos Costa wrote:
On Wed, 29 Jul 2020 11:27:45 -0400 Tom Rini trini@konsulko.com wrote:
On Tue, Jul 21, 2020 at 11:22:57AM +0200, Joao Marcos Costa wrote:
Add zlib (v1.2.11) uncompr() function to U-Boot. SquashFS depends on this function to decompress data from a raw disk image. The actual support for zlib into SquashFS sources will be added in a follow-up commit.
Signed-off-by: Joao Marcos Costa joaomarcos.costa@bootlin.com
Changes in v3:
- No changes since v2.
Changes in v2:
- Changed commit title and message, which were wrong and/or
misleading in v1. include/u-boot/zlib.h | 32 ++++++++++++++ lib/zlib/uncompr.c | 97 +++++++++++++++++++++++++++++++++++++++++++ lib/zlib/zlib.c | 1 + 3 files changed, 130 insertions(+) create mode 100644 lib/zlib/uncompr.c
Can you confirm that on a platform where we aren't enabling squashfs the new zlib functionality ends up being discarded at link time? Thanks!
I dumped lib/zlib/zlib.o with readelf and the new functionality is not discarded, even though the SquashFS support is not enabled, but this issue will be fixed in the next version (v4). Thank you!
Well, don't dump lib/zlib/zlib.o, that won't say. Check u-boot.map :)

On Wed, 29 Jul 2020 12:54:23 -0400 Tom Rini trini@konsulko.com wrote:
On Wed, Jul 29, 2020 at 06:00:57PM +0200, Joao Marcos Costa wrote:
On Wed, 29 Jul 2020 11:27:45 -0400 Tom Rini trini@konsulko.com wrote:
On Tue, Jul 21, 2020 at 11:22:57AM +0200, Joao Marcos Costa wrote:
Add zlib (v1.2.11) uncompr() function to U-Boot. SquashFS depends on this function to decompress data from a raw disk image. The actual support for zlib into SquashFS sources will be added in a follow-up commit.
Signed-off-by: Joao Marcos Costa joaomarcos.costa@bootlin.com
Changes in v3:
- No changes since v2.
Changes in v2:
- Changed commit title and message, which were wrong
and/or misleading in v1. include/u-boot/zlib.h | 32 ++++++++++++++ lib/zlib/uncompr.c | 97 +++++++++++++++++++++++++++++++++++++++++++ lib/zlib/zlib.c | 1 + 3 files changed, 130 insertions(+) create mode 100644 lib/zlib/uncompr.c
Can you confirm that on a platform where we aren't enabling squashfs the new zlib functionality ends up being discarded at link time? Thanks!
I dumped lib/zlib/zlib.o with readelf and the new functionality is not discarded, even though the SquashFS support is not enabled, but this issue will be fixed in the next version (v4). Thank you!
Well, don't dump lib/zlib/zlib.o, that won't say. Check u-boot.map :)
Thanks for the tip! Now I can confirm that the functionality is discarded when squashfs is not enabled. I just would like to know if I should send the tests in the same patch as the SquashFS commands, or should it be into a separate patch?

On Thu, Jul 30, 2020 at 02:10:03PM +0200, Joao Marcos Costa wrote:
On Wed, 29 Jul 2020 12:54:23 -0400 Tom Rini trini@konsulko.com wrote:
On Wed, Jul 29, 2020 at 06:00:57PM +0200, Joao Marcos Costa wrote:
On Wed, 29 Jul 2020 11:27:45 -0400 Tom Rini trini@konsulko.com wrote:
On Tue, Jul 21, 2020 at 11:22:57AM +0200, Joao Marcos Costa wrote:
Add zlib (v1.2.11) uncompr() function to U-Boot. SquashFS depends on this function to decompress data from a raw disk image. The actual support for zlib into SquashFS sources will be added in a follow-up commit.
Signed-off-by: Joao Marcos Costa joaomarcos.costa@bootlin.com
Changes in v3:
- No changes since v2.
Changes in v2:
- Changed commit title and message, which were wrong
and/or misleading in v1. include/u-boot/zlib.h | 32 ++++++++++++++ lib/zlib/uncompr.c | 97 +++++++++++++++++++++++++++++++++++++++++++ lib/zlib/zlib.c | 1 + 3 files changed, 130 insertions(+) create mode 100644 lib/zlib/uncompr.c
Can you confirm that on a platform where we aren't enabling squashfs the new zlib functionality ends up being discarded at link time? Thanks!
I dumped lib/zlib/zlib.o with readelf and the new functionality is not discarded, even though the SquashFS support is not enabled, but this issue will be fixed in the next version (v4). Thank you!
Well, don't dump lib/zlib/zlib.o, that won't say. Check u-boot.map :)
Thanks for the tip! Now I can confirm that the functionality is discarded when squashfs is not enabled. I just would like to know if I should send the tests in the same patch as the SquashFS commands, or should it be into a separate patch?
A separate patch, and are you able to run them on sandbox then? So it should also enable squashfs on that target. Thanks!

On Thu, 30 Jul 2020 08:17:59 -0400 Tom Rini trini@konsulko.com wrote:
On Thu, Jul 30, 2020 at 02:10:03PM +0200, Joao Marcos Costa wrote:
On Wed, 29 Jul 2020 12:54:23 -0400 Tom Rini trini@konsulko.com wrote:
On Wed, Jul 29, 2020 at 06:00:57PM +0200, Joao Marcos Costa wrote:
On Wed, 29 Jul 2020 11:27:45 -0400 Tom Rini trini@konsulko.com wrote:
On Tue, Jul 21, 2020 at 11:22:57AM +0200, Joao Marcos Costa wrote:
Add zlib (v1.2.11) uncompr() function to U-Boot. SquashFS depends on this function to decompress data from a raw disk image. The actual support for zlib into SquashFS sources will be added in a follow-up commit.
Signed-off-by: Joao Marcos Costa joaomarcos.costa@bootlin.com --- Changes in v3:
- No changes since v2.
Changes in v2:
- Changed commit title and message, which were wrong
and/or misleading in v1. include/u-boot/zlib.h | 32 ++++++++++++++ lib/zlib/uncompr.c | 97 +++++++++++++++++++++++++++++++++++++++++++ lib/zlib/zlib.c | 1 + 3 files changed, 130 insertions(+) create mode 100644 lib/zlib/uncompr.c
Can you confirm that on a platform where we aren't enabling squashfs the new zlib functionality ends up being discarded at link time? Thanks!
I dumped lib/zlib/zlib.o with readelf and the new functionality is not discarded, even though the SquashFS support is not enabled, but this issue will be fixed in the next version (v4). Thank you!
Well, don't dump lib/zlib/zlib.o, that won't say. Check u-boot.map :)
Thanks for the tip! Now I can confirm that the functionality is discarded when squashfs is not enabled. I just would like to know if I should send the tests in the same patch as the SquashFS commands, or should it be into a separate patch?
A separate patch, and are you able to run them on sandbox then? So it should also enable squashfs on that target. Thanks!
This will be addressed in v4. Thanks!

Add call to zlib's 'uncompress' function. Add function to display the right error message depending on the decompression's return value.
Signed-off-by: Joao Marcos Costa joaomarcos.costa@bootlin.com --- Changes in v3: - No changes since v2. Changes in v2: - No changes since v1. fs/squashfs/sqfs_decompressor.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
diff --git a/fs/squashfs/sqfs_decompressor.c b/fs/squashfs/sqfs_decompressor.c index a899a5704b..a8c37f73b7 100644 --- a/fs/squashfs/sqfs_decompressor.c +++ b/fs/squashfs/sqfs_decompressor.c @@ -9,17 +9,41 @@ #include <stdint.h> #include <stdio.h> #include <stdlib.h> +#include <u-boot/zlib.h>
#include "sqfs_decompressor.h" #include "sqfs_filesystem.h" #include "sqfs_utils.h"
+static void zlib_decompression_status(int ret) +{ + switch (ret) { + case Z_BUF_ERROR: + printf("Error: 'dest' buffer is not large enough.\n"); + break; + case Z_DATA_ERROR: + printf("Error: corrupted compressed data.\n"); + break; + case Z_MEM_ERROR: + printf("Error: insufficient memory.\n"); + break; + } +} + int sqfs_decompress(u16 comp_type, void *dest, unsigned long *dest_len, void *source, u32 lenp) { int ret = 0;
switch (comp_type) { + case SQFS_COMP_ZLIB: + ret = uncompress(dest, dest_len, source, lenp); + if (ret) { + zlib_decompression_status(ret); + return -EINVAL; + } + + break; default: printf("Error: unknown compression type.\n"); return -EINVAL;

Adds an 'else if' statement inside the loop to check for symbolic links.
Signed-off-by: Joao Marcos Costa joaomarcos.costa@bootlin.com --- Changes in v3: - No changes since v2. Changes in v2: - This patch was added in v2 and did not exist in the last version. fs/fs.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/fs/fs.c b/fs/fs.c index 5b31a369f7..17e4bc33f7 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -59,6 +59,9 @@ static int fs_ls_generic(const char *dirname) if (dent->type == FS_DT_DIR) { printf(" %s/\n", dent->name); ndirs++; + } else if (dent->type == FS_DT_LNK) { + printf(" <SYM> %s\n", dent->name); + nfiles++; } else { printf(" %8lld %s\n", dent->size, dent->name); nfiles++;

On Tue, 21 Jul 2020 11:22:54 +0200 Joao Marcos Costa joaomarcos.costa@bootlin.com wrote:
Hello!
This series adds support for the SquashFS filesystem. For now, zlib is the only supported compression type. This is my first contribution to U-Boot as well as to a major Open Source project.
Changes in v3:
- Replace CONFIG_IS_ENABLED by IS_ENABLED in fs/fs.c
Changes in v2:
- Replace sqfs_ls() by U-Boot's fs_ls_generic()
- Add info. to MAINTAINERS
- Fix build failures
- Fix style problems
Best regards, Joao Marcos Costa
Joao Marcos Costa (5): fs/squashfs: new filesystem fs/squashfs: add filesystem commands include/u-boot, lib/zlib: add sources for zlib decompression fs/squashfs: add support for zlib decompression fs/fs.c: add symbolic link case to fs_ls_generic()
MAINTAINERS | 7 + cmd/Kconfig | 6 + cmd/Makefile | 1 + cmd/sqfs.c | 42 + common/spl/Kconfig | 9 + fs/Kconfig | 2 + fs/Makefile | 2 + fs/fs.c | 18 + fs/squashfs/Kconfig | 10 + fs/squashfs/Makefile | 7 + fs/squashfs/sqfs.c | 1521 +++++++++++++++++++++++++++++++ fs/squashfs/sqfs_decompressor.c | 53 ++ fs/squashfs/sqfs_decompressor.h | 58 ++ fs/squashfs/sqfs_dir.c | 107 +++ fs/squashfs/sqfs_filesystem.h | 300 ++++++ fs/squashfs/sqfs_inode.c | 142 +++ fs/squashfs/sqfs_utils.h | 49 + include/fs.h | 1 + include/squashfs.h | 25 + include/u-boot/zlib.h | 32 + lib/zlib/uncompr.c | 97 ++ lib/zlib/zlib.c | 1 + 22 files changed, 2490 insertions(+) create mode 100644 cmd/sqfs.c create mode 100644 fs/squashfs/Kconfig create mode 100644 fs/squashfs/Makefile create mode 100644 fs/squashfs/sqfs.c create mode 100644 fs/squashfs/sqfs_decompressor.c create mode 100644 fs/squashfs/sqfs_decompressor.h create mode 100644 fs/squashfs/sqfs_dir.c create mode 100644 fs/squashfs/sqfs_filesystem.h create mode 100644 fs/squashfs/sqfs_inode.c create mode 100644 fs/squashfs/sqfs_utils.h create mode 100644 include/squashfs.h create mode 100644 lib/zlib/uncompr.c
Hello again,
I am currently writing tests for the SquashFS support. I wrote two Python scripts to test 'ls' and 'load' commands, but I need a SquashFS image to do so, and I would like to know what is the better approach for this: - Add a compiled SquashFS image into the same directory as the tests, i.e.: test/py/tests/test_fs/test_squashfs. This is a +-7MB sized binary file. - Use the scripts to generate an image, with mksquashfs. However, mksquashfs becomes a dependency.
Best regards, Joao Marcos

On Wed, Jul 29, 2020 at 02:54:56PM +0200, Joao Marcos Costa wrote:
On Tue, 21 Jul 2020 11:22:54 +0200 Joao Marcos Costa joaomarcos.costa@bootlin.com wrote:
Hello!
This series adds support for the SquashFS filesystem. For now, zlib is the only supported compression type. This is my first contribution to U-Boot as well as to a major Open Source project.
Changes in v3:
- Replace CONFIG_IS_ENABLED by IS_ENABLED in fs/fs.c
Changes in v2:
- Replace sqfs_ls() by U-Boot's fs_ls_generic()
- Add info. to MAINTAINERS
- Fix build failures
- Fix style problems
Best regards, Joao Marcos Costa
Joao Marcos Costa (5): fs/squashfs: new filesystem fs/squashfs: add filesystem commands include/u-boot, lib/zlib: add sources for zlib decompression fs/squashfs: add support for zlib decompression fs/fs.c: add symbolic link case to fs_ls_generic()
MAINTAINERS | 7 + cmd/Kconfig | 6 + cmd/Makefile | 1 + cmd/sqfs.c | 42 + common/spl/Kconfig | 9 + fs/Kconfig | 2 + fs/Makefile | 2 + fs/fs.c | 18 + fs/squashfs/Kconfig | 10 + fs/squashfs/Makefile | 7 + fs/squashfs/sqfs.c | 1521 +++++++++++++++++++++++++++++++ fs/squashfs/sqfs_decompressor.c | 53 ++ fs/squashfs/sqfs_decompressor.h | 58 ++ fs/squashfs/sqfs_dir.c | 107 +++ fs/squashfs/sqfs_filesystem.h | 300 ++++++ fs/squashfs/sqfs_inode.c | 142 +++ fs/squashfs/sqfs_utils.h | 49 + include/fs.h | 1 + include/squashfs.h | 25 + include/u-boot/zlib.h | 32 + lib/zlib/uncompr.c | 97 ++ lib/zlib/zlib.c | 1 + 22 files changed, 2490 insertions(+) create mode 100644 cmd/sqfs.c create mode 100644 fs/squashfs/Kconfig create mode 100644 fs/squashfs/Makefile create mode 100644 fs/squashfs/sqfs.c create mode 100644 fs/squashfs/sqfs_decompressor.c create mode 100644 fs/squashfs/sqfs_decompressor.h create mode 100644 fs/squashfs/sqfs_dir.c create mode 100644 fs/squashfs/sqfs_filesystem.h create mode 100644 fs/squashfs/sqfs_inode.c create mode 100644 fs/squashfs/sqfs_utils.h create mode 100644 include/squashfs.h create mode 100644 lib/zlib/uncompr.c
Hello again,
I am currently writing tests for the SquashFS support. I wrote two Python scripts to test 'ls' and 'load' commands, but I need a SquashFS image to do so, and I would like to know what is the better approach for this:
- Add a compiled SquashFS image into the same directory as the tests,
i.e.: test/py/tests/test_fs/test_squashfs. This is a +-7MB sized binary file.
- Use the scripts to generate an image, with mksquashfs. However,
mksquashfs becomes a dependency.
We should dynamically make the images, just like ext* and fat. The tests should skip when the required tools aren't found, in the same manner we do for other fs tests. Thanks!

On Wed, 29 Jul 2020 11:21:26 -0400 Tom Rini trini@konsulko.com wrote:
On Wed, Jul 29, 2020 at 02:54:56PM +0200, Joao Marcos Costa wrote:
On Tue, 21 Jul 2020 11:22:54 +0200 Joao Marcos Costa joaomarcos.costa@bootlin.com wrote:
Hello!
This series adds support for the SquashFS filesystem. For now, zlib is the only supported compression type. This is my first contribution to U-Boot as well as to a major Open Source project.
Changes in v3:
- Replace CONFIG_IS_ENABLED by IS_ENABLED in fs/fs.c
Changes in v2:
- Replace sqfs_ls() by U-Boot's fs_ls_generic()
- Add info. to MAINTAINERS
- Fix build failures
- Fix style problems
Best regards, Joao Marcos Costa
Joao Marcos Costa (5): fs/squashfs: new filesystem fs/squashfs: add filesystem commands include/u-boot, lib/zlib: add sources for zlib decompression fs/squashfs: add support for zlib decompression fs/fs.c: add symbolic link case to fs_ls_generic()
MAINTAINERS | 7 + cmd/Kconfig | 6 + cmd/Makefile | 1 + cmd/sqfs.c | 42 + common/spl/Kconfig | 9 + fs/Kconfig | 2 + fs/Makefile | 2 + fs/fs.c | 18 + fs/squashfs/Kconfig | 10 + fs/squashfs/Makefile | 7 + fs/squashfs/sqfs.c | 1521 +++++++++++++++++++++++++++++++ fs/squashfs/sqfs_decompressor.c | 53 ++ fs/squashfs/sqfs_decompressor.h | 58 ++ fs/squashfs/sqfs_dir.c | 107 +++ fs/squashfs/sqfs_filesystem.h | 300 ++++++ fs/squashfs/sqfs_inode.c | 142 +++ fs/squashfs/sqfs_utils.h | 49 + include/fs.h | 1 + include/squashfs.h | 25 + include/u-boot/zlib.h | 32 + lib/zlib/uncompr.c | 97 ++ lib/zlib/zlib.c | 1 + 22 files changed, 2490 insertions(+) create mode 100644 cmd/sqfs.c create mode 100644 fs/squashfs/Kconfig create mode 100644 fs/squashfs/Makefile create mode 100644 fs/squashfs/sqfs.c create mode 100644 fs/squashfs/sqfs_decompressor.c create mode 100644 fs/squashfs/sqfs_decompressor.h create mode 100644 fs/squashfs/sqfs_dir.c create mode 100644 fs/squashfs/sqfs_filesystem.h create mode 100644 fs/squashfs/sqfs_inode.c create mode 100644 fs/squashfs/sqfs_utils.h create mode 100644 include/squashfs.h create mode 100644 lib/zlib/uncompr.c
Hello again,
I am currently writing tests for the SquashFS support. I wrote two Python scripts to test 'ls' and 'load' commands, but I need a SquashFS image to do so, and I would like to know what is the better approach for this:
- Add a compiled SquashFS image into the same directory as the
tests, i.e.: test/py/tests/test_fs/test_squashfs. This is a +-7MB sized binary file.
- Use the scripts to generate an image, with mksquashfs. However,
mksquashfs becomes a dependency.
We should dynamically make the images, just like ext* and fat. The tests should skip when the required tools aren't found, in the same manner we do for other fs tests. Thanks!
Ok, thank you!
Best regards, Joao Marcos
participants (2)
-
Joao Marcos Costa
-
Tom Rini