All of lore.kernel.org
 help / color / mirror / Atom feed
From: Adrian McMenamin <adrian@newgolddream.dyndns.info>
To: LKML <linux-kernel@vger.kernel.org>
Cc: linux-fsdevel <linux-fsdevel@vger.kernel.org>,
	viro <viro@zeniv.linux.org.uk>,
	linux-sh <linux-sh@vger.kernel.org>
Subject: Re: [RFC][patch] VMUFAT filesystem - v3
Date: Sat, 11 Apr 2009 22:50:36 +0000	[thread overview]
Message-ID: <1239490236.6523.20.camel@localhost.localdomain> (raw)
In-Reply-To: <1239487891.6523.17.camel@localhost.localdomain>

A further quick clean up.

 fs/Kconfig         |    1 +
 fs/Makefile        |    1 +
 fs/vmufat/Kconfig  |   14 +
 fs/vmufat/Makefile |    7 +
 fs/vmufat/inode.c  | 1394 ++++++++++++++++++++++++++++++++++++++++++++++++++++

Signed-off by: Adrian McMenamin
---
diff --git a/fs/Kconfig b/fs/Kconfig
index 9f7270f..b7cb23d 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -176,6 +176,7 @@ source "fs/romfs/Kconfig"
 source "fs/sysv/Kconfig"
 source "fs/ufs/Kconfig"
 source "fs/exofs/Kconfig"
+source "fs/vmufat/Kconfig"
 
 config NILFS2_FS
 	tristate "NILFS2 file system support (EXPERIMENTAL)"
diff --git a/fs/Makefile b/fs/Makefile
index af6d047..9d2d469 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -124,3 +124,4 @@ obj-$(CONFIG_OCFS2_FS)		+= ocfs2/
 obj-$(CONFIG_BTRFS_FS)		+= btrfs/
 obj-$(CONFIG_GFS2_FS)           += gfs2/
 obj-$(CONFIG_EXOFS_FS)          += exofs/
