diff mbox

[RFC] filesystem: Vmufat filesystem, version 4

Message ID 1239654768.6542.10.camel@localhost.localdomain (mailing list archive)
State Changes Requested
Headers show

Commit Message

Adrian McMenamin April 13, 2009, 8:32 p.m. UTC
I know this still has issues that others have raised but I wanted to
post a version before the end of the long weekend...

As before - some documentation about the filesystem:
http://mc.pp.se/dc/vms/flashmem.html

A shell filesystem (eg for mount over loopback) available here:
http://newgolddream.dyndns.info/pristine

A few people commented that this is a bit long for a single file - but
it is comparable to other files in the filesystem hierarchy - comments
welcome.

The filesystem is hardware independent but is also written on the basis
that this is most likely to be used with a Dreamcast VMU - though it may
be of more use to emulator writers in the end.

 fs/Kconfig         |    1 +
 fs/Makefile        |    1 +
 fs/vmufat/Kconfig  |   14 +
 fs/vmufat/Makefile |    7 +
 fs/vmufat/inode.c  | 1362 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1385 insertions(+), 0 deletions(-)

Signed-off-by: Adrian McMenamin <adrian@mcmen.demon.co.uk>
---


--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Paul Mundt April 13, 2009, 9 p.m. UTC | #1
On Mon, Apr 13, 2009 at 09:32:48PM +0100, Adrian McMenamin wrote:
> I know this still has issues that others have raised but I wanted to
> post a version before the end of the long weekend...
> 
Make sure all of the folks that had comments are included in the Cc on
future versions. If they haven't seen the updated version and are
therefore unable to comment on it, that is not an indicator that the
issues have been addressed.

Reviewing code is tedious work at best, making people that have taken the
time to participate in review do even more work to find updated versions
that may or may not have addressed their issues is remarkably poor
etiquette at best, especially when they haven't completed their review.

Timing is also not so important. This code has been in development off
and on for the last 7 years, another weekend isn't going to make any
difference. Additionally, rushing through versions when you know they
haven't taken all of the concerns under advisement is only going to
discourage people from taking the time out to review anyways.

Unless you plan on hacking on this for another 7 years, you may wish to
heed some of these points, especially now that things are finally
starting to take better shape. It would be a shame to see this bitrot
now given that all of the heavy lifting is effectively done.

> A few people commented that this is a bit long for a single file - but
> it is comparable to other files in the filesystem hierarchy - comments
> welcome.
> 
Ultimately you have to think about whether splitting it up makes sense or
not. Given that there are a lot of VMU related bits here that are separate
from the file system itself, it wouldn't hurt to break things out a bit
more logically. It's a lot to take in at once, especially for folks that
aren't familiar with all of the different aspects that the fs touches
upon.

Furthermore, the fact other file systems haven't done so is not an excuse
for you not to, especially as their reasons for doing so might be valid
(you of course haven't bothered citing what other file systems you are
referring to, so one can only vaguely postulate, though at least the
arguments for things like cramfs do not apply to vmufat).

You should also think about whether you really want to go down the road
of decoupling this from the VMU device dependencies. vmufat is not and
never will be a general purpose file system, the on-disk format,
limitation in block numbering, etc. are all directly tied to the VMU
itself. Since you cited emulators as a potential user, these should be
emulating the VMU itself anyways before you worry about layering vmufat
on top of it. Trying to position this as a general purpose file system
will bring you a world of pain you really don't want.
--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Adrian McMenamin April 13, 2009, 9:36 p.m. UTC | #2
On Tue, 2009-04-14 at 06:00 +0900, Paul Mundt wrote:
> On Mon, Apr 13, 2009 at 09:32:48PM +0100, Adrian McMenamin wrote:
> > I know this still has issues that others have raised but I wanted to
> > post a version before the end of the long weekend...
> > 
> Make sure all of the folks that had comments are included in the Cc on
> future versions. If they haven't seen the updated version and are
> therefore unable to comment on it, that is not an indicator that the
> issues have been addressed.


