From 27c686fd47a7ed4edef1ecbe9bb26402774df4a6 Mon Sep 17 00:00:00 2001
From: Ferenc Wagner <wferi@niif.hu>
Date: Tue, 16 Mar 2010 11:49:12 +0100
Subject: [PATCH] squashfs: add direct MTD support

---
 fs/squashfs/Kconfig |    2 +-
 fs/squashfs/block.c |  178 +++++++++++++++++++++++++++++++++++++++++++++++---
 fs/squashfs/super.c |   51 ++++++++++++---
 3 files changed, 210 insertions(+), 21 deletions(-)

diff --git a/fs/squashfs/Kconfig b/fs/squashfs/Kconfig
index 25a00d1..c23e2eb 100644
--- a/fs/squashfs/Kconfig
+++ b/fs/squashfs/Kconfig
@@ -1,6 +1,6 @@
 config SQUASHFS
 	tristate "SquashFS 4.0 - Squashed file system support"
-	depends on BLOCK
+	depends on MTD
 	select ZLIB_INFLATE
 	help
 	  Saying Y here includes support for SquashFS 4.0 (a Compressed
diff --git a/fs/squashfs/block.c b/fs/squashfs/block.c
index 2a79603..681d5d0 100644
--- a/fs/squashfs/block.c
+++ b/fs/squashfs/block.c
@@ -33,12 +33,15 @@
 #include <linux/string.h>
 #include <linux/buffer_head.h>
 #include <linux/zlib.h>
+#include <linux/mtd/mtd.h>
 
 #include "squashfs_fs.h"
 #include "squashfs_fs_sb.h"
 #include "squashfs_fs_i.h"
 #include "squashfs.h"
 
+#ifdef CONFIG_BLOCK
+
 /*
  * Read the metadata block length, this is stored in the first two
  * bytes of the metadata block.
@@ -70,16 +73,7 @@ static struct buffer_head *get_block_length(struct super_block *sb,
 	return bh;
 }
 
-
-/*
- * Read and decompress a metadata block or datablock.  Length is non-zero
- * if a datablock is being read (the size is stored elsewhere in the
- * filesystem), otherwise the length is obtained from the first two bytes of
- * the metadata block.  A bit in the length field indicates if the block
- * is stored uncompressed in the filesystem (usually because compression
- * generated a larger block - this does occasionally happen with zlib).
- */
-int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
+static int squashfs_read_block_data(struct super_block *sb, void **buffer, u64 index,
 			int length, u64 *next_index, int srclength, int pages)
 {
 	struct squashfs_sb_info *msblk = sb->s_fs_info;
@@ -263,8 +257,170 @@ block_release:
 		put_bh(bh[k]);
 
 read_failure:
-	ERROR("squashfs_read_data failed to read block 0x%llx\n",
+	ERROR("squashfs_read_block_data failed to read block 0x%llx\n",
 					(unsigned long long) index);
 	kfree(bh);
 	return -EIO;
 }
+
+#endif
+
+static int checked_mtd_read(struct mtd_info *mtd, u64 index, int length,
+				u_char *buf)
+{
+	int ret, retlen;
+
+	ret = mtd->read(mtd, index, length, &retlen, buf);
+	TRACE("checked_mtd_read(index=0x%llx, length=%d): %d\n",
+		index, length, ret);
+	if (ret && ret != -EUCLEAN && ret != -EBADMSG)
+		return ret;
+	if (retlen != length)
+		return -EIO;
+	return 0;
+}
+
+static int fill_in(struct mtd_info *mtd, z_stream *z, u_char *buf, u64 index,
+		int *bytes_read, int length)
+{
+	z->next_in = buf;
+	z->avail_in = min(length - (*bytes_read), (int) PAGE_CACHE_SIZE);
+	if (checked_mtd_read(mtd, index + *bytes_read, z->avail_in, buf))
+		return -1;
+	*bytes_read += z->avail_in;
+	TRACE("fill: avail_in=%d (%x..%x)\n", z->avail_in,
+		buf[0], buf[z->avail_in-1]);
+	return 0;
+}
+
+static int squashfs_read_mtd_data(struct super_block *sb, void **buffer,
+	u64 index, int length, u64 *next_index, int srclength, int pages)
+{
+	struct squashfs_sb_info *msblk = sb->s_fs_info;
+	struct mtd_info *mtd = sb->s_mtd;
+	static u_char buf[PAGE_CACHE_SIZE]; /* mostly for decompression */
+	int compressed, bytes_read = 0, page = 0;
+
+	if (length) { /* datablock */
+		compressed = SQUASHFS_COMPRESSED_BLOCK(length);
+		length = SQUASHFS_COMPRESSED_SIZE_BLOCK(length);
+		TRACE("Block @ 0x%llx, %scompressed size %d, src size %d\n",
+			index, compressed ? "" : "un", length, srclength);
+	} else { /* metadata block */
+		if ((index + 2) > msblk->bytes_used)
+			goto read_failure;
+		if (checked_mtd_read(mtd, index, 2, buf))
+			goto read_failure;
+		length = buf[0] + 256 * buf[1];
+		compressed = SQUASHFS_COMPRESSED(length);
+		length = SQUASHFS_COMPRESSED_SIZE(length);
+		TRACE("Block @ 0x%llx, %scompressed size %d\n", index,
+				compressed ? "" : "un", length);
+		index += 2;
+	}
+	if (next_index)
+		*next_index = index + length;
+	if (length < 0 || length > srclength ||
+			(index + length) > msblk->bytes_used)
+		goto read_failure;
+	if (compressed) {
+		int zlib_err = 0;
+		z_stream *z = &msblk->stream;
+
+		mutex_lock(&msblk->read_data_mutex);
+
+		if (fill_in(mtd, z, buf, index, &bytes_read, length))
+			goto release_mutex;
+		z->next_out = buffer[page++];
+		z->avail_out = PAGE_CACHE_SIZE;
+
+		zlib_err = zlib_inflateInit(z);
+		if (zlib_err != Z_OK) {
+			ERROR("zlib_inflateInit returned unexpected result"
+				" 0x%x, srclength %d\n", zlib_err, srclength);
+			goto release_mutex;
+		}
+
+		while (1) {
+			zlib_err = zlib_inflate(z, Z_SYNC_FLUSH);
+			if (zlib_err != Z_OK)
+				break;
+			if (z->avail_out == 0) {
+				if (page < pages) {
+					z->next_out = buffer[page++];
+					z->avail_out = PAGE_CACHE_SIZE;
+					continue;
+				} else {
+					ERROR("zlib_inflate run out of space\n");
+					goto release_mutex;
+				}
+			}
+			if (z->avail_in == 0) {
+				if (bytes_read < length) {
+					if (fill_in(mtd, z, buf, index,
+							&bytes_read, length))
+						goto release_mutex;
+					continue;
+				} else {
+					ERROR("zlib_inflate run out of input\n");
+					goto release_mutex;
+				}
+			}
+			ERROR("should not be here\n");
+			break;
+		}
+
+		if (zlib_err != Z_STREAM_END) {
+			ERROR("zlib_inflate error %d, data probably corrupt\n",
+				zlib_err);
+			goto release_mutex;
+		}
+
+		zlib_err = zlib_inflateEnd(z);
+		if (zlib_err != Z_OK) {
+			ERROR("zlib_inflateEnd error %d, data probably corrupt\n",
+				zlib_err);
+			goto release_mutex;
+		}
+		length = z->total_out;
+		mutex_unlock(&msblk->read_data_mutex);
+	} else { /* not compressed */
+		while (bytes_read < length) {
+			int blk = min(length - bytes_read, (int) PAGE_CACHE_SIZE);
+			if (checked_mtd_read(mtd, index + bytes_read, blk,
+						buffer[page++]))
+				goto read_failure;
+			bytes_read += blk;
+		}
+	}
+
+	return length;
+
+release_mutex:
+	mutex_unlock(&msblk->read_data_mutex);
+
+read_failure:
+	ERROR("squashfs_read_mtd_data failed to read block 0x%llx\n",
+					(unsigned long long) index);
+	return -EIO;
+}
+
+/*
+ * Read and decompress a metadata block or datablock.  Length is non-zero
+ * if a datablock is being read (the size is stored elsewhere in the
+ * filesystem), otherwise the length is obtained from the first two bytes of
+ * the metadata block.  A bit in the length field indicates if the block
+ * is stored uncompressed in the filesystem (usually because compression
+ * generated a larger block - this does occasionally happen with zlib).
+ */
+int squashfs_read_data(struct super_block *sb, void **buffer, u64 index,
+			int length, u64 *next_index, int srclength, int pages)
+{
+#ifdef CONFIG_BLOCK
+	if (sb->s_bdev)
+		return squashfs_read_block_data(sb, buffer, index,
+				length, next_index, srclength, pages);
+#endif
+	return squashfs_read_mtd_data(sb, buffer, index,
+				length, next_index, srclength, pages);
+}
diff --git a/fs/squashfs/super.c b/fs/squashfs/super.c
index 6c197ef..558a2fa 100644
--- a/fs/squashfs/super.c
+++ b/fs/squashfs/super.c
@@ -37,6 +37,7 @@
 #include <linux/module.h>
 #include <linux/zlib.h>
 #include <linux/magic.h>
+#include <linux/mtd/super.h>
 
 #include "squashfs_fs.h"
 #include "squashfs_fs_sb.h"
@@ -65,6 +66,15 @@ static int supported_squashfs_filesystem(short major, short minor, short comp)
 	return 0;
 }
 
+static const char *devname(struct super_block *sb, char *buffer)
+{
+#ifdef CONFIG_BLOCK
+	if (sb->s_bdev)
+		return bdevname(sb->s_bdev, buffer);
+#endif
+	snprintf(buffer, BDEVNAME_SIZE, "MTD%d", sb->s_mtd->index);
+	return buffer;
+}
 
 static int squashfs_fill_super(struct super_block *sb, void *data, int silent)
 {
@@ -100,8 +110,12 @@ static int squashfs_fill_super(struct super_block *sb, void *data, int silent)
 		goto failure;
 	}
 
-	msblk->devblksize = sb_min_blocksize(sb, BLOCK_SIZE);
-	msblk->devblksize_log2 = ffz(~msblk->devblksize);
+#ifdef CONFIG_BLOCK
+	if (sb->s_bdev) {
+		msblk->devblksize = sb_min_blocksize(sb, BLOCK_SIZE);
+		msblk->devblksize_log2 = ffz(~msblk->devblksize);
+	}
+#endif
 
 	mutex_init(&msblk->read_data_mutex);
 	mutex_init(&msblk->meta_index_mutex);
@@ -125,7 +139,7 @@ static int squashfs_fill_super(struct super_block *sb, void *data, int silent)
 	if (sb->s_magic != SQUASHFS_MAGIC) {
 		if (!silent)
 			ERROR("Can't find a SQUASHFS superblock on %s\n",
-						bdevname(sb->s_bdev, b));
+				devname(sb, b));
 		err = -EINVAL;
 		goto failed_mount;
 	}