+obj-$(CONFIG_VMUFAT_FS)		+= vmufat/
diff --git a/fs/vmufat/Kconfig b/fs/vmufat/Kconfig
new file mode 100644
index 0000000..bdad4c6
--- /dev/null
+++ b/fs/vmufat/Kconfig
@@ -0,0 +1,14 @@
+config VMUFAT_FS
+	tristate "Dreamcast VMU FAT filesystem"
+	depends on BLOCK
+	help
+	  This implements the simple FAT type filesystem found on SEGA
+	  Dreamcast visual memory units.
+
+	  Dreamcast users who want to mount their VMUs to view the native
+	  filesystem will say 'Y' here. The filesystem is hardware independent
+	  but is not recommended for any serious use in other circumstances, so
+	  just about everyone else should say 'N'.
+
+	  To compile this as a module say 'M' here. The module will be called
+	  vmufat
diff --git a/fs/vmufat/Makefile b/fs/vmufat/Makefile
new file mode 100644
index 0000000..c716ead
--- /dev/null
+++ b/fs/vmufat/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for VMUFAT filesystem
+#
+
+obj-$(CONFIG_VMUFAT_FS) += vmufat.o
+
+vmufat-objs := inode.o
diff --git a/fs/vmufat/inode.c b/fs/vmufat/inode.c
new file mode 100644
index 0000000..a65ac84
--- /dev/null
+++ b/fs/vmufat/inode.c
@@ -0,0 +1,1394 @@
+/*
+ * inode operations for the VMU file system
+ *
+ * Copyright (C) 2002 - 2009	Adrian McMenamin
+ * Copyright (C) 2002		Paul Mundt
+ *
+ * Released under the terms of the GNU GPL.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
+#include <linux/statfs.h>
+#include <linux/mpage.h>
+#include <linux/bcd.h>
+
+#define VMUFAT_NAMELEN 12
+
+/* GNU utils won't list files with inode num 0 */
+#define VMUFAT_ZEROBLOCK 32768
+#define VMU_BLK_SZ 512
+#define VMUFAT_MAGIC 0x55555555
+
+#define FATFILEEND	0xFFFA
+#define FATEMPTY	0xFFFC
+#define DIRRECLEN	0x20
+#define DIRRECBLK	0x10
+
+static struct kmem_cache *vmufat_inode_cachep;
+static struct kmem_cache *vmufat_blist_cachep;
+static const struct inode_operations vmufat_inode_operations;
+static const struct file_operations vmufat_file_operations;
+static const struct address_space_operations vmufat_address_space_operations;
+static const struct file_operations vmufat_file_dir_operations;
+static const struct super_operations vmufat_super_operations;
+
+static struct inode *vmufat_get_inode(struct super_block *sb, long ino);
+static int vmufat_list_blocks(struct inode *in);
+
+struct memcard {
+	long sb_bnum;
+	long fat_bnum;
+	long fat_len;
+	long dir_bnum;
+	long dir_len;
+	long numblocks;
+};
+
+struct vmufat_block_list {
+	struct list_head b_list;
+	int bno;
+};
+
+struct vmufat_inode {
+	struct vmufat_block_list blocks;
+	int nblcks;
+	struct inode vfs_inode;
+};
+
+static struct vmufat_inode *VMUFAT_I(struct inode *in)
+{
+	return container_of(in, struct vmufat_inode, vfs_inode);
+}
+
+struct vmufat_file_info {
+	u8 ftype;
+	u8 copy_pro;
+	u16 fblk;
+	char fname[VMUFATNAMELEN];
+};
+
+static inline int vmufat_index(int fno)
+{
+	return (fno % DIRRECBLK) * DIRRECLEN;
+}
+
+static inline int vmufat_index_16(int fno)
+{
+	return (fno % DIRRECBLK) * DIRECLEN / 2;
+}
+
+/* VMU hardware is flaky, so let's compensate for that
+ * without losing hardare independence -
+ * as it is likely to be where this filesystem is used
+ */
+static inline struct buffer_head *vmufat_sb_bread(struct super_block *sb,
+	sector_t block)
+{
+	struct buffer_head *bh;
+	bh = sb_bread(sb, block);
+	if (bh)
+		return bh;
+	return sb_bread(sb, block);
+}
+
+/* Linear day numbers of the respective 1sts in non-leap years. */
+static int day_n[] +    {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
+
+static struct dentry *vmufat_inode_lookup(struct inode *in, struct dentry *dent,
+	struct nameidata *nd)
+{
+	struct super_block *sb;
+	struct memcard *vmudetails;
+	struct buffer_head *bh;
+	struct inode *ino;
+	char name[VMUFAT_NAMELEN];
+	long blck_read;
+	int error = 0, fno = 0;
+
+	if (dent->d_name.len > VMUFAT_NAMELEN) {
+		error = -ENAMETOOLONG;
+		goto out;
+	}
+
+	sb = in->i_sb;
+	vmudetails = sb->s_fs_info;
+	blck_read = vmudetails->dir_bnum;
+
+	bh = vmufat_sb_bread(sb, blck_read);
+	if (!bh) {
+		error = -EIO;
+		goto out;
+	}
+
+	do {
+		/* Have we got a file? */
+		if (bh->b_data[vmufat_index(fno)] = 0)
+			goto next;
+
+		/* get file name */
+		memcpy(name,
+		       bh->b_data + 4 + vmufat_index(fno), VMUFAT_NAMELEN);
+		/* do names match ?*/
+		if (memcmp(dent->d_name.name, name, dent->d_name.len) = 0) {
+			/* read the inode number from the directory */
+			ino = vmufat_get_inode(sb,
+				le16_to_cpu(((u16 *) bh->b_data)
+					[1 + vmufat_index_16(fno)]));
+			if (!ino) {
+				error = -EACCES;
+				goto release_bh;
+			}
+			if (IS_ERR(ino)) {
+				error = PTR_ERR(ino);
+				goto release_bh;
+			}
+			/* return the entry */
+			d_add(dent, ino);
+			goto release_bh;
+		}
+next:
+		/* did not match, so try the next file */
+		fno++;
+		/* do we need to move to the next block in the directory? */
+		if (fno >= 0x10) {
+			fno = 0;
+			blck_read--;
+			if (blck_read <+				vmudetails->dir_bnum - vmudetails->dir_len) {
+				d_add(dent, NULL);
+				break;
+			}
+			brelse(bh);
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh) {
+				error = -EIO;
+				goto out;
+			}
+		}
+	} while (1);
+
+release_bh:
+	brelse(bh);
+out:
+	return ERR_PTR(error);
+}
+
+/*
+ * Find a free block in the FAT and mark it
+ * as the end of a file
+ */
+static int vmufat_find_free(struct super_block *sb)
+{
+	struct memcard *vmudetails = sb->s_fs_info;
+	int nextblock, x;
+	u16 fatdata;
+	struct buffer_head *bh_fat;
+	int error = 0;
+
+	nextblock = vmudetails->fat_bnum + vmudetails->fat_len - 1;
+	x = VMU_BLK_SZ;
+	bh_fat +	    vmufat_sb_bread(sb, nextblock);
+	if (!bh_fat) {
+		error = -EIO;
+		goto fail;
+	}
+
+	do {
+		fatdata = ((u16 *) bh_fat->b_data)[x];
+		if (fatdata = FATEMPTY)
+			break;	/*empty block */
+		if (--x < 0) {
+			put_bh(bh_fat);
+			if (--nextblock >= vmudetails->fat_bnum) {
+				x = VMU_BLK_SZ;
+				bh_fat = vmufat_sb_bread(sb, nextblock);
+				if (!bh_fat) {
+					error = -EIO;
+					goto fail;
+				}
+			} else
+				break;
+		}
+	} while (1);
+
+	if (nextblock < vmudetails->fat_bnum) {
+		printk(KERN_ERR "VMUFAT: device is full\n");
+		error = -ENOSPC;
+		put_bh(bh_fat);
+		goto fail;
+	}
+	put_bh(bh_fat);
+	return  x + (nextblock - vmudetails->fat_bnum) * VMU_BLK_SZ;
+
+fail:
+	return error;
+}
+
+/* read the FAT for a given block */
+static u16 vmufat_get_fat(struct super_block *sb, long block)
+{
+	struct memcard *vmudetails = sb->s_fs_info;
+	struct buffer_head *bh;
+	int offset;
+	u16 block_content;
+
+	offset = block/(VMU_BLK_SZ/2);
+	if (offset >= vmudetails->fat_len)
+		return 0xFFFE;
+
+	bh = vmufat_sb_bread(sb, offset + 1 +
+		vmudetails->fat_bnum - vmudetails->fat_len);
+	if (!bh)
+		return 0xFFFF;
+
+	block_content = ((u16 *)bh->b_data)[block % (VMU_BLK_SZ / 2)];
+	put_bh(bh);
+	return block_content;
+}
+
+/* set the FAT for a given block */
+static int vmufat_set_fat(struct super_block *sb, long block, u16 set)
+{
+	struct memcard *vmudetails = sb->s_fs_info;
+	struct buffer_head *bh;
+	int offset;
+
+	offset = block/(VMU_BLK_SZ/2);
+	if (offset >= vmudetails->fat_len)
+		return -EINVAL;
+
+	bh = vmufat_sb_bread(sb, offset + 1 +
+		vmudetails->fat_bnum - vmudetails->fat_len);
+	if (!bh)
+		return -EIO;
+
+	((u16 *) bh->b_data)[block % (VMU_BLK_SZ / 2)] = set;
+	mark_buffer_dirty(bh);
+	put_bh(bh);
+	return 0;
+}
+
+static int vmufat_inode_create(struct inode *dir, struct dentry *de,
+		int imode, struct nameidata *nd)
+{
+	/* Create an inode */
+	int x = 0, y, z, error = 0, q;
+	long blck_read;
+	struct inode *inode;
+	struct super_block *sb;
+	struct memcard *vmudetails;
+	struct buffer_head *bh_fat = NULL, *bh;
+	unsigned long unix_date;
+	int year, day, nl_day, month;	/*inspired by FAT driver */
+	u8 century, u8year;
+
+	if (de->d_name.len > VMUFAT_NAMELEN)
+		return -ENAMETOOLONG;
+
+	sb = dir->i_sb;
+	vmudetails = sb->s_fs_info;
+
+	inode = new_inode(sb);
+	if (!inode) {
+		error = -ENOSPC;
+		goto out;
+	}
+
+	/* Walk through blocks looking for place to write
+	* Is this an executible file? */
+	if (imode & 73) {	/*Octal 111 */
+		inode->i_ino = VMUFAT_ZEROBLOCK;
+		/* But this already allocated? */
+		if (vmufat_get_fat(sb, 0) != FATEMPTY) {
+			printk(KERN_ERR
+				"VMUFAT: cannot write executible file to"
+				" filesystem - block 0 already allocated.\n");
+			error = -ENOSPC;
+			goto clean_inode;
+		}
+		q = 0;
+	} else {
+		q = vmufat_find_free(sb);
+		if (q < 0) {
+			error = q;
+			goto clean_inode;
+		}
+		inode->i_ino = q;
+	}
+
+	error = vmufat_set_fat(sb, q, FATFILEEND);
+	if (error)
+		goto clean_inode;
+
+	inode->i_uid = 0;
+	inode->i_gid = 0;
+	inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
+	inode->i_mode = imode;
+	inode->i_blocks = 1;
+	inode->i_sb = sb;
+	insert_inode_hash(inode);
+	inode->i_op = &vmufat_inode_operations;
+	inode->i_fop = &vmufat_file_operations;
+	inode->i_mapping->a_ops = &vmufat_address_space_operations;
+
+	/* Write to the directory
+	* Now search for space for the directory entry */
+	blck_read = vmudetails->dir_bnum;
+	bh = vmufat_sb_bread(sb, blck_read);
+	if (!bh) {
+		error = -EIO;
+		goto clean_inode;
+	}
+
+	for (y = 0; y < (vmudetails->dir_len * 0x10); y++) {
+		if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) {
+			brelse(bh);
+			blck_read--;
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh) {
+				error = -EIO;
+				goto clean_fat;
+			}
+		}
+		if (((bh->b_data)[vmufat_index(y)]) = 0)
+			break;
+	}
+	/* Have the directory entry
+	 * so now update it */
+	z = vmufat_index(y);
+	if (inode->i_ino != VMUFAT_ZEROBLOCK)
+		bh->b_data[z] = 0x33;	/* data file */
+	else
+		bh->b_data[z] = 0xcc;
+
+	if ((bh->b_data[z + 1] != (char) 0x00) &&
+		(bh->b_data[z + 1] != (char) 0xff))
+		bh->b_data[z + 1] = (char) 0x00;
+
+	if (inode->i_ino != VMUFAT_ZEROBLOCK) {
+		((u16 *) bh->b_data)[z / 2 + 1] +		    cpu_to_le16(inode->i_ino);
+		((u16 *) bh->b_data)[z / 2 + 0x0D] = 0;
+	} else {
+		((u16 *) bh->b_data)[z / 2 + 1] = 0;
+		((u16 *) bh->b_data)[z / 2 + 0x0D] = 1;
+	}
+
+	/* Name */
+	memset((char *) (bh->b_data + z + 0x04), '\0', 0x0C);
+	memcpy((char *) (bh->b_data + z + 0x04), ((de->d_name).name),
+		de->d_name.len);
+
+	/* BCD timestamp it */
+	unix_date = CURRENT_TIME.tv_sec;
+	day = unix_date / 86400 - 3652;
+	year = day / 365;
+
+	if ((year + 3) / 4 + 365 * year > day)
+		year--;
+
+	day -= (year + 3) / 4 + 365 * year;
+	if (day = 59 && !(year & 3)) {
+		nl_day = day;
+		month = 2;
+	} else {
+		nl_day = (year & 3) || day <= 59 ? day : day - 1;
+		for (month = 0; month < 12; month++)
+			if (day_n[month] > nl_day)
+				break;
+	}
+
+	century = 19;
+	if (year > 19)
+		century = 20;
+
+	bh->b_data[z + 0x10] = bin2bcd(century);
+	u8year = year + 80;
+	if (u8year > 99)
+		u8year = u8year - 100;
+
+	bh->b_data[z + 0x11] = bin2bcd(u8year);
+	bh->b_data[z + 0x12] = bin2bcd(month);
+	bh->b_data[z + 0x13] +	    bin2bcd(day - day_n[month - 1] + 1);
+	bh->b_data[z + 0x14] +	    bin2bcd((unix_date / 3600) % 24);
+	bh->b_data[z + 0x15] = bin2bcd((unix_date / 60) % 60);
+	bh->b_data[z + 0x16] = bin2bcd(unix_date % 60);
+
+	((u16 *) bh->b_data)[z / 2 + 0x0C] +	    cpu_to_le16(inode->i_blocks);
+
+	inode->i_mtime.tv_sec = unix_date;
+	mark_buffer_dirty(bh);
+	brelse(bh);
+
+	error = vmufat_list_blocks(inode);
+	if (error)
+		goto clean_fat;
+
+	d_instantiate(de, inode);
+	brelse(bh_fat);
+	return error;
+
+clean_fat:
+	((u16 *)bh_fat->b_data)[x] = FATEMPTY;
+	mark_buffer_dirty(bh_fat);
+	brelse(bh_fat);
+clean_inode:
+	iput(inode);
+out:
+	return error;
+}
+
+static int vmufat_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+	int filenamelen, i, error = 0;
+	struct vmufat_file_info *saved_file = NULL;
+	struct dentry *dentry = filp->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	struct super_block *sb = inode->i_sb;
+	struct memcard *vmudetails = sb->s_fs_info;
+	struct buffer_head *bh;
+
+	int blck_read = vmudetails->dir_bnum;
+	bh = vmufat_sb_bread(sb, blck_read);
+	if (!bh) {
+		error = -EIO;
+		goto out;
+	}
+
+	i = filp->f_pos;
+
+	/* handle . for this directory and .. for parent */
+	switch ((unsigned int) filp->f_pos) {
+	case 0:
+		if (filldir(dirent, ".", 1, i++, inode->i_ino, DT_DIR) < 0)
+			goto finish;
+
+		filp->f_pos++;
+	case 1:
+		if (filldir(dirent, "..", 2, i++,
+			    dentry->d_parent->d_inode->i_ino, DT_DIR) < 0)
+			goto finish;
+
+		filp->f_pos++;
+	default:
+		break;
+	}
+
+	/* trap reading beyond the end of the directory */
+	if ((i - 2) > (vmudetails->dir_len * 0x10)) {
+		error = -EINVAL;
+		goto release_bh;
+	}
+
+	saved_file +	    kmalloc(sizeof(struct vmufat_file_info), GFP_KERNEL);
+	if (!saved_file) {
+		error = -ENOMEM;
+		goto release_bh;
+	}
+
+	do {
+		if ((i - 2) / 0x10 > (vmudetails->dir_bnum - blck_read)) {
+			/* move to next block in directory */
+			blck_read--;
+			if (vmudetails->dir_bnum - vmudetails->dir_len <+				blck_read)
+				break;
+			brelse(bh);
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh) {
+				kfree(saved_file);
+				error = -EIO;
+				goto out;
+			}
+		}
+
+		saved_file->ftype = bh->b_data[vmufat_index(i - 2)];
+
+		if (saved_file->ftype = 0)
+			break;
+
+		saved_file->fblk +		    le16_to_cpu(((u16 *) bh->b_data)[1 +
+			vmufat_index_16(i - 2)]);
+		if (saved_file->fblk = 0)
+			saved_file->fblk = VMUFAT_ZEROBLOCK;
+
+		memcpy(saved_file->fname,
+		       bh->b_data + 4 + vmufat_index(i - 2), VMUFAT_NAMELEN);
+		filenamelen = strlen(saved_file->fname);
+		if (filenamelen > VMUFAT_NAMELEN)
+			filenamelen = VMUFAT_NAMELEN;
+		if (filldir
+		    (dirent, saved_file->fname, filenamelen, i++,
+		     saved_file->fblk, DT_REG) < 0) {
+			goto finish;
+		}
+
+		filp->f_pos++;
+	} while (1);
+
+finish:
+	kfree(saved_file);
+release_bh:
+	brelse(bh);
+out:
+	return error;
+}
+
+
+
+static long vmufat_get_date(struct buffer_head *bh, int offset)
+{
+	int century, year, month, day, hour, minute, second;
+
+	century = bcd2bin(bh->b_data[offset++]);
+	year = bcd2bin(bh->b_data[offset++]);
+	month = bcd2bin(bh->b_data[offset++]);
+	day = bcd2bin(bh->b_data[offset++]);
+	hour = bcd2bin(bh->b_data[offset++]);
+	minute = bcd2bin(bh->b_data[offset++]);
+	second = bcd2bin(bh->b_data[offset]);
+
+	return 	mktime(century * 100 + year, month, day, hour, minute,
+			second);
+}
+
+static struct inode *vmufat_alloc_inode(struct super_block *sb)
+{
+	struct vmufat_inode *vi = kmem_cache_alloc(vmufat_inode_cachep,
+		GFP_KERNEL);
+
+	if (!vi)
+		return NULL;
+	INIT_LIST_HEAD(&vi->blocks.b_list);
+	return &vi->vfs_inode;
+}
+
+static void vmufat_destroy_inode(struct inode *in)
+{
+	struct vmufat_inode *vi = VMUFAT_I(in);
+	struct vmufat_block_list *vb;
+	struct list_head *iter, *iter2;
+
+	list_for_each_safe(iter, iter2, &vi->blocks.b_list) {
+		vb = list_entry(iter, struct vmufat_block_list, b_list);
+		list_del(iter);
+		kmem_cache_free(vmufat_blist_cachep, vb);
+	}
+	kmem_cache_free(vmufat_inode_cachep, vi);
+}
+
+static int vmufat_list_blocks(struct inode *in)
+{
+	struct vmufat_inode *vi = VMUFAT_I(in);
+	struct super_block *sb = in->i_sb;
+	long nextblock;
+	long ino = in->i_ino;
+	int fat_seg;
+	struct buffer_head *bh;
+	struct memcard *vmudetails;
+	int error;
+	struct list_head *iter, *iter2;
+	struct vmufat_block_list *vbl, *nvbl;
+	u16 fatdata;
+
+	vmudetails = sb->s_fs_info;
+	nextblock = ino;
+	fat_seg = nextblock / (sb->s_blocksize / 2);
+	bh = vmufat_sb_bread(sb, vmudetails->fat_bnum -
+		(vmudetails->fat_len - 1) + fat_seg);
+	if (!bh) {
+		error = -EIO;
+		goto out;
+	}
+
+	/* Delete any previous list of blocks */
+	list_for_each_safe(iter, iter2, &vi->blocks.b_list) {
+		vbl = list_entry(iter, struct vmufat_block_list, b_list);
+		list_del(iter);
+		kmem_cache_free(vmufat_blist_cachep, vbl);
+	}
+	vi->nblcks = 0;
+	do {
+		vbl = kmem_cache_alloc(vmufat_blist_cachep,
+			GFP_KERNEL);
+		if (!vbl) {
+			error = -ENOMEM;
+			goto unwind_out;
+		}
+		INIT_LIST_HEAD(&vbl->b_list);
+		vbl->bno = nextblock;
+		list_add_tail(&vbl->b_list, &vi->blocks.b_list);
+		vi->nblcks++;
+
+		/* Find next block in the FAT - if there is one */
+		fatdata = ((u16 *) bh->b_data)
+			[nextblock % (sb->s_blocksize / 2)];
+		if (fatdata = FATEMPTY) {
+			printk(KERN_WARNING "VMUFAT: FAT table appears to have"
+				" been corrupted.\n");
+			error = -EIO;
+			goto unwind_out;
+		}
+		if (fatdata = FATFILEEND)
+			break;	/*end of file */
+		if (fatdata / (sb->s_blocksize / 2) != fat_seg) {
+			brelse(bh);
+			fat_seg = fatdata / (sb->s_blocksize / 2);
+			bh = vmufat_sb_bread(sb, vmudetails->fat_bnum -
+				(vmudetails->fat_len - 1) + fat_seg);
+			if (!bh) {
+				error = -ENOMEM;
+				goto unwind_out;
+			}
+		}
+		nextblock = fatdata;
+	} while (1);
+	brelse(bh);
+	return 0;
+
+unwind_out:
+	list_for_each_entry_safe(vbl, nvbl, &vi->blocks.b_list, b_list) {
+		list_del_init(&vbl->b_list);
+		kmem_cache_free(vmufat_blist_cachep, vbl);
+	}
+out:
+	return error;
+}
+
+static struct inode *vmufat_get_inode(struct super_block *sb, long ino)
+{
+	struct buffer_head *bh;
+	int error, blck_read, y, z;
+	struct inode *inode = iget_locked(sb, ino);
+	struct memcard *vmudetails = sb->s_fs_info;
+	long superblock_bno = vmudetails->sb_bnum;
+
+	if (inode && (inode->i_state & I_NEW)) {
+		inode->i_uid = 0;
+		inode->i_gid = 0;
+		inode->i_mode &= ~S_IFMT;
+		if (inode->i_ino = superblock_bno) {
+			bh = vmufat_sb_bread(sb, inode->i_ino);
+			if (!bh) {
+				error = -EIO;
+				goto failed;
+			}
+			inode->i_ctime.tv_sec = inode->i_mtime.tv_sec +				vmufat_get_date(bh, 0x30);
+
+			/* Mark as a directory */
+			inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO;
+
+			inode->i_op = &vmufat_inode_operations;
+			inode->i_fop = &vmufat_file_dir_operations;
+		} else {
+			blck_read = vmudetails->dir_bnum;
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh) {
+				error = -EIO;
+				goto failed;
+			}
+
+			/* Mark file as regular type */
+			inode->i_mode = S_IFREG;
+
+			/* Scan through the directory to find matching file */
+			for (y = 0; y < vmudetails->numblocks; y++) {
+				if ((y / 0x10) >
+				    (vmudetails->dir_bnum - blck_read)) {
+					brelse(bh);
+					blck_read--;
+					bh = vmufat_sb_bread(sb, blck_read);
+					if (!bh) {
+						error = -EIO;
+						goto failed;
+					}
+				}
+				if (le16_to_cpu
+				    (((u16 *) bh->b_data)[(y % 0x10) * 0x10 +
+						    0x01]) = ino)
+					break;
+				}
+
+				if (y >= vmudetails->numblocks) {
+					brelse(bh);
+					printk(KERN_INFO
+						"vmufat: could not find this "
+						"file on filesystem\n");
+				error = -ENOENT;
+				goto failed;
+			}
+
+			/* identified the correct directory entry */
+			z = vmufat_index(y);
+			inode->i_ctime.tv_sec = inode->i_mtime.tv_sec +				vmufat_get_date(bh, z + 0x10);
+
+			/* Execute if a game, write if not copy protected */
+			inode->i_mode &= ~(S_IWUGO | S_IXUGO);
+			inode->i_mode |= S_IRUGO;
+
+			/* Mode - is it write protected? */
+			if ((((u8 *) bh->b_data)[0x01 + z] =
+			     0x00) & ~(sb->s_flags & MS_RDONLY))
+				inode->i_mode |= S_IWUGO;
+			/* Is file executible - ie a game */
+			if ((((u8 *) bh->b_data)[z] =
+			     0xcc) & ~(sb->s_flags & MS_NOEXEC))
+				inode->i_mode |= S_IXUGO;
+
+			inode->i_fop = &vmufat_file_operations;
+
+			inode->i_blocks +			    le16_to_cpu(((u16 *) bh->b_data)
+				[vmufat_index_16(y) + 0x0C]);
+			inode->i_size = inode->i_blocks * sb->s_blocksize;
+
+			inode->i_mapping->a_ops +				&vmufat_address_space_operations;
+			inode->i_op = &vmufat_inode_operations;
+			inode->i_fop = &vmufat_file_operations;
+			error = vmufat_list_blocks(inode);
+			if (error)
+				goto failed;
+		}
+		inode->i_atime = CURRENT_TIME;
+		unlock_new_inode(inode);
+	}
+
+	return inode;
+
+failed:
+	iget_failed(inode);
+	return ERR_PTR(error);
+}
+
+static void vmufat_put_super(struct super_block *sb)
+{
+	sb->s_dev = 0;
+	kfree(sb->s_fs_info);
+}
+
+static int vmufat_scan(struct super_block *sb, struct kstatfs *buf)
+{
+	int error = 0;
+	int free = 0;
+	int x;
+	u16 fatdata;
+	struct buffer_head *bh_fat;
+	struct memcard *vmudetails = sb->s_fs_info;
+	long nextblock;
+
+	/* Look through the FAT */
+	nextblock = vmudetails->fat_bnum + vmudetails->fat_len - 1;
+	x = sb->s_blocksize;
+	bh_fat = vmufat_sb_bread(sb, nextblock);
+	if (!bh_fat) {
+		error = -EIO;
+		goto out;
+	}
+
+	do {
+		fatdata = ((u16 *) bh_fat->b_data)[x];
+		if (fatdata = FATEMPTY)
+			free++;
+		if (--x < 0) {
+			brelse(bh_fat);
+			if (--nextblock >= vmudetails->fat_bnum) {
+				x = sb->s_blocksize;
+				bh_fat = vmufat_sb_bread(sb, nextblock);
+				if (!bh_fat) {
+					error = -EIO;
+					goto out;
+				}
+			} else
+				break;
+		}
+	} while (1);
+
+	buf->f_bfree = free;
+	buf->f_bavail = free;
+	buf->f_blocks = vmudetails->numblocks;
+
+out:
+	return error;
+}
+
+static int vmufat_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+	struct super_block *sb = dentry->d_sb;
+	int error;
+
+	error = vmufat_scan(sb, buf);
+	if (error)
+		return error;
+	buf->f_type = VMUFAT_MAGIC;
+	buf->f_bsize = sb->s_blocksize;
+	buf->f_namelen = VMUFAT_NAMELEN;
+
+	return 0;
+}
+
+/*
+ * Delete inode by marking space as free in FAT
+ * no need to waste time and effort by actually
+ * wiping underlying data on media
+ */
+static void vmufat_delete_inode(struct inode *in)
+{
+	struct buffer_head *bh, *bh_old;
+	struct super_block *sb;
+	struct memcard *vmudetails;
+	int z, y, x, w, v, blck_read, error;
+	u16 nextblock, fatdata;
+
+	if (in->i_ino = VMUFAT_ZEROBLOCK)
+		in->i_ino = 0;
+	sb = in->i_sb;
+	vmudetails = sb->s_fs_info;
+	if (in->i_ino > vmudetails->fat_len * sb->s_blocksize / 2) {
+		printk(KERN_ERR "vmufat: attempting to delete"
+			"inode beyond device size");
+		return;
+	}
+
+	/* Seek start of file and wander through FAT
+ 	 * Marking the blocks as unallocated */
+	nextblock = in->i_ino;
+	do {
+		fatdata = vmufat_get_fat(sb, nextblock);
+		error = vmufat_set_fat(sb, nextblock, FATEMPTY);
+		if (error)
+			goto failure;
+		if (fatdata = FATFILEEND)
+			break;
+		nextblock = fatdata;
+	} while (1);
+
+	/* Now clean the directory entry
+	 * Have to wander through this
+	 * to find the appropriate entry */
+	blck_read = vmudetails->dir_bnum;
+	bh = vmufat_sb_bread(sb, blck_read);
+	if (!bh)
+		goto failure;
+
+	for (y = 0; y < (vmudetails->dir_len * 0x10); y++) {
+		if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) {
+			brelse(bh);
+			blck_read--;
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh)
+				goto failure;
+		}
+		if (le16_to_cpu
+		    (((u16 *) bh->b_data)[(y % 0x10) * 0x10 +
+					    0x01]) = in->i_ino)
+			break;
+	}
+
+	/* Found directory entry - so NULL it now */
+	w = vmufat_index_16(y);
+	for (z = 0; z < 0x10; z++)
+		((u16 *) bh->b_data)[w + z] = 0;
+	mark_buffer_dirty(bh);
+	/* Replace it with another entry - if one exists */
+	x = y;
+	for (y = x + 1; y < (vmudetails->dir_len * 0x10); y++) {
+		if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) {
+			brelse(bh);
+			blck_read--;
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh)
+				return;
+		}
+		/* look for the end of entries in the directory */
+		if (bh->b_data[vmufat_index(y)] = 0) {
+			y--;
+			if (y = x)
+				break;	/* At the end in any case */
+			brelse(bh);
+
+			/* force read of correct block */
+			bh = vmufat_sb_bread(sb, vmudetails->dir_bnum -
+				y / 0x10);
+			if (!bh)
+				goto failure;
+			bh_old +			    vmufat_sb_bread(sb, vmudetails->dir_bnum -
+				x / 0x10);
+			if (!bh_old) {
+				brelse(bh);
+				goto failure;
+			}
+
+			/*
+ 			 * Copy final directory entry into space created
+			 * by the deletion of the inode
+			 */
+			w = vmufat_index_16(x);
+			v = vmufat_index_16(y);
+			for (z = 0; z < 0x10; z++) {
+				((u16 *) bh_old->b_data)[w + z] +					((u16 *) bh->b_data)[v + z];
+				((u16 *) bh->b_data)[v + z] = 0;
+			}
+			mark_buffer_dirty(bh);
+			if (x/0x10 != y/0x10)
+				mark_buffer_dirty(bh_old);
+			brelse(bh_old);
+			break;
+		}
+	}
+
+	brelse(bh);
+	return;
+
+failure:
+	printk(KERN_ERR "vmufat: Failure to read device,"
+		" could not delete inode - filesystem may be damaged\n");
+	return;
+}
+
+/*
+ * vmufat_unlink - delete a file pointed to
+ * by the dentry (only one directory in a
+ * vmufat fs so safe to ignore the inode
+ * upplied here
+ */
+static int vmufat_unlink(struct inode *dir, struct dentry *dentry)
+{
+	struct inode *in;
+
+	in = dentry->d_inode;
+	if (!in)
+		return -EIO;
+	vmufat_delete_inode(in);
+	return 0;
+}
+
+static int vmufat_get_block(struct inode *inode, sector_t iblock,
+	struct buffer_head *bh_result, int create)
+{
+	struct vmufat_inode *vin = VMUFAT_I(inode);
+	struct vmufat_block_list *vlist = &vin->blocks;
+	struct vmufat_block_list *vblk;
+	struct super_block *sb = inode->i_sb;
+	int cural;
+	int finblk, nxtblk, exeblk;
+	struct list_head *iter;
+	sector_t cntdwn = iblock;
+	sector_t phys;
+	int error = -EINVAL;
+
+	/* quick sanity check */
+	if (vin->nblcks <= 0)
+		goto out;
+	if (iblock < vin->nblcks) {
+		/* block is already here so read it into the buffer head */
+		list_for_each(iter, &vlist->b_list) {
+			if (cntdwn-- = 0)
+				break;
+		}
+		vblk = list_entry(iter, struct vmufat_block_list, b_list);
+		clear_buffer_new(bh_result);
+		error = 0;
+		phys = vblk->bno;
+		goto got_it;
+	}
+	if (!create) {
+		error = -EINVAL;
+		goto out;
+	}
+	/*
+	 * check not looking for a block too far
+	 * beyond the end of the existing file
+	 */
+	if (iblock > vin->nblcks) {
+		error = -EINVAL;
+		goto out;
+	}
+
+	/* if looking for a block that is not current - allocate it*/
+	cural = vin->nblcks;
+	list_for_each(iter, &vlist->b_list) {
+		if (cural-- = 1)
+			break;
+	}
+	vblk = list_entry(iter, struct vmufat_block_list, b_list);
+	finblk = vblk->bno;
+
+	/* Exec files have to be linear */
+	if (inode->i_ino = 0) {
+		exeblk = vmufat_get_fat(sb, finblk + 1);
+		if (exeblk != FATEMPTY) {
+			printk(KERN_WARNING "Cannot allocate linear space "
+				"needed for executible\n");
+			error = -ENOSPC;
+			goto out;
+		}
+		nxtblk = finblk + 1;
+	} else {
+		nxtblk = vmufat_find_free(sb);
+		if (nxtblk < 0) {
+			error = nxtblk;
+			goto out;
+		}
+	}
+	error = vmufat_set_fat(sb, finblk, nxtblk);
+	if (error)
+		goto out;
+	error = vmufat_set_fat(sb, nxtblk, FATFILEEND);
+	if (error)
+		goto out;
+	error = vmufat_list_blocks(inode);
+	mark_inode_dirty(inode);
+	if (error)
+		goto out;
+	set_buffer_new(bh_result);
+	phys = nxtblk;
+	error = 0;
+got_it:
+	map_bh(bh_result, sb, phys);
+out:
+	return error;
+}
+
+static int vmufat_writepage(struct page *page, struct writeback_control *wbc)
+{
+	return block_write_full_page(page, vmufat_get_block, wbc);
+}
+
+static int vmufat_write_begin(struct file *file, struct address_space *mapping,
+		loff_t pos, unsigned len, unsigned flags,
+		struct page **pagep, void **fsdata)
+{
+	*pagep = NULL;
+	return block_write_begin(file, mapping, pos, len, flags, pagep, fsdata,
+		vmufat_get_block);
+}
+
+static int vmufat_readpage(struct file *file, struct page *page)
+{
+	return block_read_full_page(page, vmufat_get_block);
+}
+
+/*
+ * There are no inodes on the medium - vmufat_write_inode
+ * updates the directory entry
+ */
+static int vmufat_write_inode(struct inode *in, int wait)
+{
+	struct buffer_head *bh;
+	__u8 century, u8year;
+	unsigned long inode_num;
+	int y, blck_read, z;
+	unsigned long unix_date;
+	int year, day, nl_day, month;
+	struct super_block *sb = in->i_sb;
+	struct memcard *vmudetails +	    ((struct memcard *) sb->s_fs_info);
+
+	/* As most real world devices are flash
+ 	 * we won't update the superblock every
+ 	 * time we change something else on the fs
+ 	 * - it is ugly but a sensible compromise
+ 	 */
+	if (in->i_ino = vmudetails->sb_bnum)
+		return 0;
+
+	if (in->i_ino = VMUFAT_ZEROBLOCK)
+		inode_num = 0;
+	else
+		inode_num = in->i_ino;
+
+	/* update the directory and inode details */
+	/* Now search for the directory entry */
+	blck_read = vmudetails->dir_bnum;
+	bh = vmufat_sb_bread(sb, blck_read);
+	if (!bh)
+		return -EIO;
+
+	for (y = 0; y < vmudetails->numblocks; y++) {
+		if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) {
+			brelse(bh);
+			blck_read--;
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh)
+				return -EIO;
+		}
+		if (le16_to_cpu
+		    (((__u16 *) bh->b_data)[vmufat_index_16(y) +
+					    0x01]) = inode_num)
+			break;
+	}
+	/* Have the directory entry
+	 * so now update it */
+	z = (y % 0x10) * 0x20;
+	if (inode_num != 0)
+		bh->b_data[z] = 0x33;	/* data file */
+	else
+		bh->b_data[z] = 0xcc;
+	if (bh->b_data[z + 1] !=  0
+	    && bh->b_data[z + 1] != (char) 0xff)
+		bh->b_data[z + 1] = 0;
+	((__u16 *) bh->b_data)[z / 2 + 1] = cpu_to_le16(inode_num);
+
+	/* BCD timestamp it */
+	unix_date = CURRENT_TIME.tv_sec;
+	day = unix_date / 86400 - 3652;
+	year = day / 365;
+	if ((year + 3) / 4 + 365 * year > day)
+		year--;
+	day -= (year + 3) / 4 + 365 * year;
+	if (day = 59 && !(year & 3)) {
+		nl_day = day;
+		month = 2;
+	} else {
+		nl_day = (year & 3) || day <= 59 ? day : day - 1;
+		for (month = 0; month < 12; month++)
+			if (day_n[month] > nl_day)
+				break;
+	}
+
+	century = 19;
+	if (year > 19)
+		century = 20;
+	bh->b_data[z + 0x10] = bin2bcd(century);
+	u8year = year + 80;
+	if (u8year > 99)
+		u8year = u8year - 100;
+	bh->b_data[z + 0x11] = bin2bcd(u8year);
+	bh->b_data[z + 0x12] = bin2bcd(month);
+	bh->b_data[z + 0x13] = bin2bcd(day - day_n[month - 1] + 1);
+	bh->b_data[z + 0x14] = bin2bcd((unix_date / 3600) % 24);
+	bh->b_data[z + 0x15] = bin2bcd((unix_date / 60) % 60);
+	bh->b_data[z + 0x16] = bin2bcd(unix_date % 60);
+
+	((__u16 *) bh->b_data)[z / 2 + 0x0C] = cpu_to_le16(in->i_blocks);
+	if (inode_num != 0)
+		((__u16 *) bh->b_data)[z / 2 + 0x0D] = 0;
+	else /* game */
+		((__u16 *) bh->b_data)[z / 2 + 0x0D] = cpu_to_le16(1);
+	in->i_mtime.tv_sec = unix_date;
+	mark_buffer_dirty(bh);
+	brelse(bh);
+	return 0;
+}
+
+static int check_sb_format(struct buffer_head *bh)
+{
+	u32 s_magic = VMUFAT_MAGIC;
+
+	if (!(((u32 *) bh->b_data)[0] = s_magic &&
+		((u32 *) bh->b_data)[1] = s_magic &&
+		((u32 *) bh->b_data)[2] = s_magic &&
+		((u32 *) bh->b_data)[3] = s_magic))
+		return 0;
+	else
+		return 1;
+}
+
+static void init_once(void *foo)
+{
+	struct vmufat_inode *vi = foo;
+
+	vi->nblcks = 0;
+	inode_init_once(&vi->vfs_inode);
+}
+
+
+static int init_inodecache(void)
+{
+	vmufat_inode_cachep = kmem_cache_create("vmufat_inode_cache",
+		sizeof(struct vmufat_inode), 0,
+			SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD, init_once);
+	if (!vmufat_inode_cachep)
+		return -ENOMEM;
+
+	vmufat_blist_cachep = kmem_cache_create("vmufat_blocklist_cache",
+		sizeof(struct vmufat_block_list), 0, SLAB_MEM_SPREAD, NULL);
+	if (!vmufat_blist_cachep) {
+		kmem_cache_destroy(vmufat_inode_cachep);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+static void destroy_inodecache(void)
+{
+	kmem_cache_destroy(vmufat_blist_cachep);
+	kmem_cache_destroy(vmufat_inode_cachep);
+}
+
+static int vmufat_fill_super(struct super_block *sb,
+					    void *data, int silent)
+{
+	/*Search for the superblock - assuming likely sizes are
+	* 128, 256, 512 and 1024 blocks */
+
+	struct buffer_head *bh;
+	struct memcard *vmudata;
+	int z;
+	struct inode *root_i;
+	int ret = -EINVAL;
+
+	sb_set_blocksize(sb, VMU_BLK_SZ);
+
+	/* Have to try lowest number first to avoid oops */
+	for (z = 128; z < 1024; z = z * 2) {
+		bh = vmufat_sb_bread(sb, z - 1);
+		if (!bh) {
+			ret = -EIO;
+			goto out;
+		}
+		if (check_sb_format(bh))
+			break;
+		brelse(bh);
+		if (z = 1024) {	/* failed */
+			printk(KERN_ERR
+				"vmufat: attempted to mount non vmufat medium"
+				" as vmufat\n");
+			goto out;
+		}
+	}
+	/* Store this data in the super block */
+
+	vmudata = kmalloc(sizeof(struct memcard), GFP_KERNEL);
+	if (!vmudata) {
+		ret = -ENOMEM;
+		goto freebh_out;
+	}
+
+	/* user blocks */
+	vmudata->sb_bnum = z - 1;
+	vmudata->fat_bnum = le16_to_cpu(((u16 *) bh->b_data)[0x46 / 2]);
+	vmudata->fat_len = le16_to_cpu(((u16 *) bh->b_data)[0x48 / 2]);
+	vmudata->dir_bnum = le16_to_cpu(((u16 *) bh->b_data)[0x4a / 2]);
+	vmudata->dir_len = le16_to_cpu(((u16 *) bh->b_data)[0x4c / 2]);
+	/* return the true number of user available blocks - VMUs
+ 	* return a neat 200 and ignore 40 blocks of usable space -
+ 	* we get round that in a hardware neutral way */
+	vmudata->numblocks = vmudata->dir_bnum - vmudata->dir_len + 1;
+	sb->s_fs_info = vmudata;
+
+	sb->s_blocksize_bits = ilog2(VMU_BLK_SZ);
+	sb->s_magic = VMUFAT_MAGIC;
+	sb->s_op = &vmufat_super_operations;
+
+	root_i = vmufat_get_inode(sb, vmudata->sb_bnum);
+	if (!root_i) {
+		printk(KERN_ERR "vmufat: get root inode failed\n");
+		ret = -ENOMEM;
+		goto freevmudata_out;
+	}
+	if (IS_ERR(root_i)) {
+		printk(KERN_ERR "vmufat: get root inode failed - error 0x%lX\n",
+			PTR_ERR(root_i));
+		ret = PTR_ERR(root_i);
+		goto freevmudata_out;
+	}
+
+	sb->s_root = d_alloc_root(root_i);
+
+	if (!sb->s_root) {
+		ret = -EIO;
+		goto freeroot_out;
+	}
+	return 0;
+
+freeroot_out:
+	iput(root_i);
+freevmudata_out:
+	kfree(vmudata);
+freebh_out:
+	brelse(bh);
+out:
+	return ret;
+
+}
+
+static const struct address_space_operations vmufat_address_space_operations = {
+	.readpage =	vmufat_readpage,
+	.writepage =	vmufat_writepage,
+	.write_begin =	vmufat_write_begin,
+	.write_end =	generic_write_end,
+};
+
+static const struct super_operations vmufat_super_operations = {
+	.alloc_inode =		vmufat_alloc_inode,
+	.destroy_inode =	vmufat_destroy_inode,
+	.write_inode =		vmufat_write_inode,
+	.delete_inode =		vmufat_delete_inode,
+	.put_super =		vmufat_put_super,
+	.statfs =		vmufat_statfs,
+};
+
+static const struct inode_operations vmufat_inode_operations = {
+	.lookup =	vmufat_inode_lookup,
+	.create =	vmufat_inode_create,
+	.unlink =	vmufat_unlink,
+};
+
+static const struct file_operations vmufat_file_dir_operations = {
+	.owner =	THIS_MODULE,
+	.read =		generic_read_dir,
+	.readdir =	vmufat_readdir,
+	.fsync =	file_fsync,
+};
+
+static const struct file_operations vmufat_file_operations = {
+	.llseek =	generic_file_llseek,
+	.read =		do_sync_read,
+	.write =	do_sync_write,
+	.aio_read =	generic_file_aio_read,
+	.aio_write =	generic_file_aio_write,
+};
+
+static int vmufat_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, vmufat_fill_super,
+			   mnt);
+}
+
+static struct file_system_type vmufat_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "vmufat",
+	.get_sb		= vmufat_get_sb,
+	.kill_sb	= kill_block_super,
+	.fs_flags	= FS_REQUIRES_DEV,
+};
+
+static int __init init_vmufat_fs(void)
+{
+	int err;
+	err = init_inodecache();
+	if (err)
+		return err;
+	return register_filesystem(&vmufat_fs_type);
+}
+
+static void __exit exit_vmufat_fs(void)
+{
+	destroy_inodecache();
+	unregister_filesystem(&vmufat_fs_type);
+}
+
+module_init(init_vmufat_fs);
+module_exit(exit_vmufat_fs);
+
+MODULE_DESCRIPTION("Filesystem for Sega Dreamcast VMU");
+MODULE_AUTHOR("Adrian McMenamin <adrian@mcmen.demon.co.uk>");
+MODULE_LICENSE("GPL");