Fair point. Did think about this but wasn't sure what the correct
etiquette is, as sometimes people don't want more email, but will do so
in future.

> 
> Reviewing code is tedious work at best, making people that have taken the
> time to participate in review do even more work to find updated versions
> that may or may not have addressed their issues is remarkably poor
> etiquette at best, especially when they haven't completed their review.
> 

As far as I can see the three issues I haven't addressed were posting a
further patch to magic.h and a couple of *suggestions* about code style.
I will fix these points in due course, for sure. I have taken on board,
afaics, all the substantive points for which thanks

...


> 
> > A few people commented that this is a bit long for a single file - but
> > it is comparable to other files in the filesystem hierarchy - comments
> > welcome.
> > 

...

>
> Furthermore, the fact other file systems haven't done so is not an excuse
> for you not to, especially as their reasons for doing so might be valid
> (you of course haven't bothered citing what other file systems you are
> referring to, so one can only vaguely postulate, though at least the
> arguments for things like cramfs do not apply to vmufat).
> 

I didn't say that it was - I was offering my explanation for having
taken the decision to make it all one file and then seeing what the
bounce back was... there are in fact a very large number of filesystems
like this, not least of which of course is ext2 which has longer inode.c
and super.c files than this inode.c


> You should also think about whether you really want to go down the road
> of decoupling this from the VMU device dependencies. vmufat is not and
> never will be a general purpose file system, the on-disk format,
> limitation in block numbering, etc. are all directly tied to the VMU
> itself. Since you cited emulators as a potential user, these should be
> emulating the VMU itself anyways before you worry about layering vmufat
> on top of it. Trying to position this as a general purpose file system
> will bring you a world of pain you really don't want.

The two compromises are that on a failed read of a block it tries again
(as experience suggest that fixes most issues with the flaky VMU
hardware - though I doubt emulator builders will replicate that) and
that it does not write out a new date for the "superblock" on a change
of the filesystem - to avoid wear on the flash on the VMU, but again not
an issue for most emulator writers - I think the Kconfig makes it pretty
clear this isn't a general purpose filesystem.

--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Adrian McMenamin April 13, 2009, 9:40 p.m. UTC | #3
Should any earlier reviewer want to see what changes I've made please
have a look here:

http://newgolddream.dyndns.info/cgi-bin/gitweb.cgi

Hopefully that will make the changes from earlier versions a bit
clearer.

--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Paul Mundt April 13, 2009, 9:59 p.m. UTC | #4
On Mon, Apr 13, 2009 at 10:36:44PM +0100, Adrian McMenamin wrote:
> On Tue, 2009-04-14 at 06:00 +0900, Paul Mundt wrote:
> > Reviewing code is tedious work at best, making people that have taken the
> > time to participate in review do even more work to find updated versions
> > that may or may not have addressed their issues is remarkably poor
> > etiquette at best, especially when they haven't completed their review.
> > 
> 
> As far as I can see the three issues I haven't addressed were posting a
> further patch to magic.h and a couple of *suggestions* about code style.
> I will fix these points in due course, for sure. I have taken on board,
> afaics, all the substantive points for which thanks
> 
This got cut off, but the point remains that you have provided no
incremental comments in your changelog to note what has changed across
versions, so it's not obvious at first glance what issues have been
addressed and which ones have not.

Likewise, folks that have started looking at the code have cut their
review short pending an updated version with the aforementioned issues
corrected. This is the primary reason why you want to make sure all of
those nits are addressed and the Cc list maintained, especially if you
are still in 'RFC' mode.

