diff mbox series

[1/4] fs/squashfs: new filesystem

Message ID 20200709175148.17193-2-joaomarcos.costa@bootlin.com
State New
Headers show
Series Add support for the SquashFS filesystem | expand

Commit Message

Joao Marcos Costa July 9, 2020, 5:51 p.m. UTC
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 at bootlin.com>
---
 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              | 1625 +++++++++++++++++++++++++++++++
 fs/squashfs/sqfs_decompressor.c |   29 +
 fs/squashfs/sqfs_decompressor.h |   58 ++
 fs/squashfs/sqfs_dir.c          |  107 ++
 fs/squashfs/sqfs_filesystem.h   |  301 ++++++
 fs/squashfs/sqfs_inode.c        |  142 +++
 fs/squashfs/sqfs_utils.h        |   49 +
 include/fs.h                    |   13 +
 include/squashfs.h              |   24 +
 15 files changed, 2393 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

--
2.17.1

Comments

Rasmus Villemoes July 9, 2020, 7:50 p.m. UTC | #1
On 09/07/2020 19.51, Joao Marcos Costa wrote:
> Add support for SquashFS filesystem. Right now, it does not support
> compression but support for zlib will be added in a follow-up commit.

Cool, thanks for proposing this. While I'm not really in a position to
review these, just one comment below.

> 
> +config SPL_FS_SQUASHFS
> +	bool "Support SquashFS filesystems"
> +	select FS_SQUASHFS

Is there any reason U-Boot proper must support squashfs if the SPL does?
This isn't quite specific to this patch; I see a number of 'config
SPL_FOO' that either depends on or selects FOO (e.g. SPL_FS_FAT), and I
wonder why.

Rasmus
Joao Marcos Costa July 9, 2020, 8:19 p.m. UTC | #2
Hello!

On Thu, 9 Jul 2020 21:50:03 +0200
Rasmus Villemoes <rasmus.villemoes at prevas.dk> wrote:

> On 09/07/2020 19.51, Joao Marcos Costa wrote:
> > Add support for SquashFS filesystem. Right now, it does not support
> > compression but support for zlib will be added in a follow-up
> > commit.  
> 
> Cool, thanks for proposing this. While I'm not really in a position to
> review these, just one comment below.
> 
> > 
> > +config SPL_FS_SQUASHFS
> > +	bool "Support SquashFS filesystems"
> > +	select FS_SQUASHFS  
> 
> Is there any reason U-Boot proper must support squashfs if the SPL
> does? This isn't quite specific to this patch; I see a number of
> 'config SPL_FOO' that either depends on or selects FOO (e.g.
> SPL_FS_FAT), and I wonder why.
> 
> Rasmus

Well, I don't have enough experience to answer this question properly,
but precisely as you mentioned, other filesystems do the same
concerning SPL, and I simply decided to follow their example.

Best regards,

Joao Marcos
Thomas Petazzoni July 10, 2020, 8:29 a.m. UTC | #3
Hello Rasmus,

On Thu, 9 Jul 2020 21:50:03 +0200
Rasmus Villemoes <rasmus.villemoes at prevas.dk> wrote:

> > +config SPL_FS_SQUASHFS
> > +	bool "Support SquashFS filesystems"
> > +	select FS_SQUASHFS  
> 
> Is there any reason U-Boot proper must support squashfs if the SPL does?
> This isn't quite specific to this patch; I see a number of 'config
> SPL_FOO' that either depends on or selects FOO (e.g. SPL_FS_FAT), and I
> wonder why.

Well, if your Linux kernel image and Device Tree are stored in a
squashfs filesystem, then U-Boot proper needs SquashFS support, right ?

And that is completely independent of whether the SPL has SquashFS
support to be able to load U-Boot proper (which potentially could be
stored on SquashFS as well).

It's very much like the FAT filesystem case: if you have U-Boot proper
and your Linux kernel image in a FAT filesystem, then the SPL needs FAT
filesystem support to load U-Boot proper, and U-Boot proper needs FAT
filesystem support to load the Linux kernel image.

