All of lore.kernel.org
 help / color / mirror / Atom feed
From: Joao Marcos Costa <joaomarcos.costa@bootlin.com>
To: u-boot@lists.denx.de
Subject: [PATCH v3 1/5] fs/squashfs: new filesystem
Date: Tue, 21 Jul 2020 11:22:55 +0200	[thread overview]
Message-ID: <20200721092259.26916-2-joaomarcos.costa@bootlin.com> (raw)
In-Reply-To: <20200721092259.26916-1-joaomarcos.costa@bootlin.com>

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(&reg, &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(&reg, ipos, sizeof(reg));
+		datablk_count = sqfs_get_regfile_info(&reg, &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(&reg, 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(&reg, 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  */
-- 
2.17.1

  reply	other threads:[~2020-07-21  9:22 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-07-21  9:22 [PATCH v3 0/5] Add support for the SquashFS filesystem Joao Marcos Costa
2020-07-21  9:22 ` Joao Marcos Costa [this message]
2020-07-21  9:22 ` [PATCH v3 2/5] fs/squashfs: add filesystem commands Joao Marcos Costa
2020-07-21  9:22 ` [PATCH v3 3/5] include/u-boot, lib/zlib: add sources for zlib decompression Joao Marcos Costa
2020-07-29 15:27   ` Tom Rini
2020-07-29 16:00     ` Joao Marcos Costa
2020-07-29 16:54       ` Tom Rini
2020-07-30 12:10         ` Joao Marcos Costa
2020-07-30 12:17           ` Tom Rini
2020-07-30 13:27             ` Joao Marcos Costa
2020-07-21  9:22 ` [PATCH v3 4/5] fs/squashfs: add support " Joao Marcos Costa
2020-07-21  9:22 ` [PATCH v3 5/5] fs/fs.c: add symbolic link case to fs_ls_generic() Joao Marcos Costa
2020-07-29 12:54 ` [PATCH v3 0/5] Add support for the SquashFS filesystem Joao Marcos Costa
2020-07-29 15:21   ` Tom Rini
2020-07-29 16:02     ` Joao Marcos Costa

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200721092259.26916-2-joaomarcos.costa@bootlin.com \
    --to=joaomarcos.costa@bootlin.com \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.