> > Furthermore, the fact other file systems haven't done so is not an excuse
> > for you not to, especially as their reasons for doing so might be valid
> > (you of course haven't bothered citing what other file systems you are
> > referring to, so one can only vaguely postulate, though at least the
> > arguments for things like cramfs do not apply to vmufat).
> > 
> 
> I didn't say that it was - I was offering my explanation for having
> taken the decision to make it all one file and then seeing what the
> bounce back was... there are in fact a very large number of filesystems
> like this, not least of which of course is ext2 which has longer inode.c
> and super.c files than this inode.c
> 
Lines of code is a terrible metric. There are a lot of things you can
split out here that do not apply to other generic file systems. The
inode.c/super.c is the typical split for the fs, but a lot of the code
you are carrying around is for managing the on-disk format and other
VMU-specific oddities. It is useful to split this out logically and
compartmentalize where it makes sense, especially as there are far more
people who can review the file system part than the VMU part.

> > You should also think about whether you really want to go down the road
> > of decoupling this from the VMU device dependencies. vmufat is not and
> > never will be a general purpose file system, the on-disk format,
> > limitation in block numbering, etc. are all directly tied to the VMU
> > itself. Since you cited emulators as a potential user, these should be
> > emulating the VMU itself anyways before you worry about layering vmufat
> > on top of it. Trying to position this as a general purpose file system
> > will bring you a world of pain you really don't want.
> 
> The two compromises are that on a failed read of a block it tries again
> (as experience suggest that fixes most issues with the flaky VMU
> hardware - though I doubt emulator builders will replicate that) and
> that it does not write out a new date for the "superblock" on a change
> of the filesystem - to avoid wear on the flash on the VMU, but again not
> an issue for most emulator writers - I think the Kconfig makes it pretty
> clear this isn't a general purpose filesystem.
> 
Whether these hacks belong in the fs at all or not is another issue
entirely, but none of that addresses any of the things I noted.

This file system is tied directly to the VMU. Assumptions about the
on-disk format, block numbering limitations, etc. are all VMU
constraints, and papering over that in the Kconfig text is not
sufficient. This file system is and always will be tied to the VMU, and
you really do not want to decouple the two. What you do in loopback mode
for testing is your own business, but this will not work in the way
people expect on a fixed disk. You are only making things harder on
yourself by insisting that this is somehow generic.

The file system at least wants a dependency on the VMU (and I suppose
mtdblock) itself.
--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mike Frysinger April 13, 2009, 10:28 p.m. UTC | #5
On Monday 13 April 2009 17:40:43 Adrian McMenamin wrote:
> Should any earlier reviewer want to see what changes I've made please
> have a look here:
>
> http://newgolddream.dyndns.info/cgi-bin/gitweb.cgi
>
> Hopefully that will make the changes from earlier versions a bit
> clearer.

if the changelog history is clean, then you could always ask for people to 
pull the whole history rather than the final diff between mainline.  that's  
actually kind of preferred as it implicitly retains some of the logic behind 
decisions made and other ideas tried.
-mike
Adrian McMenamin April 14, 2009, 7 a.m. UTC | #6
On Tue, 2009-04-14 at 06:59 +0900, Paul Mundt wrote:

> This file system is tied directly to the VMU. Assumptions about the
> on-disk format, block numbering limitations, etc. are all VMU
> constraints, and papering over that in the Kconfig text is not
> sufficient. This file system is and always will be tied to the VMU, and
> you really do not want to decouple the two. What you do in loopback mode
> for testing is your own business, but this will not work in the way
> people expect on a fixed disk. You are only making things harder on
> yourself by insisting that this is somehow generic.
> 
> The file system at least wants a dependency on the VMU (and I suppose
> mtdblock) itself.

Why won't it work on a fixed disk "in the way people expect"? Granted
they'd be eccentric to format a disk in this way but there is no
inherent reason why this file system *has* to be tied to a VMU.