Am I missing something here ?

Best regards,

Thomas
Rasmus Villemoes July 10, 2020, 8:54 a.m. UTC | #4
On 10/07/2020 10.29, Thomas Petazzoni wrote:
> Hello Rasmus,
> 
> On Thu, 9 Jul 2020 21:50:03 +0200
> Rasmus Villemoes <rasmus.villemoes at prevas.dk> wrote:
> 
>>> +config SPL_FS_SQUASHFS
>>> +	bool "Support SquashFS filesystems"
>>> +	select FS_SQUASHFS  
>>
>> Is there any reason U-Boot proper must support squashfs if the SPL does?
>> This isn't quite specific to this patch; I see a number of 'config
>> SPL_FOO' that either depends on or selects FOO (e.g. SPL_FS_FAT), and I
>> wonder why.
> 
> Well, if your Linux kernel image and Device Tree are stored in a
> squashfs filesystem, then U-Boot proper needs SquashFS support, right ?

Exactly.

> And that is completely independent of whether the SPL has SquashFS
> support to be able to load U-Boot proper (which potentially could be
> stored on SquashFS as well).

Exactly.

> It's very much like the FAT filesystem case: if you have U-Boot proper
> and your Linux kernel image in a FAT filesystem, 

No, this is very much _not_ like the above. In this paragraph, you
combine "U-Boot proper and your Linux kernel", imposing an implicit
assumption that they are stored in the same way. Sure, _if_ both these
items are stored in squashfs images (possibly the same, possibly
distinct), then the thing that loads the respective images obviously
needs squashfs (or FAT, or whatnot) support.

My point is that it's possible that, say, U-Boot proper is stored in a
FAT file system, and the kernel is stored in a UBI volume. So SPL needs
FAT support. Why should I be forced to compile FAT support into U-Boot
proper if U-Boot proper never needs to access a FAT filesystem? And the
same for squashfs. Or any of the drivers or DM_ frameworks that do that
"depends on" or "select".

I can see why things like bloblist that are very much about passing info
from one stage to the next only make sense if both sides have that
configured in. But the drivers/filesystems that are needed in SPL need
not have anything to do with that which is needed in U-Boot proper.

Rasmus
Thomas Petazzoni July 10, 2020, 9:13 a.m. UTC | #5
On Fri, 10 Jul 2020 10:54:24 +0200
Rasmus Villemoes <rasmus.villemoes at prevas.dk> wrote:

> > It's very much like the FAT filesystem case: if you have U-Boot proper
> > and your Linux kernel image in a FAT filesystem,   
> 
> No, this is very much _not_ like the above. In this paragraph, you
> combine "U-Boot proper and your Linux kernel", imposing an implicit
> assumption that they are stored in the same way. Sure, _if_ both these
> items are stored in squashfs images (possibly the same, possibly
> distinct), then the thing that loads the respective images obviously
> needs squashfs (or FAT, or whatnot) support.
> 
> My point is that it's possible that, say, U-Boot proper is stored in a
> FAT file system, and the kernel is stored in a UBI volume. So SPL needs
> FAT support. Why should I be forced to compile FAT support into U-Boot
> proper if U-Boot proper never needs to access a FAT filesystem? And the
> same for squashfs. Or any of the drivers or DM_ frameworks that do that
> "depends on" or "select".

Ah, I absolutely agree that it should be possible to have Squashfs in
both SPL and U-Boot proper, or only in SPL or only in U-Boot proper.

It was not clear in your initial e-mail that this was the issue you
were pointing.

Thomas
diff mbox series

Patch

diff --git a/common/spl/Kconfig b/common/spl/Kconfig
index b03a476b9f..426b783d9d 100644
--- a/common/spl/Kconfig
+++ b/common/spl/Kconfig
@@ -566,6 +566,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 0c66d60477..2684e6ccce 100644
--- a/fs/fs.c
+++ b/fs/fs.c
@@ -19,6 +19,7 @@ 
 #include <div64.h>
 #include <linux/math64.h>
 #include <efi_loader.h>