WARNING: multiple messages have this Message-ID (diff)
From: Adrian McMenamin <adrian@newgolddream.dyndns.info>
To: LKML <linux-kernel@vger.kernel.org>
Cc: linux-fsdevel <linux-fsdevel@vger.kernel.org>,
	viro <viro@zeniv.linux.org.uk>,
	linux-sh <linux-sh@vger.kernel.org>
Subject: Re: [RFC][patch] VMUFAT filesystem - v3
Date: Sat, 11 Apr 2009 23:50:36 +0100	[thread overview]
Message-ID: <1239490236.6523.20.camel@localhost.localdomain> (raw)
In-Reply-To: <1239487891.6523.17.camel@localhost.localdomain>

A further quick clean up.

 fs/Kconfig         |    1 +
 fs/Makefile        |    1 +
 fs/vmufat/Kconfig  |   14 +
 fs/vmufat/Makefile |    7 +
 fs/vmufat/inode.c  | 1394 ++++++++++++++++++++++++++++++++++++++++++++++++++++

Signed-off by: Adrian McMenamin
---
diff --git a/fs/Kconfig b/fs/Kconfig
index 9f7270f..b7cb23d 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -176,6 +176,7 @@ source "fs/romfs/Kconfig"
 source "fs/sysv/Kconfig"
 source "fs/ufs/Kconfig"
 source "fs/exofs/Kconfig"