--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Paul Mundt April 14, 2009, 7:03 a.m. UTC | #7
On Tue, Apr 14, 2009 at 08:00:29AM +0100, Adrian McMenamin wrote:
> On Tue, 2009-04-14 at 06:59 +0900, Paul Mundt wrote:
> 
> > This file system is tied directly to the VMU. Assumptions about the
> > on-disk format, block numbering limitations, etc. are all VMU
> > constraints, and papering over that in the Kconfig text is not
> > sufficient. This file system is and always will be tied to the VMU, and
> > you really do not want to decouple the two. What you do in loopback mode
> > for testing is your own business, but this will not work in the way
> > people expect on a fixed disk. You are only making things harder on
> > yourself by insisting that this is somehow generic.
> > 
> > The file system at least wants a dependency on the VMU (and I suppose
> > mtdblock) itself.
> 
> Why won't it work on a fixed disk "in the way people expect"? Granted
> they'd be eccentric to format a disk in this way but there is no
> inherent reason why this file system *has* to be tied to a VMU.
> 
Everything about the on-disk format is tied to the VMU. Until that sinks
in, don't bother sending me email, thanks.
--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Adrian McMenamin April 14, 2009, 7:16 a.m. UTC | #8
On Tue, 2009-04-14 at 16:03 +0900, Paul Mundt wrote:

> > 
> Everything about the on-disk format is tied to the VMU. Until that sinks
> in, don't bother sending me email, thanks.

No, everything in the file system is dependent on the specification. The
specification is not dependent on the hardware. Those are the facts.


--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mike Frysinger April 14, 2009, 7:23 a.m. UTC | #9
On Tuesday 14 April 2009 03:16:15 Adrian McMenamin wrote:
> On Tue, 2009-04-14 at 16:03 +0900, Paul Mundt wrote:
> > Everything about the on-disk format is tied to the VMU. Until that sinks
> > in, don't bother sending me email, thanks.
>
> No, everything in the file system is dependent on the specification. The
> specification is not dependent on the hardware. Those are the facts.

i must be slow because i tend to agree with Adrian here.  if the VMU is a 
simple block device, then while the filesystem format may have been tailored 
to the exact hardware layout of the VMU, it doesnt mean that the resulting 
filesystem spec will only work on that hardware.  then again, i didnt read the 
entire source code from top to bottom ...
-mike
Al Viro April 16, 2009, 11:23 a.m. UTC | #10
On Mon, Apr 13, 2009 at 09:32:48PM +0100, Adrian McMenamin wrote:
> +struct memcard {
> +	long sb_bnum;
> +	long fat_bnum;
> +	long fat_len;
> +	long dir_bnum;
> +	long dir_len;
> +	long numblocks;
> +};

Eh...
	a) are any of those really signed?
	b) can any of those be more than 32 bits?

> +struct vmufat_block_list {
> +	struct list_head b_list;
> +	int bno;
> +};

You've got to be kidding.  So you want to keep a list of ints and do it
that way?  With separate allocation for every sodding one and a cyclic
list going through the entire bunch?

Besides, 'int' is almost certainly a wrong type.  I can buy 'u32', but...

> +	do {
...
> +	} while (1);

er... that's highly unidiomatic (and fairly common in your code below).
Any reasons for that?

> +	bh_fat =
> +	    vmufat_sb_bread(sb, nextblock);
> +	if (!bh_fat) {
> +		error = -EIO;
> +		goto fail;
> +	}
> +
> +	do {
> +		fatdata = ((u16 *) bh_fat->b_data)[x];
> +		if (fatdata == FAT_UNALLOCATED)
> +			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);

Off-by-twice and that should've been a couple of nested loops.

> +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;
> +	/* which block in the FAT */
> +	offset = block / (VMU_BLK_SZ / 2);
> +	if (offset >= vmudetails->fat_len)
> +		return FAT_ERROR;
> +
> +	bh = vmufat_sb_bread(sb, offset + 1 +
> +		vmudetails->fat_bnum - vmudetails->fat_len);
> +	if (!bh)
> +		return FAT_ERROR;
> +	/* look inside the block */
> +	block_content = ((u16 *)bh->b_data)[block % (VMU_BLK_SZ / 2)];
> +	put_bh(bh);
> +	return block_content;
> +}

What's the endianness of that puppy?