+#include <squashfs.h>

 DECLARE_GLOBAL_DATA_PTR;

@@ -273,6 +274,20 @@  static struct fstype_info fstypes[] = {
 		.mkdir = fs_mkdir_unsupported,
 		.ln = fs_ln_unsupported,
 	},
+#endif
+#ifdef CONFIG_FS_SQUASHFS
+	{
+		.fstype = FS_TYPE_SQUASHFS,
+		.name = "squashfs",
+		.probe = sqfs_probe,
+		.opendir = sqfs_opendir,
+		.readdir = sqfs_readdir,
+		.ls = sqfs_ls,
+		.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..3302a63cca
--- /dev/null
+++ b/fs/squashfs/sqfs.c
@@ -0,0 +1,1625 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Bootlin
+ *
+ * Author: Joao Marcos Costa <joaomarcos.costa at 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 "sqfs_decompressor.h"
+#include "sqfs_filesystem.h"
+#include "sqfs_utils.h"
+
+static disk_partition_t cur_part_info;
+static struct blk_desc *cur_dev;
+
+static int sqfs_disk_read(__u32 block, __u32 nr_blocks, void *buf)
+{
+	ulong ret;
+
+	if (!cur_dev)
+		return -1;
+
+	ret = blk_dread(cur_dev, 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(cur_dev->blksz);
+	if (!*sblk)
+		return -ENOMEM;
+
+	if (sqfs_disk_read(0, 1, *sblk) != 1) {
+		free(*sblk);
+		cur_dev = NULL;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int sqfs_count_tokens(const char *path)
+{
+	int token_count = 1, l;
+
+	for (l = 1; l < strlen(path); l++) {
+		if (path[l] == '/')
+			token_count++;
+	}
+
+	return token_count;
+}
+
+static int sqfs_tokenize_path(const char *filename)
+{
+	int token_count;
+
+	token_count = sqfs_count_tokens(filename);
+	if (token_count < 0)
+		return -EINVAL;
+
+	/* 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) / cur_dev->blksz;
+	*offset = le64_to_cpu(start) - (start_ * cur_dev->blksz);
+
+	return DIV_ROUND_UP(table_size + *offset, 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;
+	unsigned long dest_len;
+	int block, offset, ret;
+	u16 header, comp_type;
+
+	ret = sqfs_read_sblk(&sblk);
+	if (ret)
+		return ret;
+
+	comp_type = le16_to_cpu(sblk->compression);
+
+	if (inode_fragment_index >= le32_to_cpu(sblk->fragments)) {
+		ret = -EINVAL;
+		goto free_sblk;
+	}
+
+	start = le64_to_cpu(sblk->fragment_table_start) / 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 * cur_dev->blksz);
+	if (!table) {
+		ret = -ENOMEM;
+		goto free_sblk;
+	}
+
+	if (sqfs_disk_read(start, n_blks, table) < 0) {
+		free(sblk);
+		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 / 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 * 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);
+free_sblk:
+	free(sblk);
+	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;
+}
+
+/*
+ * 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, *aux, *resolved = NULL, *basec, *relc;
+	int bc, rc, i, j, updir = 0, resolved_size = 0, offset;
+
+	/* 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_tokenize_path(basec);
+	rc = sqfs_tokenize_path(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 base tokens list */
+	if (!strcmp(basec, "/")) {
+		base_tokens[0] = strdup(basec);
+		if (!base_tokens[0])
+			goto free_r_tokens;
+	} else {
+		for (j = 0; j < bc; j++) {
+			aux = strtok(!j ? basec : NULL, "/");
+			base_tokens[j] = strdup(aux);
+			if (!base_tokens[j]) {
+				for (i = 0; i < j; i++)
+					free(base_tokens[i]);
+				goto free_r_tokens;
+			}
+		}
+	}
+
+	/* Allocate and fill rel. tokens list */
+	if (!strcmp(relc, "/")) {
+		rel_tokens[0] = strdup(relc);
+		if (!rel_tokens[0])
+			goto free_b_tokens_loop;
+	} else {
+		for (j = 0; j < rc; j++) {
+			aux = strtok(!j ? relc : NULL, "/");
+			rel_tokens[j] = strdup(aux);
+			if (!rel_tokens[j]) {
+				for (i = 0; i < j; i++)
+					free(rel_tokens[i]);
+				goto free_b_tokens_loop;
+			}
+		}
+	}
+
+	/* ignore file name in base path, e.g. /dir/file -> dir */
+	free(base_tokens[bc - 1]);
+	bc--;
+
+	/* count '..' occurrences in target path */
+	for (i = 0; i < rc; i++) {
+		if (!strcmp(rel_tokens[i], ".."))
+			updir++;
+	}
+
+	for (i = bc - updir; i < bc; i++)
+		free(base_tokens[i]);
+
+	bc -= updir;
+	if (bc < 0)
+		bc = 0;
+
+	if (!bc)
+		resolved_size++;
+
+	for (i = 0; i < bc; i++)
+		resolved_size += strlen(base_tokens[i]) + 1;
+
+	for (i = updir; i < rc; i++)
+		resolved_size += strlen(rel_tokens[i])  + 1;
+
+	resolved = malloc(resolved_size + 1);
+	if (!resolved)
+		goto free_r_tokens_loop;
+
+	resolved[0] = '/';
+	memset(resolved + 1, '\0', resolved_size);
+
+	offset = 1;
+	for (i = 0; i < bc; i++) {
+		strncpy(resolved + offset, base_tokens[i],
+			strlen(base_tokens[i]));
+		offset += strlen(base_tokens[i]);
+		resolved[offset++] = '/';
+	}
+
+	for (i = updir; i < rc; i++) {
+		strncpy(resolved + offset, rel_tokens[i],
+			strlen(rel_tokens[i]));
+		offset += strlen(rel_tokens[i]);
+		if (offset >= resolved_size - 1)
+			break;
+		resolved[offset++] = '/';
+	}
+
+free_r_tokens_loop:
+	for (i = 0; i < rc; i++)
+		free(rel_tokens[i]);
+free_b_tokens_loop:
+	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)
+{
+	int j, ret, new_inode_number, offset;
+	struct squashfs_symlink_inode sym;
+	struct squashfs_super_block *sblk;
+	struct squashfs_ldir_inode ldir;
+	struct squashfs_dir_inode dir;
+	struct fs_dir_stream *dirsp;
+	char  *path, *filename;
+	struct fs_dirent dent;
+	unsigned char *table;
+
+	/* Read SquashFS super block */
+	ret = sqfs_read_sblk(&sblk);
+	if (ret)
+		return ret;
+
+	dirsp = (struct fs_dir_stream *)dirs;
+	dirs->dentp = &dent;
+
+	/* 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) {
+		free(sblk);
+		return -ENOMEM;
+	}
+
+	memcpy(dirs->dir_header, dirs->table, SQFS_DIR_HEADER_SIZE);
+	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;
+
+	/* No path given -> root directory */
+	if (!strcmp(token_list[0], "/")) {
+		dirs->table = &dirs->dir_table[offset];
+		memcpy(&dirs->i_dir, &dir, sizeof(dir));
+		free(sblk);
+		return 0;
+	}
+
+	for (j = 0; j < token_count; j++) {
+		if (!sqfs_is_dir(dir.inode_type)) {
+			printf("Directory not found: %s\n", token_list[j]);
+			free(sblk);
+			return -EINVAL;
+		}
+
+		while (sqfs_readdir(dirsp, &dirs->dentp)) {
+			ret = strcmp(dent.name, token_list[j]);
+			if (!ret)
+				break;
+			free(dirs->entry);
+		}
+
+		if (ret) {
+			printf("Directory not found: %s\n", token_list[j]);
+			free(dirs->entry);
+			free(sblk);
+			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);
+			filename = sqfs_resolve_symlink(&sym, table, path);
+			printf("%s - > %s\n", token_list[j], filename);
+			free(dirs->entry);
+			free(sblk);
+			return SQFS_SYMLINK_TYPE;
+		} else if (!sqfs_is_dir(dir.inode_type)) {
+			printf("%s is not a directory.\n", token_list[j]);
+			free(dirs->entry);
+			free(sblk);
+			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);
+			free(sblk);
+			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));
+
+	free(sblk);
+
+	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;
+	unsigned char *src_table, *itb;
+	u32 src_len, dest_offset = 0;
+	unsigned long dest_len;
+	bool compressed;
+
+	/* Read SquashFS super block */
+	ret = sqfs_read_sblk(&sblk);
+	if (ret)
+		return ret;
+
+	comp_type = sblk->compression;
+	table_size = le64_to_cpu(sblk->directory_table_start -
+				 sblk->inode_table_start);
+	start = sblk->inode_table_start / 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 * cur_dev->blksz);
+	if (!itb) {
+		ret = -ENOMEM;
+		goto free_sblk;
+	}
+
+	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);
+free_sblk:
+	free(sblk);
+
+	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;
+	unsigned char *src_table, *dtb;
+	u32 src_len, dest_offset = 0;
+	unsigned long dest_len;
+	bool compressed;
+
+	/* Read SquashFS super block */
+	ret = sqfs_read_sblk(&sblk);
+	if (ret)
+		return ret;
+
+	comp_type = sblk->compression;
+
+	/* DIRECTORY TABLE */
+	table_size = le64_to_cpu(sblk->fragment_table_start -
+				 sblk->directory_table_start);
+	start = sblk->directory_table_start / 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 * cur_dev->blksz);
+
+	if (!dtb)
+		goto free_sblk;
+
+	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);
+free_sblk:
+	free(sblk);
+
+	return metablks_count;
+}
+
+int sqfs_opendir(const char *filename, struct fs_dir_stream **dirsp)
+{
+	unsigned char *inode_table = NULL, *dir_table = NULL;
+	int i, j, token_count, ret = 0, metablks_count;
+	struct squashfs_super_block *sblk;
+	struct squashfs_dir_stream *dirs;
+	char **token_list, *path, *aux;
+	u32 *pos_list = NULL;
+
+	/* Read SquashFS super block */
+	ret = sqfs_read_sblk(&sblk);
+	if (ret)
+		return -EINVAL;
+
+	ret = sqfs_read_inode_table(&inode_table);
+	if (ret) {
+		ret = -EINVAL;
+		goto free_sblk;
+	}
+
+	metablks_count = sqfs_read_directory_table(&dir_table, &pos_list);
+	if (metablks_count < 1) {
+		ret = -EINVAL;
+		goto free_sblk;
+	}
+
+	/* Tokenize filename */
+	token_count = sqfs_tokenize_path(filename);
+	if (token_count < 0) {
+		ret = -EINVAL;
+		goto free_sblk;
+	}
+
+	path = strdup(filename);
+	if (!path) {
+		ret = -ENOMEM;
+		goto free_sblk;
+	}
+
+	token_list = malloc(token_count * sizeof(char *));
+	if (!token_list) {
+		ret = -EINVAL;
+		goto free_path;
+	}
+
+	/* Allocate and fill token list */
+	if (!strcmp(path, "/")) {
+		token_list[0] = strdup(path);
+		if (!token_list[0]) {
+			free(token_list);
+			ret = -ENOMEM;
+			goto free_path;
+		}
+
+	} else {
+		for (j = 0; j < token_count; j++) {
+			aux = strtok(!j ? path : NULL, "/");
+			token_list[j] = strdup(aux);
+			if (!token_list[j]) {
+				for (i = 0; i < j; i++)
+					free(token_list[i]);
+				free(token_list);
+				ret = -ENOMEM;
+				goto free_list;
+			}
+		}
+	}
+
+	dirs = (struct squashfs_dir_stream *)dirsp;
+	/*
+	 * 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;
+
+free_tokens:
+	for (j = 0; j < token_count; j++)
+		free(token_list[j]);
+	free(token_list);
+free_list:
+	free(pos_list);
+free_path:
+	free(path);
+free_sblk:
+	free(sblk);
+
+	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;
+	int offset = 0, ret;
+
+	dirs = (struct squashfs_dir_stream *)fs_dirs;
+	if (!dirs->size)
+		return SQFS_STOP_READDIR;
+
+	if (!dirs->entry_count) {
+		if (dirs->size > SQFS_DIR_HEADER_SIZE) {
+			dirs->size -= SQFS_DIR_HEADER_SIZE;
+		} else {
+			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:
+		(*dentp)->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));
+			(*dentp)->size = (loff_t)le64_to_cpu(lreg.file_size);
+		} else {
+			memcpy(&reg, &dirs->inode_table[dirs->entry->offset],
+			       sizeof(reg));
+			(*dentp)->size = (loff_t)le32_to_cpu(reg.file_size);
+		}
+
+		(*dentp)->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:
+		(*dentp)->type = SQFS_MISC_ENTRY_TYPE;
+		break;
+	case SQFS_SYMLINK_TYPE:
+	case SQFS_LSYMLINK_TYPE:
+		(*dentp)->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((*dentp)->name, dirs->entry->name, dirs->entry->name_size + 1);
+	(*dentp)->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;
+
+	return SQFS_CONTINUE_READDIR;
+}
+
+int sqfs_probe(struct blk_desc *fs_dev_desc, disk_partition_t *fs_partition)
+{
+	struct squashfs_super_block *sblk;
+
+	cur_dev = fs_dev_desc;
+	cur_part_info = *fs_partition;
+	sblk = malloc_cache_aligned(cur_dev->blksz);
+
+	if (!sblk)
+		return -ENOMEM;
+
+	/* Read SquashFS super block */
+	if (sqfs_disk_read(0, 1, sblk) != 1) {
+		free(sblk);
+		cur_dev = NULL;
+		return -EINVAL;
+	}
+
+	/* Make sure it has a valid SquashFS magic number*/
+	if (sblk->s_magic != SQFS_MAGIC_NUMBER) {
+		printf("Bad magic number for SquashFS image.\n");
+		cur_dev = NULL;
+		return -EINVAL;
+	}
+
+	free(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_fragment_block_entry frag_entry;
+	struct squashfs_directory_entry *entry = NULL;
+	struct squashfs_file_info finfo = {0};
+	struct squashfs_symlink_inode symlink;
+	struct fs_dir_stream *dirsp = NULL;
+	struct squashfs_super_block *sblk;
+	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;
+
+	/* Read SquashFS super block */
+	ret = sqfs_read_sblk(&sblk);
+	if (ret)
+		return ret;
+
+	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, &dirs.fs_dirs);
+	if (ret) {
+		ret = CMD_RET_FAILURE;
+		goto free_paths;
+	}
+
+	dirsp = (struct fs_dir_stream *)&dirs;
+	dirs.dentp = &dent;
+	/* For now, only regular files are able to be loaded */
+	while (sqfs_readdir(dirsp, &dirs.dentp)) {
+		if (!strncmp(dent.name, file, strlen(dent.name))) {
+			ret = sqfs_read_entry(&entry, dirs.entry);
+			if (ret) {
+				ret = CMD_RET_FAILURE;
+				free(dirs.entry);
+				sqfs_closedir(dirsp);
+				goto free_paths;
+			}
+
+			break;
+		}
+
+		free(dirs.entry);
+	}
+
+	if (!entry) {
+		printf("File not found.\n");
+		*actread = 0;
+		sqfs_closedir(dirsp);
+		ret = CMD_RET_FAILURE;
+		goto free_paths;
+	}
+
+	i_number = dirs.dir_header->inode_number + 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);
+		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);
+		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);
+		printf("%s - > %s\n", filename, resolved);
+		ret = sqfs_read(resolved, buf, offset, len, actread);
+		free(resolved);
+		if (ret)
+			ret = CMD_RET_FAILURE;
+		ret = CMD_RET_SUCCESS;
+		goto free_entry;
+	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 = CMD_RET_FAILURE;
+		goto free_entry;
+	}
+
+	if (datablk_count < 0) {
+		ret = CMD_RET_FAILURE;
+		goto free_entry;
+	}
+
+	/* If the user specifies a length, check its sanity */
+	if (len) {
+		if (len > finfo.size) {
+			ret = CMD_RET_FAILURE;
+			goto free_entry;
+		}
+
+		finfo.size = len;
+	}
+
+	if (datablk_count) {
+		data_offset = finfo.start;
+		datablock = malloc(sblk->block_size);
+		if (!datablock) {
+			ret = CMD_RET_FAILURE;
+			goto free_entry;
+		}
+	}
+
+	for (j = 0; j < datablk_count; j++) {
+		start = data_offset / cur_dev->blksz;
+		table_size = SQFS_BLOCK_SIZE(finfo.blk_sizes[j]);
+		table_offset = data_offset - (start * cur_dev->blksz);
+		n_blks = DIV_ROUND_UP(table_size + table_offset,
+				      cur_dev->blksz);
+
+		data_buffer = malloc_cache_aligned(n_blks * cur_dev->blksz);
+
+		if (!data_buffer) {
+			ret = CMD_RET_FAILURE;
+			goto free_datablk;
+		}
+
+		if (sqfs_disk_read(start, n_blks, data_buffer) < 0) {
+			/*
+			 * Tip: re-compile the SquashFS image with mksquashfs's
+			 * -b <block_size> option.
+			 */
+			printf("Error: too many data blocks or too large"\
+			       "SquashFS block size.\n");
+			ret = CMD_RET_FAILURE;
+			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) {
+				ret = CMD_RET_FAILURE;
+				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 = CMD_RET_SUCCESS;
+		goto free_buffer;
+	}
+
+	start = frag_entry.start / cur_dev->blksz;
+	table_size = SQFS_BLOCK_SIZE(frag_entry.size);
+	table_offset = frag_entry.start - (start * cur_dev->blksz);
+	n_blks = DIV_ROUND_UP(table_size + table_offset, cur_dev->blksz);
+
+	fragment = malloc_cache_aligned(n_blks * cur_dev->blksz);
+
+	if (!fragment) {
+		ret = CMD_RET_FAILURE;
+		goto free_buffer;
+	}
+
+	if (sqfs_disk_read(start, n_blks, fragment) < 0) {
+		ret = CMD_RET_FAILURE;
+		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 = CMD_RET_FAILURE;
+			goto free_fragment;
+		}
+
+		ret = sqfs_decompress(comp_type, fragment_block, &dest_len,
+				      (void *)fragment  + table_offset,
+				      frag_entry.size);
+		if (ret) {
+			free(fragment_block);
+			ret = CMD_RET_FAILURE;
+			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_entry:
+	free(entry);
+free_paths:
+	free(file);
+	free(dir);
+	free(sblk);
+
+	return ret;
+}
+
+int sqfs_ls(const char *filename)
+{
+	int ret = 0, nfiles = 0, ndirs = 0;
+	struct squashfs_dir_stream dirs;
+	struct fs_dir_stream *dirsp;
+	struct fs_dirent dent;
+
+	dirsp = (struct fs_dir_stream *)&dirs;
+	ret = sqfs_opendir(filename, &dirs.fs_dirs);
+	if (ret) {
+		sqfs_closedir(dirsp);
+		return CMD_RET_FAILURE;
+	}
+
+	dirs.dentp = &dent;
+	dirsp = (struct fs_dir_stream *)&dirs;
+	while (sqfs_readdir(dirsp, &dirs.dentp)) {
+		switch (dent.type) {
+		case FS_DT_DIR:
+			printf("            %s/\n", dent.name);
+			ndirs++;
+			break;
+		case FS_DT_REG:
+			printf("%8lld    %s\n", dent.size, dent.name);
+			nfiles++;
+			break;
+		case FS_DT_LNK:
+			printf("<SYMLINK>   %s\n", dent.name);
+			nfiles++;
+			break;
+		case SQFS_MISC_ENTRY_TYPE:
+			printf("            %s\n", dent.name);
+			nfiles++;
+			break;
+		default:
+			break;
+		}
+
+		free(dirs.entry);
+	}
+
+	sqfs_closedir(dirsp);
+
+	return ret;
+}
+
+int sqfs_size(const char *filename, loff_t *size)
+{
+	struct squashfs_symlink_inode symlink;
+	struct fs_dir_stream *dirsp = NULL;
+	struct squashfs_super_block *sblk;
+	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;
+
+	/* Read SquashFS super block */
+	ret = sqfs_read_sblk(&sblk);
+	if (ret)
+		return ret;
+
+	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, &dirs.fs_dirs);
+	if (ret) {
+		sqfs_closedir(dirsp);
+		ret = CMD_RET_FAILURE;
+		goto free_strings;
+	}
+
+	dirsp = (struct fs_dir_stream *)&dirs;
+	dirs.dentp = &dent;
+
+	while (sqfs_readdir(dirsp, &dirs.dentp)) {
+		ret = strcmp(dent.name, file);
+		if (!ret)
+			break;
+		free(dirs.entry);
+	}
+
+	if (ret) {
+		sqfs_closedir(dirsp);
+		ret = CMD_RET_FAILURE;
+		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);
+		if (ret)
+			ret = CMD_RET_FAILURE;
+		ret = CMD_RET_SUCCESS;
+		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;
+		break;
+	}
+
+free_strings:
+	free(dir);
+	free(file);
+
+	free(sblk);
+	sqfs_closedir(dirsp);
+
+	return 0;
+}
+
+void sqfs_close(void)
+{
+}
+
+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 at 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 at 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 at 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..638d4f7d72
--- /dev/null
+++ b/fs/squashfs/sqfs_filesystem.h
@@ -0,0 +1,301 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 Bootlin
+ *
+ * Author: Joao Marcos Costa <joaomarcos.costa at 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 0
+#define SQFS_CONTINUE_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 at 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 at 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 37e35c2120..5ee3b0a651 100644
--- a/include/fs.h
+++ b/include/fs.h
@@ -13,6 +13,7 @@ 
 #define FS_TYPE_SANDBOX	3
 #define FS_TYPE_UBIFS	4
 #define FS_TYPE_BTRFS	5