@@ -147,10 +161,11 @@ static int squashfs_fill_super(struct super_block *sb, void *data, int silent)
 		ERROR("Xattrs in filesystem, these will be ignored\n");
 
 	/* Check the filesystem does not extend beyond the end of the
-	   block device */
+	   device */
 	msblk->bytes_used = le64_to_cpu(sblk->bytes_used);
 	if (msblk->bytes_used < 0 || msblk->bytes_used >
-			i_size_read(sb->s_bdev->bd_inode))
+			(sb->s_bdev ? i_size_read(sb->s_bdev->bd_inode)
+				: sb->s_mtd->size))
 		goto failed_mount;
 
 	/* Check block size for sanity */
@@ -182,7 +197,7 @@ static int squashfs_fill_super(struct super_block *sb, void *data, int silent)
 	msblk->inodes = le32_to_cpu(sblk->inodes);
 	flags = le16_to_cpu(sblk->flags);
 
-	TRACE("Found valid superblock on %s\n", bdevname(sb->s_bdev, b));
+	TRACE("Found valid superblock on %s\n", devname(sb, b));
 	TRACE("Inodes are %scompressed\n", SQUASHFS_UNCOMPRESSED_INODES(flags)
 				? "un" : "");
 	TRACE("Data is %scompressed\n", SQUASHFS_UNCOMPRESSED_DATA(flags)
@@ -362,10 +377,28 @@ static int squashfs_get_sb(struct file_system_type *fs_type, int flags,
 				const char *dev_name, void *data,
 				struct vfsmount *mnt)
 {
-	return get_sb_bdev(fs_type, flags, dev_name, data, squashfs_fill_super,
-				mnt);
+	int ret;
+
+#ifdef CONFIG_BLOCK
+	ret = get_sb_bdev(fs_type, flags, dev_name, data, squashfs_fill_super, mnt);
+	ERROR("get_sb_bdev returned %x = %d\n", ret, ret);
+	if (!ret)
+		return 0;
+#endif
+	ret = get_sb_mtd(fs_type, flags, dev_name, data, squashfs_fill_super, mnt);
+	ERROR("get_sb_mtd returned %x = %d\n", ret, ret);
+	return ret;
 }
 
+static void kill_squash_super(struct super_block *sb)
+{
+#ifdef CONFIG_BLOCK
+	if (sb->s_bdev)
+		kill_block_super(sb);
+	else
+#endif
+		kill_mtd_super(sb);
+}
 
 static struct kmem_cache *squashfs_inode_cachep;
 
@@ -440,7 +473,7 @@ static struct file_system_type squashfs_fs_type = {
 	.owner = THIS_MODULE,
 	.name = "squashfs",
 	.get_sb = squashfs_get_sb,
-	.kill_sb = kill_block_super,
+	.kill_sb = kill_squash_super,
 	.fs_flags = FS_REQUIRES_DEV
 };
 
-- 
1.5.6.5