> +
> +	/* Walk through blocks looking for place to write
> +	* Is this an executible file? */
> +	if (imode & 73) {

No comments.  Really.  There must be some limits on the language one is
willing to use on public maillist, after all.

> +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 * DIR_ENT_PER_BLK)) {
> +		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) / DIR_ENT_PER_BLK >
> +			(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);

Who said there will be NUL anywhere at all?

> +		if (filenamelen > VMUFAT_NAMELEN)
> +			filenamelen = VMUFAT_NAMELEN;

See above.

> +		if (filldir
> +		    (dirent, saved_file->fname, filenamelen, i++,
> +		     saved_file->fblk, DT_REG) < 0) {
> +			goto finish;
> +		}

WTF do we bother with that copying, anyway?

> +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;
> +	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;
> +	if (nextblock == VMUFAT_ZEROBLOCK)
> +		nextblock = 0;
> +
> +	/* 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 = vmufat_get_fat(sb, nextblock);
> +		if (fatdata == FAT_UNALLOCATED) {
> +			printk(KERN_WARNING "VMUFAT: FAT table appears to have"
> +				" been corrupted.\n");
> +			error = -EIO;
> +			goto unwind_out;
> +		}
> +		if (fatdata == FAT_FILE_END)
> +			break;	/*end of file */
> +		nextblock = fatdata;
> +	} while (1);
> +
> +	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);
> +	}
> +	return error;
> +}

And you call *that* on every block allocation?

> +				if (le16_to_cpu(((u16 *) bh->b_data)
__le16 *, please, and the same for other places like that.
> +					[(y % DIR_ENT_PER_BLK) * 
> +					DIR_REC_LEN / 2 + 0x01]) == ino)
> +					break;

> +			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;

a) there's such thing as local variables.  Use them.
b) when you do that, do remember that names may be longer than one character.
c) linux-kernel is not an IOCCC.  This & ~ above is a pure obfuscation.

> +static void vmufat_put_super(struct super_block *sb)
> +{
> +	sb->s_dev = 0;
WTF for?  Leave handling that to fs/super.c, please.
> +	kfree(sb->s_fs_info);
> +}