+#define FS_TYPE_SQUASHFS 6

 /**
  * do_fat_fsload - Run the fatload command
@@ -36,6 +37,18 @@  int do_fat_fsload(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]);
  */
 int do_ext2load(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]);

+
+/**
+ * do_sqfs_load - Run the sqfsload command
+ *
+ * @cmdtp: Command information for sqfsload
+ * @flag: Command flags (CMD_FLAG_...)
+ * @argc: Number of arguments
+ * @argv: List of arguments
+ * @return result (see enum command_ret_t)
+ */
+int do_sqfs_load(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]);
+
 /*
  * Tell the fs layer which block device an partition to use for future
  * commands. This also internally identifies the filesystem that is present
diff --git a/include/squashfs.h b/include/squashfs.h
new file mode 100644
index 0000000000..ec59eee8d8
--- /dev/null
+++ b/include/squashfs.h
@@ -0,0 +1,24 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2020 Bootlin
+ *
+ * Author: Joao Marcos Costa <joaomarcos.costa at bootlin.com>
+ *
+ * squashfs.h: SquashFS filesystem implementation.
+ */
+
+#ifndef _SQFS_H_
+#define _SQFS_H_
+
+int sqfs_opendir(const char *filename, struct fs_dir_stream **dirsp);
+int sqfs_ls(const char *filename);
+int sqfs_readdir(struct fs_dir_stream *dirs, struct fs_dirent **dentp);
+int sqfs_probe(struct blk_desc *fs_dev_desc,
+	       disk_partition_t *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  */