+source "fs/vmufat/Kconfig"
 
 config NILFS2_FS
 	tristate "NILFS2 file system support (EXPERIMENTAL)"
diff --git a/fs/Makefile b/fs/Makefile
index af6d047..9d2d469 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -124,3 +124,4 @@ obj-$(CONFIG_OCFS2_FS)		+= ocfs2/
 obj-$(CONFIG_BTRFS_FS)		+= btrfs/
 obj-$(CONFIG_GFS2_FS)           += gfs2/
 obj-$(CONFIG_EXOFS_FS)          += exofs/
+obj-$(CONFIG_VMUFAT_FS)		+= vmufat/
diff --git a/fs/vmufat/Kconfig b/fs/vmufat/Kconfig
new file mode 100644
index 0000000..bdad4c6
--- /dev/null
+++ b/fs/vmufat/Kconfig
@@ -0,0 +1,14 @@
+config VMUFAT_FS
+	tristate "Dreamcast VMU FAT filesystem"
+	depends on BLOCK
+	help
+	  This implements the simple FAT type filesystem found on SEGA
+	  Dreamcast visual memory units.
+
+	  Dreamcast users who want to mount their VMUs to view the native
+	  filesystem will say 'Y' here. The filesystem is hardware independent
+	  but is not recommended for any serious use in other circumstances, so
+	  just about everyone else should say 'N'.
+
+	  To compile this as a module say 'M' here. The module will be called
+	  vmufat
diff --git a/fs/vmufat/Makefile b/fs/vmufat/Makefile
new file mode 100644
index 0000000..c716ead
--- /dev/null
+++ b/fs/vmufat/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for VMUFAT filesystem
+#
+
+obj-$(CONFIG_VMUFAT_FS) += vmufat.o
+
+vmufat-objs := inode.o
diff --git a/fs/vmufat/inode.c b/fs/vmufat/inode.c
new file mode 100644
index 0000000..a65ac84
--- /dev/null
+++ b/fs/vmufat/inode.c
@@ -0,0 +1,1394 @@
+/*
+ * inode operations for the VMU file system
+ *
+ * Copyright (C) 2002 - 2009	Adrian McMenamin
+ * Copyright (C) 2002		Paul Mundt
+ *
+ * Released under the terms of the GNU GPL.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
+#include <linux/statfs.h>
+#include <linux/mpage.h>
+#include <linux/bcd.h>
+
+#define VMUFAT_NAMELEN 12
+
+/* GNU utils won't list files with inode num 0 */
+#define VMUFAT_ZEROBLOCK 32768
+#define VMU_BLK_SZ 512
+#define VMUFAT_MAGIC 0x55555555
+
+#define FATFILEEND	0xFFFA
+#define FATEMPTY	0xFFFC
+#define DIRRECLEN	0x20
+#define DIRRECBLK	0x10
+
+static struct kmem_cache *vmufat_inode_cachep;
+static struct kmem_cache *vmufat_blist_cachep;
+static const struct inode_operations vmufat_inode_operations;
+static const struct file_operations vmufat_file_operations;
+static const struct address_space_operations vmufat_address_space_operations;
+static const struct file_operations vmufat_file_dir_operations;
+static const struct super_operations vmufat_super_operations;
+
+static struct inode *vmufat_get_inode(struct super_block *sb, long ino);
+static int vmufat_list_blocks(struct inode *in);
+
+struct memcard {
+	long sb_bnum;
+	long fat_bnum;
+	long fat_len;
+	long dir_bnum;
+	long dir_len;
+	long numblocks;
+};
+
+struct vmufat_block_list {
+	struct list_head b_list;
+	int bno;
+};
+
+struct vmufat_inode {
+	struct vmufat_block_list blocks;
+	int nblcks;
+	struct inode vfs_inode;
+};
+
+static struct vmufat_inode *VMUFAT_I(struct inode *in)
+{
+	return container_of(in, struct vmufat_inode, vfs_inode);
+}
+
+struct vmufat_file_info {
+	u8 ftype;
+	u8 copy_pro;
+	u16 fblk;
+	char fname[VMUFATNAMELEN];
+};
+
+static inline int vmufat_index(int fno)
+{
+	return (fno % DIRRECBLK) * DIRRECLEN;
+}
+
+static inline int vmufat_index_16(int fno)
+{
+	return (fno % DIRRECBLK) * DIRECLEN / 2;
+}
+
+/* VMU hardware is flaky, so let's compensate for that
+ * without losing hardare independence -
+ * as it is likely to be where this filesystem is used
+ */
+static inline struct buffer_head *vmufat_sb_bread(struct super_block *sb,
+	sector_t block)
+{
+	struct buffer_head *bh;
+	bh = sb_bread(sb, block);
+	if (bh)
+		return bh;
+	return sb_bread(sb, block);
+}
+
+/* Linear day numbers of the respective 1sts in non-leap years. */
+static int day_n[] =
+    {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
+
+static struct dentry *vmufat_inode_lookup(struct inode *in, struct dentry *dent,
+	struct nameidata *nd)
+{
+	struct super_block *sb;
+	struct memcard *vmudetails;
+	struct buffer_head *bh;
+	struct inode *ino;
+	char name[VMUFAT_NAMELEN];
+	long blck_read;
+	int error = 0, fno = 0;
+
+	if (dent->d_name.len > VMUFAT_NAMELEN) {
+		error = -ENAMETOOLONG;
+		goto out;
+	}
+
+	sb = in->i_sb;
+	vmudetails = sb->s_fs_info;
+	blck_read = vmudetails->dir_bnum;
+
+	bh = vmufat_sb_bread(sb, blck_read);
+	if (!bh) {
+		error = -EIO;
+		goto out;
+	}
+
+	do {
+		/* Have we got a file? */
+		if (bh->b_data[vmufat_index(fno)] == 0)
+			goto next;
+
+		/* get file name */
+		memcpy(name,
+		       bh->b_data + 4 + vmufat_index(fno), VMUFAT_NAMELEN);
+		/* do names match ?*/
+		if (memcmp(dent->d_name.name, name, dent->d_name.len) == 0) {
+			/* read the inode number from the directory */
+			ino = vmufat_get_inode(sb,
+				le16_to_cpu(((u16 *) bh->b_data)
+					[1 + vmufat_index_16(fno)]));
+			if (!ino) {
+				error = -EACCES;
+				goto release_bh;
+			}
+			if (IS_ERR(ino)) {
+				error = PTR_ERR(ino);
+				goto release_bh;
+			}
+			/* return the entry */
+			d_add(dent, ino);
+			goto release_bh;
+		}
+next:
+		/* did not match, so try the next file */
+		fno++;
+		/* do we need to move to the next block in the directory? */
+		if (fno >= 0x10) {
+			fno = 0;
+			blck_read--;
+			if (blck_read <=
+				vmudetails->dir_bnum - vmudetails->dir_len) {
+				d_add(dent, NULL);
+				break;
+			}
+			brelse(bh);
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh) {
+				error = -EIO;
+				goto out;
+			}
+		}
+	} while (1);
+
+release_bh:
+	brelse(bh);
+out:
+	return ERR_PTR(error);
+}
+
+/*
+ * Find a free block in the FAT and mark it
+ * as the end of a file
+ */
+static int vmufat_find_free(struct super_block *sb)
+{
+	struct memcard *vmudetails = sb->s_fs_info;
+	int nextblock, x;
+	u16 fatdata;
+	struct buffer_head *bh_fat;
+	int error = 0;
+
+	nextblock = vmudetails->fat_bnum + vmudetails->fat_len - 1;
+	x = VMU_BLK_SZ;
+	bh_fat =
+	    vmufat_sb_bread(sb, nextblock);
+	if (!bh_fat) {
+		error = -EIO;
+		goto fail;
+	}
+
+	do {
+		fatdata = ((u16 *) bh_fat->b_data)[x];
+		if (fatdata == FATEMPTY)
+			break;	/*empty block */
+		if (--x < 0) {
+			put_bh(bh_fat);
+			if (--nextblock >= vmudetails->fat_bnum) {
+				x = VMU_BLK_SZ;
+				bh_fat = vmufat_sb_bread(sb, nextblock);
+				if (!bh_fat) {
+					error = -EIO;
+					goto fail;
+				}
+			} else
+				break;
+		}
+	} while (1);
+
+	if (nextblock < vmudetails->fat_bnum) {
+		printk(KERN_ERR "VMUFAT: device is full\n");
+		error = -ENOSPC;
+		put_bh(bh_fat);
+		goto fail;
+	}
+	put_bh(bh_fat);
+	return  x + (nextblock - vmudetails->fat_bnum) * VMU_BLK_SZ;
+
+fail:
+	return error;
+}
+
+/* read the FAT for a given block */
+static u16 vmufat_get_fat(struct super_block *sb, long block)
+{
+	struct memcard *vmudetails = sb->s_fs_info;
+	struct buffer_head *bh;
+	int offset;
+	u16 block_content;
+
+	offset = block/(VMU_BLK_SZ/2);
+	if (offset >= vmudetails->fat_len)
+		return 0xFFFE;
+
+	bh = vmufat_sb_bread(sb, offset + 1 +
+		vmudetails->fat_bnum - vmudetails->fat_len);
+	if (!bh)
+		return 0xFFFF;
+
+	block_content = ((u16 *)bh->b_data)[block % (VMU_BLK_SZ / 2)];
+	put_bh(bh);
+	return block_content;
+}
+
+/* set the FAT for a given block */
+static int vmufat_set_fat(struct super_block *sb, long block, u16 set)
+{
+	struct memcard *vmudetails = sb->s_fs_info;
+	struct buffer_head *bh;
+	int offset;
+
+	offset = block/(VMU_BLK_SZ/2);
+	if (offset >= vmudetails->fat_len)
+		return -EINVAL;
+
+	bh = vmufat_sb_bread(sb, offset + 1 +
+		vmudetails->fat_bnum - vmudetails->fat_len);
+	if (!bh)
+		return -EIO;
+
+	((u16 *) bh->b_data)[block % (VMU_BLK_SZ / 2)] = set;
+	mark_buffer_dirty(bh);
+	put_bh(bh);
+	return 0;
+}
+
+static int vmufat_inode_create(struct inode *dir, struct dentry *de,
+		int imode, struct nameidata *nd)
+{
+	/* Create an inode */
+	int x = 0, y, z, error = 0, q;
+	long blck_read;
+	struct inode *inode;
+	struct super_block *sb;
+	struct memcard *vmudetails;
+	struct buffer_head *bh_fat = NULL, *bh;
+	unsigned long unix_date;
+	int year, day, nl_day, month;	/*inspired by FAT driver */
+	u8 century, u8year;
+
+	if (de->d_name.len > VMUFAT_NAMELEN)
+		return -ENAMETOOLONG;
+
+	sb = dir->i_sb;
+	vmudetails = sb->s_fs_info;
+
+	inode = new_inode(sb);
+	if (!inode) {
+		error = -ENOSPC;
+		goto out;
+	}
+
+	/* Walk through blocks looking for place to write
+	* Is this an executible file? */
+	if (imode & 73) {	/*Octal 111 */
+		inode->i_ino = VMUFAT_ZEROBLOCK;
+		/* But this already allocated? */
+		if (vmufat_get_fat(sb, 0) != FATEMPTY) {
+			printk(KERN_ERR
+				"VMUFAT: cannot write executible file to"
+				" filesystem - block 0 already allocated.\n");
+			error = -ENOSPC;
+			goto clean_inode;
+		}
+		q = 0;
+	} else {
+		q = vmufat_find_free(sb);
+		if (q < 0) {
+			error = q;
+			goto clean_inode;
+		}
+		inode->i_ino = q;
+	}
+
+	error = vmufat_set_fat(sb, q, FATFILEEND);
+	if (error)
+		goto clean_inode;
+
+	inode->i_uid = 0;
+	inode->i_gid = 0;
+	inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
+	inode->i_mode = imode;
+	inode->i_blocks = 1;
+	inode->i_sb = sb;
+	insert_inode_hash(inode);
+	inode->i_op = &vmufat_inode_operations;
+	inode->i_fop = &vmufat_file_operations;
+	inode->i_mapping->a_ops = &vmufat_address_space_operations;
+
+	/* Write to the directory
+	* Now search for space for the directory entry */
+	blck_read = vmudetails->dir_bnum;
+	bh = vmufat_sb_bread(sb, blck_read);
+	if (!bh) {
+		error = -EIO;
+		goto clean_inode;
+	}
+
+	for (y = 0; y < (vmudetails->dir_len * 0x10); y++) {
+		if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) {
+			brelse(bh);
+			blck_read--;
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh) {
+				error = -EIO;
+				goto clean_fat;
+			}
+		}
+		if (((bh->b_data)[vmufat_index(y)]) == 0)
+			break;
+	}
+	/* Have the directory entry
+	 * so now update it */
+	z = vmufat_index(y);
+	if (inode->i_ino != VMUFAT_ZEROBLOCK)
+		bh->b_data[z] = 0x33;	/* data file */
+	else
+		bh->b_data[z] = 0xcc;
+
+	if ((bh->b_data[z + 1] != (char) 0x00) &&
+		(bh->b_data[z + 1] != (char) 0xff))
+		bh->b_data[z + 1] = (char) 0x00;
+
+	if (inode->i_ino != VMUFAT_ZEROBLOCK) {
+		((u16 *) bh->b_data)[z / 2 + 1] =
+		    cpu_to_le16(inode->i_ino);
+		((u16 *) bh->b_data)[z / 2 + 0x0D] = 0;
+	} else {
+		((u16 *) bh->b_data)[z / 2 + 1] = 0;
+		((u16 *) bh->b_data)[z / 2 + 0x0D] = 1;
+	}
+
+	/* Name */
+	memset((char *) (bh->b_data + z + 0x04), '\0', 0x0C);
+	memcpy((char *) (bh->b_data + z + 0x04), ((de->d_name).name),
+		de->d_name.len);
+
+	/* BCD timestamp it */
+	unix_date = CURRENT_TIME.tv_sec;
+	day = unix_date / 86400 - 3652;
+	year = day / 365;
+
+	if ((year + 3) / 4 + 365 * year > day)
+		year--;
+
+	day -= (year + 3) / 4 + 365 * year;
+	if (day == 59 && !(year & 3)) {
+		nl_day = day;
+		month = 2;
+	} else {
+		nl_day = (year & 3) || day <= 59 ? day : day - 1;
+		for (month = 0; month < 12; month++)
+			if (day_n[month] > nl_day)
+				break;
+	}
+
+	century = 19;
+	if (year > 19)
+		century = 20;
+
+	bh->b_data[z + 0x10] = bin2bcd(century);
+	u8year = year + 80;
+	if (u8year > 99)
+		u8year = u8year - 100;
+
+	bh->b_data[z + 0x11] = bin2bcd(u8year);
+	bh->b_data[z + 0x12] = bin2bcd(month);
+	bh->b_data[z + 0x13] =
+	    bin2bcd(day - day_n[month - 1] + 1);
+	bh->b_data[z + 0x14] =
+	    bin2bcd((unix_date / 3600) % 24);
+	bh->b_data[z + 0x15] = bin2bcd((unix_date / 60) % 60);
+	bh->b_data[z + 0x16] = bin2bcd(unix_date % 60);
+
+	((u16 *) bh->b_data)[z / 2 + 0x0C] =
+	    cpu_to_le16(inode->i_blocks);
+
+	inode->i_mtime.tv_sec = unix_date;
+	mark_buffer_dirty(bh);
+	brelse(bh);
+
+	error = vmufat_list_blocks(inode);
+	if (error)
+		goto clean_fat;
+
+	d_instantiate(de, inode);
+	brelse(bh_fat);
+	return error;
+
+clean_fat:
+	((u16 *)bh_fat->b_data)[x] = FATEMPTY;
+	mark_buffer_dirty(bh_fat);
+	brelse(bh_fat);
+clean_inode:
+	iput(inode);
+out:
+	return error;
+}
+
+static int vmufat_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+	int filenamelen, i, error = 0;
+	struct vmufat_file_info *saved_file = NULL;
+	struct dentry *dentry = filp->f_dentry;
+	struct inode *inode = dentry->d_inode;
+	struct super_block *sb = inode->i_sb;
+	struct memcard *vmudetails = sb->s_fs_info;
+	struct buffer_head *bh;
+
+	int blck_read = vmudetails->dir_bnum;
+	bh = vmufat_sb_bread(sb, blck_read);
+	if (!bh) {
+		error = -EIO;
+		goto out;
+	}
+
+	i = filp->f_pos;
+
+	/* handle . for this directory and .. for parent */
+	switch ((unsigned int) filp->f_pos) {
+	case 0:
+		if (filldir(dirent, ".", 1, i++, inode->i_ino, DT_DIR) < 0)
+			goto finish;
+
+		filp->f_pos++;
+	case 1:
+		if (filldir(dirent, "..", 2, i++,
+			    dentry->d_parent->d_inode->i_ino, DT_DIR) < 0)
+			goto finish;
+
+		filp->f_pos++;
+	default:
+		break;
+	}
+
+	/* trap reading beyond the end of the directory */
+	if ((i - 2) > (vmudetails->dir_len * 0x10)) {
+		error = -EINVAL;
+		goto release_bh;
+	}
+
+	saved_file =
+	    kmalloc(sizeof(struct vmufat_file_info), GFP_KERNEL);
+	if (!saved_file) {
+		error = -ENOMEM;
+		goto release_bh;
+	}
+
+	do {
+		if ((i - 2) / 0x10 > (vmudetails->dir_bnum - blck_read)) {
+			/* move to next block in directory */
+			blck_read--;
+			if (vmudetails->dir_bnum - vmudetails->dir_len <=
+				blck_read)
+				break;
+			brelse(bh);
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh) {
+				kfree(saved_file);
+				error = -EIO;
+				goto out;
+			}
+		}
+
+		saved_file->ftype = bh->b_data[vmufat_index(i - 2)];
+
+		if (saved_file->ftype == 0)
+			break;
+
+		saved_file->fblk =
+		    le16_to_cpu(((u16 *) bh->b_data)[1 +
+			vmufat_index_16(i - 2)]);
+		if (saved_file->fblk == 0)
+			saved_file->fblk = VMUFAT_ZEROBLOCK;
+
+		memcpy(saved_file->fname,
+		       bh->b_data + 4 + vmufat_index(i - 2), VMUFAT_NAMELEN);
+		filenamelen = strlen(saved_file->fname);
+		if (filenamelen > VMUFAT_NAMELEN)
+			filenamelen = VMUFAT_NAMELEN;
+		if (filldir
+		    (dirent, saved_file->fname, filenamelen, i++,
+		     saved_file->fblk, DT_REG) < 0) {
+			goto finish;
+		}
+
+		filp->f_pos++;
+	} while (1);
+
+finish:
+	kfree(saved_file);
+release_bh:
+	brelse(bh);
+out:
+	return error;
+}
+
+
+
+static long vmufat_get_date(struct buffer_head *bh, int offset)
+{
+	int century, year, month, day, hour, minute, second;
+
+	century = bcd2bin(bh->b_data[offset++]);
+	year = bcd2bin(bh->b_data[offset++]);
+	month = bcd2bin(bh->b_data[offset++]);
+	day = bcd2bin(bh->b_data[offset++]);
+	hour = bcd2bin(bh->b_data[offset++]);
+	minute = bcd2bin(bh->b_data[offset++]);
+	second = bcd2bin(bh->b_data[offset]);
+
+	return 	mktime(century * 100 + year, month, day, hour, minute,
+			second);
+}
+
+static struct inode *vmufat_alloc_inode(struct super_block *sb)
+{
+	struct vmufat_inode *vi = kmem_cache_alloc(vmufat_inode_cachep,
+		GFP_KERNEL);
+
+	if (!vi)
+		return NULL;
+	INIT_LIST_HEAD(&vi->blocks.b_list);
+	return &vi->vfs_inode;
+}
+
+static void vmufat_destroy_inode(struct inode *in)
+{
+	struct vmufat_inode *vi = VMUFAT_I(in);
+	struct vmufat_block_list *vb;
+	struct list_head *iter, *iter2;
+
+	list_for_each_safe(iter, iter2, &vi->blocks.b_list) {
+		vb = list_entry(iter, struct vmufat_block_list, b_list);
+		list_del(iter);
+		kmem_cache_free(vmufat_blist_cachep, vb);
+	}
+	kmem_cache_free(vmufat_inode_cachep, vi);
+}
+
+static int vmufat_list_blocks(struct inode *in)
+{
+	struct vmufat_inode *vi = VMUFAT_I(in);
+	struct super_block *sb = in->i_sb;
+	long nextblock;
+	long ino = in->i_ino;
+	int fat_seg;
+	struct buffer_head *bh;
+	struct memcard *vmudetails;
+	int error;
+	struct list_head *iter, *iter2;
+	struct vmufat_block_list *vbl, *nvbl;
+	u16 fatdata;
+
+	vmudetails = sb->s_fs_info;
+	nextblock = ino;
+	fat_seg = nextblock / (sb->s_blocksize / 2);
+	bh = vmufat_sb_bread(sb, vmudetails->fat_bnum -
+		(vmudetails->fat_len - 1) + fat_seg);
+	if (!bh) {
+		error = -EIO;
+		goto out;
+	}
+
+	/* Delete any previous list of blocks */
+	list_for_each_safe(iter, iter2, &vi->blocks.b_list) {
+		vbl = list_entry(iter, struct vmufat_block_list, b_list);
+		list_del(iter);
+		kmem_cache_free(vmufat_blist_cachep, vbl);
+	}
+	vi->nblcks = 0;
+	do {
+		vbl = kmem_cache_alloc(vmufat_blist_cachep,
+			GFP_KERNEL);
+		if (!vbl) {
+			error = -ENOMEM;
+			goto unwind_out;
+		}
+		INIT_LIST_HEAD(&vbl->b_list);
+		vbl->bno = nextblock;
+		list_add_tail(&vbl->b_list, &vi->blocks.b_list);
+		vi->nblcks++;
+
+		/* Find next block in the FAT - if there is one */
+		fatdata = ((u16 *) bh->b_data)
+			[nextblock % (sb->s_blocksize / 2)];
+		if (fatdata == FATEMPTY) {
+			printk(KERN_WARNING "VMUFAT: FAT table appears to have"
+				" been corrupted.\n");
+			error = -EIO;
+			goto unwind_out;
+		}
+		if (fatdata == FATFILEEND)
+			break;	/*end of file */
+		if (fatdata / (sb->s_blocksize / 2) != fat_seg) {
+			brelse(bh);
+			fat_seg = fatdata / (sb->s_blocksize / 2);
+			bh = vmufat_sb_bread(sb, vmudetails->fat_bnum -
+				(vmudetails->fat_len - 1) + fat_seg);
+			if (!bh) {
+				error = -ENOMEM;
+				goto unwind_out;
+			}
+		}
+		nextblock = fatdata;
+	} while (1);
+	brelse(bh);
+	return 0;
+
+unwind_out:
+	list_for_each_entry_safe(vbl, nvbl, &vi->blocks.b_list, b_list) {
+		list_del_init(&vbl->b_list);
+		kmem_cache_free(vmufat_blist_cachep, vbl);
+	}
+out:
+	return error;
+}
+
+static struct inode *vmufat_get_inode(struct super_block *sb, long ino)
+{
+	struct buffer_head *bh;
+	int error, blck_read, y, z;
+	struct inode *inode = iget_locked(sb, ino);
+	struct memcard *vmudetails = sb->s_fs_info;
+	long superblock_bno = vmudetails->sb_bnum;
+
+	if (inode && (inode->i_state & I_NEW)) {
+		inode->i_uid = 0;
+		inode->i_gid = 0;
+		inode->i_mode &= ~S_IFMT;
+		if (inode->i_ino == superblock_bno) {
+			bh = vmufat_sb_bread(sb, inode->i_ino);
+			if (!bh) {
+				error = -EIO;
+				goto failed;
+			}
+			inode->i_ctime.tv_sec = inode->i_mtime.tv_sec =
+				vmufat_get_date(bh, 0x30);
+
+			/* Mark as a directory */
+			inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO;
+
+			inode->i_op = &vmufat_inode_operations;
+			inode->i_fop = &vmufat_file_dir_operations;
+		} else {
+			blck_read = vmudetails->dir_bnum;
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh) {
+				error = -EIO;
+				goto failed;
+			}
+
+			/* Mark file as regular type */
+			inode->i_mode = S_IFREG;
+
+			/* Scan through the directory to find matching file */
+			for (y = 0; y < vmudetails->numblocks; y++) {
+				if ((y / 0x10) >
+				    (vmudetails->dir_bnum - blck_read)) {
+					brelse(bh);
+					blck_read--;
+					bh = vmufat_sb_bread(sb, blck_read);
+					if (!bh) {
+						error = -EIO;
+						goto failed;
+					}
+				}
+				if (le16_to_cpu
+				    (((u16 *) bh->b_data)[(y % 0x10) * 0x10 +
+						    0x01]) == ino)
+					break;
+				}
+
+				if (y >= vmudetails->numblocks) {
+					brelse(bh);
+					printk(KERN_INFO
+						"vmufat: could not find this "
+						"file on filesystem\n");
+				error = -ENOENT;
+				goto failed;
+			}
+
+			/* identified the correct directory entry */
+			z = vmufat_index(y);
+			inode->i_ctime.tv_sec = inode->i_mtime.tv_sec =
+				vmufat_get_date(bh, z + 0x10);
+
+			/* Execute if a game, write if not copy protected */
+			inode->i_mode &= ~(S_IWUGO | S_IXUGO);
+			inode->i_mode |= S_IRUGO;
+
+			/* Mode - is it write protected? */
+			if ((((u8 *) bh->b_data)[0x01 + z] ==
+			     0x00) & ~(sb->s_flags & MS_RDONLY))
+				inode->i_mode |= S_IWUGO;
+			/* Is file executible - ie a game */
+			if ((((u8 *) bh->b_data)[z] ==
+			     0xcc) & ~(sb->s_flags & MS_NOEXEC))
+				inode->i_mode |= S_IXUGO;
+
+			inode->i_fop = &vmufat_file_operations;
+
+			inode->i_blocks =
+			    le16_to_cpu(((u16 *) bh->b_data)
+				[vmufat_index_16(y) + 0x0C]);
+			inode->i_size = inode->i_blocks * sb->s_blocksize;
+
+			inode->i_mapping->a_ops =
+				&vmufat_address_space_operations;
+			inode->i_op = &vmufat_inode_operations;
+			inode->i_fop = &vmufat_file_operations;
+			error = vmufat_list_blocks(inode);
+			if (error)
+				goto failed;
+		}
+		inode->i_atime = CURRENT_TIME;
+		unlock_new_inode(inode);
+	}
+
+	return inode;
+
+failed:
+	iget_failed(inode);
+	return ERR_PTR(error);
+}
+
+static void vmufat_put_super(struct super_block *sb)
+{
+	sb->s_dev = 0;
+	kfree(sb->s_fs_info);
+}
+
+static int vmufat_scan(struct super_block *sb, struct kstatfs *buf)
+{
+	int error = 0;
+	int free = 0;
+	int x;
+	u16 fatdata;
+	struct buffer_head *bh_fat;
+	struct memcard *vmudetails = sb->s_fs_info;
+	long nextblock;
+
+	/* Look through the FAT */
+	nextblock = vmudetails->fat_bnum + vmudetails->fat_len - 1;
+	x = sb->s_blocksize;
+	bh_fat = vmufat_sb_bread(sb, nextblock);
+	if (!bh_fat) {
+		error = -EIO;
+		goto out;
+	}
+
+	do {
+		fatdata = ((u16 *) bh_fat->b_data)[x];
+		if (fatdata == FATEMPTY)
+			free++;
+		if (--x < 0) {
+			brelse(bh_fat);
+			if (--nextblock >= vmudetails->fat_bnum) {
+				x = sb->s_blocksize;
+				bh_fat = vmufat_sb_bread(sb, nextblock);
+				if (!bh_fat) {
+					error = -EIO;
+					goto out;
+				}
+			} else
+				break;
+		}
+	} while (1);
+
+	buf->f_bfree = free;
+	buf->f_bavail = free;
+	buf->f_blocks = vmudetails->numblocks;
+
+out:
+	return error;
+}
+
+static int vmufat_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+	struct super_block *sb = dentry->d_sb;
+	int error;
+
+	error = vmufat_scan(sb, buf);
+	if (error)
+		return error;
+	buf->f_type = VMUFAT_MAGIC;
+	buf->f_bsize = sb->s_blocksize;
+	buf->f_namelen = VMUFAT_NAMELEN;
+
+	return 0;
+}
+
+/*
+ * Delete inode by marking space as free in FAT
+ * no need to waste time and effort by actually
+ * wiping underlying data on media
+ */
+static void vmufat_delete_inode(struct inode *in)
+{
+	struct buffer_head *bh, *bh_old;
+	struct super_block *sb;
+	struct memcard *vmudetails;
+	int z, y, x, w, v, blck_read, error;
+	u16 nextblock, fatdata;
+
+	if (in->i_ino == VMUFAT_ZEROBLOCK)
+		in->i_ino = 0;
+	sb = in->i_sb;
+	vmudetails = sb->s_fs_info;
+	if (in->i_ino > vmudetails->fat_len * sb->s_blocksize / 2) {
+		printk(KERN_ERR "vmufat: attempting to delete"
+			"inode beyond device size");
+		return;
+	}
+
+	/* Seek start of file and wander through FAT
+ 	 * Marking the blocks as unallocated */
+	nextblock = in->i_ino;
+	do {
+		fatdata = vmufat_get_fat(sb, nextblock);
+		error = vmufat_set_fat(sb, nextblock, FATEMPTY);
+		if (error)
+			goto failure;
+		if (fatdata == FATFILEEND)
+			break;
+		nextblock = fatdata;
+	} while (1);
+
+	/* Now clean the directory entry
+	 * Have to wander through this
+	 * to find the appropriate entry */
+	blck_read = vmudetails->dir_bnum;
+	bh = vmufat_sb_bread(sb, blck_read);
+	if (!bh)
+		goto failure;
+
+	for (y = 0; y < (vmudetails->dir_len * 0x10); y++) {
+		if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) {
+			brelse(bh);
+			blck_read--;
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh)
+				goto failure;
+		}
+		if (le16_to_cpu
+		    (((u16 *) bh->b_data)[(y % 0x10) * 0x10 +
+					    0x01]) == in->i_ino)
+			break;
+	}
+
+	/* Found directory entry - so NULL it now */
+	w = vmufat_index_16(y);
+	for (z = 0; z < 0x10; z++)
+		((u16 *) bh->b_data)[w + z] = 0;
+	mark_buffer_dirty(bh);
+	/* Replace it with another entry - if one exists */
+	x = y;
+	for (y = x + 1; y < (vmudetails->dir_len * 0x10); y++) {
+		if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) {
+			brelse(bh);
+			blck_read--;
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh)
+				return;
+		}
+		/* look for the end of entries in the directory */
+		if (bh->b_data[vmufat_index(y)] == 0) {
+			y--;
+			if (y == x)
+				break;	/* At the end in any case */
+			brelse(bh);
+
+			/* force read of correct block */
+			bh = vmufat_sb_bread(sb, vmudetails->dir_bnum -
+				y / 0x10);
+			if (!bh)
+				goto failure;
+			bh_old =
+			    vmufat_sb_bread(sb, vmudetails->dir_bnum -
+				x / 0x10);
+			if (!bh_old) {
+				brelse(bh);
+				goto failure;
+			}
+
+			/*
+ 			 * Copy final directory entry into space created
+			 * by the deletion of the inode
+			 */
+			w = vmufat_index_16(x);
+			v = vmufat_index_16(y);
+			for (z = 0; z < 0x10; z++) {
+				((u16 *) bh_old->b_data)[w + z] =
+					((u16 *) bh->b_data)[v + z];
+				((u16 *) bh->b_data)[v + z] = 0;
+			}
+			mark_buffer_dirty(bh);
+			if (x/0x10 != y/0x10)
+				mark_buffer_dirty(bh_old);
+			brelse(bh_old);
+			break;
+		}
+	}
+
+	brelse(bh);
+	return;
+
+failure:
+	printk(KERN_ERR "vmufat: Failure to read device,"
+		" could not delete inode - filesystem may be damaged\n");
+	return;
+}
+
+/*
+ * vmufat_unlink - delete a file pointed to
+ * by the dentry (only one directory in a
+ * vmufat fs so safe to ignore the inode
+ * upplied here
+ */
+static int vmufat_unlink(struct inode *dir, struct dentry *dentry)
+{
+	struct inode *in;
+
+	in = dentry->d_inode;
+	if (!in)
+		return -EIO;
+	vmufat_delete_inode(in);
+	return 0;
+}
+
+static int vmufat_get_block(struct inode *inode, sector_t iblock,
+	struct buffer_head *bh_result, int create)
+{
+	struct vmufat_inode *vin = VMUFAT_I(inode);
+	struct vmufat_block_list *vlist = &vin->blocks;
+	struct vmufat_block_list *vblk;
+	struct super_block *sb = inode->i_sb;
+	int cural;
+	int finblk, nxtblk, exeblk;
+	struct list_head *iter;
+	sector_t cntdwn = iblock;
+	sector_t phys;
+	int error = -EINVAL;
+
+	/* quick sanity check */
+	if (vin->nblcks <= 0)
+		goto out;
+	if (iblock < vin->nblcks) {
+		/* block is already here so read it into the buffer head */
+		list_for_each(iter, &vlist->b_list) {
+			if (cntdwn-- == 0)
+				break;
+		}
+		vblk = list_entry(iter, struct vmufat_block_list, b_list);
+		clear_buffer_new(bh_result);
+		error = 0;
+		phys = vblk->bno;
+		goto got_it;
+	}
+	if (!create) {
+		error = -EINVAL;
+		goto out;
+	}
+	/*
+	 * check not looking for a block too far
+	 * beyond the end of the existing file
+	 */
+	if (iblock > vin->nblcks) {
+		error = -EINVAL;
+		goto out;
+	}
+
+	/* if looking for a block that is not current - allocate it*/
+	cural = vin->nblcks;
+	list_for_each(iter, &vlist->b_list) {
+		if (cural-- == 1)
+			break;
+	}
+	vblk = list_entry(iter, struct vmufat_block_list, b_list);
+	finblk = vblk->bno;
+
+	/* Exec files have to be linear */
+	if (inode->i_ino == 0) {
+		exeblk = vmufat_get_fat(sb, finblk + 1);
+		if (exeblk != FATEMPTY) {
+			printk(KERN_WARNING "Cannot allocate linear space "
+				"needed for executible\n");
+			error = -ENOSPC;
+			goto out;
+		}
+		nxtblk = finblk + 1;
+	} else {
+		nxtblk = vmufat_find_free(sb);
+		if (nxtblk < 0) {
+			error = nxtblk;
+			goto out;
+		}
+	}
+	error = vmufat_set_fat(sb, finblk, nxtblk);
+	if (error)
+		goto out;
+	error = vmufat_set_fat(sb, nxtblk, FATFILEEND);
+	if (error)
+		goto out;
+	error = vmufat_list_blocks(inode);
+	mark_inode_dirty(inode);
+	if (error)
+		goto out;
+	set_buffer_new(bh_result);
+	phys = nxtblk;
+	error = 0;
+got_it:
+	map_bh(bh_result, sb, phys);
+out:
+	return error;
+}
+
+static int vmufat_writepage(struct page *page, struct writeback_control *wbc)
+{
+	return block_write_full_page(page, vmufat_get_block, wbc);
+}
+
+static int vmufat_write_begin(struct file *file, struct address_space *mapping,
+		loff_t pos, unsigned len, unsigned flags,
+		struct page **pagep, void **fsdata)
+{
+	*pagep = NULL;
+	return block_write_begin(file, mapping, pos, len, flags, pagep, fsdata,
+		vmufat_get_block);
+}
+
+static int vmufat_readpage(struct file *file, struct page *page)
+{
+	return block_read_full_page(page, vmufat_get_block);
+}
+
+/*
+ * There are no inodes on the medium - vmufat_write_inode
+ * updates the directory entry
+ */
+static int vmufat_write_inode(struct inode *in, int wait)
+{
+	struct buffer_head *bh;
+	__u8 century, u8year;
+	unsigned long inode_num;
+	int y, blck_read, z;
+	unsigned long unix_date;
+	int year, day, nl_day, month;
+	struct super_block *sb = in->i_sb;
+	struct memcard *vmudetails =
+	    ((struct memcard *) sb->s_fs_info);
+
+	/* As most real world devices are flash
+ 	 * we won't update the superblock every
+ 	 * time we change something else on the fs
+ 	 * - it is ugly but a sensible compromise
+ 	 */
+	if (in->i_ino == vmudetails->sb_bnum)
+		return 0;
+
+	if (in->i_ino == VMUFAT_ZEROBLOCK)
+		inode_num = 0;
+	else
+		inode_num = in->i_ino;
+
+	/* update the directory and inode details */
+	/* Now search for the directory entry */
+	blck_read = vmudetails->dir_bnum;
+	bh = vmufat_sb_bread(sb, blck_read);
+	if (!bh)
+		return -EIO;
+
+	for (y = 0; y < vmudetails->numblocks; y++) {
+		if ((y / 0x10) > (vmudetails->dir_bnum - blck_read)) {
+			brelse(bh);
+			blck_read--;
+			bh = vmufat_sb_bread(sb, blck_read);
+			if (!bh)
+				return -EIO;
+		}
+		if (le16_to_cpu
+		    (((__u16 *) bh->b_data)[vmufat_index_16(y) +
+					    0x01]) == inode_num)
+			break;
+	}
+	/* Have the directory entry
+	 * so now update it */
+	z = (y % 0x10) * 0x20;
+	if (inode_num != 0)
+		bh->b_data[z] = 0x33;	/* data file */
+	else
+		bh->b_data[z] = 0xcc;
+	if (bh->b_data[z + 1] !=  0
+	    && bh->b_data[z + 1] != (char) 0xff)
+		bh->b_data[z + 1] = 0;
+	((__u16 *) bh->b_data)[z / 2 + 1] = cpu_to_le16(inode_num);
+
+	/* BCD timestamp it */
+	unix_date = CURRENT_TIME.tv_sec;
+	day = unix_date / 86400 - 3652;
+	year = day / 365;
+	if ((year + 3) / 4 + 365 * year > day)
+		year--;
+	day -= (year + 3) / 4 + 365 * year;
+	if (day == 59 && !(year & 3)) {
+		nl_day = day;
+		month = 2;
+	} else {
+		nl_day = (year & 3) || day <= 59 ? day : day - 1;
+		for (month = 0; month < 12; month++)
+			if (day_n[month] > nl_day)
+				break;
+	}
+
+	century = 19;
+	if (year > 19)
+		century = 20;
+	bh->b_data[z + 0x10] = bin2bcd(century);
+	u8year = year + 80;
+	if (u8year > 99)
+		u8year = u8year - 100;
+	bh->b_data[z + 0x11] = bin2bcd(u8year);
+	bh->b_data[z + 0x12] = bin2bcd(month);
+	bh->b_data[z + 0x13] = bin2bcd(day - day_n[month - 1] + 1);
+	bh->b_data[z + 0x14] = bin2bcd((unix_date / 3600) % 24);
+	bh->b_data[z + 0x15] = bin2bcd((unix_date / 60) % 60);
+	bh->b_data[z + 0x16] = bin2bcd(unix_date % 60);
+
+	((__u16 *) bh->b_data)[z / 2 + 0x0C] = cpu_to_le16(in->i_blocks);
+	if (inode_num != 0)
+		((__u16 *) bh->b_data)[z / 2 + 0x0D] = 0;
+	else /* game */
+		((__u16 *) bh->b_data)[z / 2 + 0x0D] = cpu_to_le16(1);
+	in->i_mtime.tv_sec = unix_date;
+	mark_buffer_dirty(bh);
+	brelse(bh);
+	return 0;
+}
+
+static int check_sb_format(struct buffer_head *bh)
+{
+	u32 s_magic = VMUFAT_MAGIC;
+
+	if (!(((u32 *) bh->b_data)[0] == s_magic &&
+		((u32 *) bh->b_data)[1] == s_magic &&
+		((u32 *) bh->b_data)[2] == s_magic &&
+		((u32 *) bh->b_data)[3] == s_magic))
+		return 0;
+	else
+		return 1;
+}
+
+static void init_once(void *foo)
+{
+	struct vmufat_inode *vi = foo;
+
+	vi->nblcks = 0;
+	inode_init_once(&vi->vfs_inode);
+}
+
+
+static int init_inodecache(void)
+{
+	vmufat_inode_cachep = kmem_cache_create("vmufat_inode_cache",
+		sizeof(struct vmufat_inode), 0,
+			SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD, init_once);
+	if (!vmufat_inode_cachep)
+		return -ENOMEM;
+
+	vmufat_blist_cachep = kmem_cache_create("vmufat_blocklist_cache",
+		sizeof(struct vmufat_block_list), 0, SLAB_MEM_SPREAD, NULL);
+	if (!vmufat_blist_cachep) {
+		kmem_cache_destroy(vmufat_inode_cachep);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+static void destroy_inodecache(void)
+{
+	kmem_cache_destroy(vmufat_blist_cachep);
+	kmem_cache_destroy(vmufat_inode_cachep);
+}
+
+static int vmufat_fill_super(struct super_block *sb,
+					    void *data, int silent)
+{
+	/*Search for the superblock - assuming likely sizes are
+	* 128, 256, 512 and 1024 blocks */
+
+	struct buffer_head *bh;
+	struct memcard *vmudata;
+	int z;
+	struct inode *root_i;
+	int ret = -EINVAL;
+
+	sb_set_blocksize(sb, VMU_BLK_SZ);
+
+	/* Have to try lowest number first to avoid oops */
+	for (z = 128; z < 1024; z = z * 2) {
+		bh = vmufat_sb_bread(sb, z - 1);
+		if (!bh) {
+			ret = -EIO;
+			goto out;
+		}
+		if (check_sb_format(bh))
+			break;
+		brelse(bh);
+		if (z == 1024) {	/* failed */
+			printk(KERN_ERR
+				"vmufat: attempted to mount non vmufat medium"
+				" as vmufat\n");
+			goto out;
+		}
+	}
+	/* Store this data in the super block */
+
+	vmudata = kmalloc(sizeof(struct memcard), GFP_KERNEL);
+	if (!vmudata) {
+		ret = -ENOMEM;
+		goto freebh_out;
+	}
+
+	/* user blocks */
+	vmudata->sb_bnum = z - 1;
+	vmudata->fat_bnum = le16_to_cpu(((u16 *) bh->b_data)[0x46 / 2]);
+	vmudata->fat_len = le16_to_cpu(((u16 *) bh->b_data)[0x48 / 2]);
+	vmudata->dir_bnum = le16_to_cpu(((u16 *) bh->b_data)[0x4a / 2]);
+	vmudata->dir_len = le16_to_cpu(((u16 *) bh->b_data)[0x4c / 2]);
+	/* return the true number of user available blocks - VMUs
+ 	* return a neat 200 and ignore 40 blocks of usable space -
+ 	* we get round that in a hardware neutral way */
+	vmudata->numblocks = vmudata->dir_bnum - vmudata->dir_len + 1;
+	sb->s_fs_info = vmudata;
+
+	sb->s_blocksize_bits = ilog2(VMU_BLK_SZ);
+	sb->s_magic = VMUFAT_MAGIC;
+	sb->s_op = &vmufat_super_operations;
+
+	root_i = vmufat_get_inode(sb, vmudata->sb_bnum);
+	if (!root_i) {
+		printk(KERN_ERR "vmufat: get root inode failed\n");
+		ret = -ENOMEM;
+		goto freevmudata_out;
+	}
+	if (IS_ERR(root_i)) {
+		printk(KERN_ERR "vmufat: get root inode failed - error 0x%lX\n",
+			PTR_ERR(root_i));
+		ret = PTR_ERR(root_i);
+		goto freevmudata_out;
+	}
+
+	sb->s_root = d_alloc_root(root_i);
+
+	if (!sb->s_root) {
+		ret = -EIO;
+		goto freeroot_out;
+	}
+	return 0;
+
+freeroot_out:
+	iput(root_i);
+freevmudata_out:
+	kfree(vmudata);
+freebh_out:
+	brelse(bh);
+out:
+	return ret;
+
+}
+
+static const struct address_space_operations vmufat_address_space_operations = {
+	.readpage =	vmufat_readpage,
+	.writepage =	vmufat_writepage,
+	.write_begin =	vmufat_write_begin,
+	.write_end =	generic_write_end,
+};
+
+static const struct super_operations vmufat_super_operations = {
+	.alloc_inode =		vmufat_alloc_inode,
+	.destroy_inode =	vmufat_destroy_inode,
+	.write_inode =		vmufat_write_inode,
+	.delete_inode =		vmufat_delete_inode,
+	.put_super =		vmufat_put_super,
+	.statfs =		vmufat_statfs,
+};
+
+static const struct inode_operations vmufat_inode_operations = {
+	.lookup =	vmufat_inode_lookup,
+	.create =	vmufat_inode_create,
+	.unlink =	vmufat_unlink,
+};
+
+static const struct file_operations vmufat_file_dir_operations = {
+	.owner =	THIS_MODULE,
+	.read =		generic_read_dir,
+	.readdir =	vmufat_readdir,
+	.fsync =	file_fsync,
+};
+
+static const struct file_operations vmufat_file_operations = {
+	.llseek =	generic_file_llseek,
+	.read =		do_sync_read,
+	.write =	do_sync_write,
+	.aio_read =	generic_file_aio_read,
+	.aio_write =	generic_file_aio_write,
+};
+
+static int vmufat_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, vmufat_fill_super,
+			   mnt);
+}
+
+static struct file_system_type vmufat_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "vmufat",
+	.get_sb		= vmufat_get_sb,
+	.kill_sb	= kill_block_super,
+	.fs_flags	= FS_REQUIRES_DEV,
+};
+
+static int __init init_vmufat_fs(void)
+{
+	int err;
+	err = init_inodecache();
+	if (err)
+		return err;
+	return register_filesystem(&vmufat_fs_type);
+}
+
+static void __exit exit_vmufat_fs(void)
+{
+	destroy_inodecache();
+	unregister_filesystem(&vmufat_fs_type);
+}
+
+module_init(init_vmufat_fs);
+module_exit(exit_vmufat_fs);
+
+MODULE_DESCRIPTION("Filesystem for Sega Dreamcast VMU");
+MODULE_AUTHOR("Adrian McMenamin <adrian@mcmen.demon.co.uk>");
+MODULE_LICENSE("GPL");



  parent reply	other threads:[~2009-04-11 22:50 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-04-11 22:11 [RFC][patch] VMUFAT filesystem - v2 Adrian McMenamin
2009-04-11 22:11 ` Adrian McMenamin
2009-04-11 22:31 ` Josef 'Jeff' Sipek
2009-04-11 22:31   ` Josef 'Jeff' Sipek
2009-04-11 22:50 ` Adrian McMenamin [this message]
2009-04-11 22:50   ` [RFC][patch] VMUFAT filesystem - v3 Adrian McMenamin
2009-04-12  0:35 ` [RFC][patch] VMUFAT filesystem - v2 Sam Ravnborg
2009-04-12  0:35   ` Sam Ravnborg

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=1239490236.6523.20.camel@localhost.localdomain \
    --to=adrian@newgolddream.dyndns.info \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sh@vger.kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    /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.