> +	/* 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 == FAT_UNALLOCATED)
> +			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);

Pardon me, but... what the hell is going on in that code?  In particular,
is there any reason for not making it a straightforward for() going through
blocks and equally straightforward inner for() going through each block?
BTW, there seems to be an off-by-factor-of-2 above nicely obfuscated by
all that mess.  Namely,
	x = sb->s_blocksize;
	((u16 *) bh_fat->b_data)[x];
will end up accessing data at offset 2 * blocksize, which is twice the size
of actual block.

> +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;
> +}

And what happens when I open a file, unlink it and try to read?

Overall: code badly needs deobfuscation before anything else can be done
with it.
--
To unsubscribe from this list: send the line "unsubscribe linux-sh" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pavel Machek April 24, 2009, 10:14 p.m. UTC | #11
On Mon 2009-04-13 21:32:48, Adrian McMenamin wrote:
> I know this still has issues that others have raised but I wanted to
> post a version before the end of the long weekend...
> 
> As before - some documentation about the filesystem:
> http://mc.pp.se/dc/vms/flashmem.html
> 
> A shell filesystem (eg for mount over loopback) available here:
> http://newgolddream.dyndns.info/pristine
> 
> A few people commented that this is a bit long for a single file - but
> it is comparable to other files in the filesystem hierarchy - comments
> welcome.
> 
> The filesystem is hardware independent but is also written on the basis
> that this is most likely to be used with a Dreamcast VMU - though it may
> be of more use to emulator writers in the end.
> 
>  fs/Kconfig         |    1 +
>  fs/Makefile        |    1 +
>  fs/vmufat/Kconfig  |   14 +
>  fs/vmufat/Makefile |    7 +
>  fs/vmufat/inode.c  | 1362 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 1385 insertions(+), 0 deletions(-)
> 
> Signed-off-by: Adrian McMenamin <adrian@mcmen.demon.co.uk>
> ---
> 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

"vmufat.ko." ?

> +static unsigned long vmufat_save_bcd(char *bh, int z)
> +{
> +	unsigned char day, year, century, nl_day, month;
> +	unsigned char u8year;

u8year? Should this be "u8 u8year"?

> +	unsigned long 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[z + 0x10] = bin2bcd(century);
> +	u8year = year + 80;
> +	if (u8year > 99)
> +		u8year = u8year - 100;

It would be nice to document when this stops working.

> +	bh[z + 0x11] = bin2bcd(u8year);
> +	bh[z + 0x12] = bin2bcd(month);
> +	bh[z + 0x13] =
> +	    bin2bcd(day - day_n[month - 1] + 1);
> +	bh[z + 0x14] =
> +	    bin2bcd((unix_date / 3600) % 24);
> +	bh[z + 0x15] = bin2bcd((unix_date / 60) % 60);
> +	bh[z + 0x16] = bin2bcd(unix_date % 60);
> +	return unix_date;
> +}
Mike Frysinger April 27, 2009, 5:56 p.m. UTC | #12
On Friday 24 April 2009 18:14:55 Pavel Machek wrote:
> On Mon 2009-04-13 21:32:48, Adrian McMenamin wrote:
> > --- /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
>
> "vmufat.ko." ?

no, the standard is to name it without extension.  there should be a period 
there though as it's the end of the sentence ...
-mike
Pavel Machek April 27, 2009, 6:03 p.m. UTC | #13
On Mon 2009-04-27 13:56:20, Mike Frysinger wrote:
> On Friday 24 April 2009 18:14:55 Pavel Machek wrote:
> > On Mon 2009-04-13 21:32:48, Adrian McMenamin wrote:
> > > --- /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
> >
> > "vmufat.ko." ?
> 
> no, the standard is to name it without extension.  there should be a period 
> there though as it's the end of the sentence ...

Ok, add that period at end of sentence, I stand corrected.
								Pavel
diff mbox

Patch

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..4882d0c
--- /dev/null
+++ b/fs/vmufat/Makefile
@@ -0,0 +1,7 @@ 
+#
+# Makefile for VMUFAT filesystem
+#
+
+obj-$(CONFIG_VMUFAT_FS) += vmufat.o
+
+vmufat-y := inode.o
diff --git a/fs/vmufat/inode.c b/fs/vmufat/inode.c
new file mode 100644
index 0000000..7b6334b
--- /dev/null
+++ b/fs/vmufat/inode.c
@@ -0,0 +1,1362 @@ 
+/*
+ * 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/fs.h>
+#include <linux/bcd.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/statfs.h>
+#include <linux/buffer_head.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 FAT_FILE_END	0xFFFA
+#define FAT_UNALLOCATED	0xFFFC
+#define FAT_ERROR	0xFFFF
+
+#define VMUFAT_MIN_BLK	0x80
+#define VMUFAT_MAX_BLK	0x1000
+
+#define DIR_REC_LEN	0x20
+#define DIR_ENT_PER_BLK	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[VMUFAT_NAMELEN];
+};
+
+static inline int vmufat_index(int fno)
+{
+	return (fno % DIR_ENT_PER_BLK) * DIR_REC_LEN;
+}
+
+static inline int vmufat_index_16(int fno)
+{
+	return (fno % DIR_ENT_PER_BLK) * DIR_REC_LEN / 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 >= DIR_ENT_PER_BLK) {
+			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 == FAT_UNALLOCATED)
+			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;
+	/* which block in the FAT */
+	offset = block / (VMU_BLK_SZ / 2);
+	if (offset >= vmudetails->fat_len)
+		return FAT_ERROR;
+
+	bh = vmufat_sb_bread(sb, offset + 1 +
+		vmudetails->fat_bnum - vmudetails->fat_len);
+	if (!bh)
+		return FAT_ERROR;
+	/* look inside the block */
+	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;
+}
+
+/*
+ * write out the date in bcd format
+ * in the appropriate part of the 
+ * directory entry
+ */
+static unsigned long vmufat_save_bcd(char *bh, int z)
+{
+	unsigned char day, year, century, nl_day, month;
+	unsigned char u8year;
+	unsigned long 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[z + 0x10] = bin2bcd(century);
+	u8year = year + 80;
+	if (u8year > 99)
+		u8year = u8year - 100;
+
+	bh[z + 0x11] = bin2bcd(u8year);
+	bh[z + 0x12] = bin2bcd(month);
+	bh[z + 0x13] =
+	    bin2bcd(day - day_n[month - 1] + 1);
+	bh[z + 0x14] =
+	    bin2bcd((unix_date / 3600) % 24);
+	bh[z + 0x15] = bin2bcd((unix_date / 60) % 60);
+	bh[z + 0x16] = bin2bcd(unix_date % 60);
+	return unix_date;
+}
+
+static int vmufat_inode_create(struct inode *dir, struct dentry *de,
+		int imode, struct nameidata *nd)
+{
+	/* Create an inode */
+	int 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;
+
+	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) {
+		inode->i_ino = VMUFAT_ZEROBLOCK;
+		/* But this already allocated? */
+		if (vmufat_get_fat(sb, 0) != FAT_UNALLOCATED) {
+			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, FAT_FILE_END);
+	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 * DIR_ENT_PER_BLK); y++) {
+		if ((y / DIR_ENT_PER_BLK) >
+			(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 = vmufat_save_bcd(bh->b_data, z);
+
+	((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)[q] = FAT_UNALLOCATED;
+	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 * DIR_ENT_PER_BLK)) {
+		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) / DIR_ENT_PER_BLK >
+			(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;
+	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;
+	if (nextblock == VMUFAT_ZEROBLOCK)
+		nextblock = 0;
+
+	/* 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 = vmufat_get_fat(sb, nextblock);
+		if (fatdata == FAT_UNALLOCATED) {
+			printk(KERN_WARNING "VMUFAT: FAT table appears to have"
+				" been corrupted.\n");
+			error = -EIO;
+			goto unwind_out;
+		}
+		if (fatdata == FAT_FILE_END)
+			break;	/*end of file */
+		nextblock = fatdata;
+	} while (1);
+
+	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);
+	}
+	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 / DIR_ENT_PER_BLK) >
+				    (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 % DIR_ENT_PER_BLK) * 
+					DIR_REC_LEN / 2 + 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 == FAT_UNALLOCATED)
+			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;
+	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);
+		if (fatdata == FAT_ERROR) 
+			goto failure;
+		if (vmufat_set_fat(sb, nextblock, FAT_UNALLOCATED))
+			goto failure;
+		if (fatdata == FAT_FILE_END)
+			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 * DIR_ENT_PER_BLK); y++) {
+		if ((y / DIR_ENT_PER_BLK) >
+			(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 % DIR_ENT_PER_BLK) * DIR_REC_LEN / 2 +
+			0x01]) == in->i_ino)
+			break;
+	}
+
+	/* Found directory entry - so NULL it now */
+	w = vmufat_index_16(y);
+	for (z = 0; z < DIR_REC_LEN / 2; 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 * DIR_ENT_PER_BLK); y++) {
+		if ((y / DIR_ENT_PER_BLK) >
+			(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 / DIR_ENT_PER_BLK);
+			if (!bh)
+				goto failure;
+			bh_old =
+			    vmufat_sb_bread(sb, vmudetails->dir_bnum -
+				x / DIR_ENT_PER_BLK);
+			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 < DIR_REC_LEN / 2; 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);
+			/* check if the same buffer */
+			if (x / DIR_ENT_PER_BLK != y / DIR_ENT_PER_BLK)
+				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 != FAT_UNALLOCATED) {
+			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, FAT_FILE_END);
+	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;
+	unsigned long inode_num;
+	int y, blck_read, z;
+	unsigned long unix_date;
+	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 = vmufat_save_bcd(bh->b_data, z);
+
+	((__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 = VMUFAT_MIN_BLK; z < VMUFAT_MAX_BLK; 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");