Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/fs/adfs/Makefile b/fs/adfs/Makefile
new file mode 100644
index 0000000..9b2d71a
--- /dev/null
+++ b/fs/adfs/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the linux adfs filesystem routines.
+#
+
+obj-$(CONFIG_ADFS_FS) += adfs.o
+
+adfs-objs := dir.o dir_f.o dir_fplus.o file.o inode.o map.o super.o
diff --git a/fs/adfs/adfs.h b/fs/adfs/adfs.h
new file mode 100644
index 0000000..63f5df9
--- /dev/null
+++ b/fs/adfs/adfs.h
@@ -0,0 +1,127 @@
+/* Internal data structures for ADFS */
+
+#define ADFS_FREE_FRAG		 0
+#define ADFS_BAD_FRAG		 1
+#define ADFS_ROOT_FRAG		 2
+
+#define ADFS_NDA_OWNER_READ	(1 << 0)
+#define ADFS_NDA_OWNER_WRITE	(1 << 1)
+#define ADFS_NDA_LOCKED		(1 << 2)
+#define ADFS_NDA_DIRECTORY	(1 << 3)
+#define ADFS_NDA_EXECUTE	(1 << 4)
+#define ADFS_NDA_PUBLIC_READ	(1 << 5)
+#define ADFS_NDA_PUBLIC_WRITE	(1 << 6)
+
+#include <linux/version.h>
+#include "dir_f.h"
+
+struct buffer_head;
+
+/*
+ * Directory handling
+ */
+struct adfs_dir {
+	struct super_block	*sb;
+
+	int			nr_buffers;
+	struct buffer_head	*bh[4];
+	unsigned int		pos;
+	unsigned int		parent_id;
+
+	struct adfs_dirheader	dirhead;
+	union  adfs_dirtail	dirtail;
+};
+
+/*
+ * This is the overall maximum name length
+ */
+#define ADFS_MAX_NAME_LEN	256
+struct object_info {
+	__u32		parent_id;		/* parent object id	*/
+	__u32		file_id;		/* object id		*/
+	__u32		loadaddr;		/* load address		*/
+	__u32		execaddr;		/* execution address	*/
+	__u32		size;			/* size			*/
+	__u8		attr;			/* RISC OS attributes	*/
+	unsigned char	name_len;		/* name length		*/
+	char		name[ADFS_MAX_NAME_LEN];/* file name		*/
+};
+
+struct adfs_dir_ops {
+	int	(*read)(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir);
+	int	(*setpos)(struct adfs_dir *dir, unsigned int fpos);
+	int	(*getnext)(struct adfs_dir *dir, struct object_info *obj);
+	int	(*update)(struct adfs_dir *dir, struct object_info *obj);
+	int	(*create)(struct adfs_dir *dir, struct object_info *obj);
+	int	(*remove)(struct adfs_dir *dir, struct object_info *obj);
+	void	(*free)(struct adfs_dir *dir);
+};
+
+struct adfs_discmap {
+	struct buffer_head	*dm_bh;
+	__u32			dm_startblk;
+	unsigned int		dm_startbit;
+	unsigned int		dm_endbit;
+};
+
+/* Inode stuff */
+struct inode *adfs_iget(struct super_block *sb, struct object_info *obj);
+int adfs_write_inode(struct inode *inode,int unused);
+int adfs_notify_change(struct dentry *dentry, struct iattr *attr);
+
+/* map.c */
+extern int adfs_map_lookup(struct super_block *sb, unsigned int frag_id, unsigned int offset);
+extern unsigned int adfs_map_free(struct super_block *sb);
+
+/* Misc */
+void __adfs_error(struct super_block *sb, const char *function,
+		  const char *fmt, ...);
+#define adfs_error(sb, fmt...) __adfs_error(sb, __FUNCTION__, fmt)
+
+/* super.c */
+
+/*
+ * Inodes and file operations
+ */
+
+/* dir_*.c */
+extern struct inode_operations adfs_dir_inode_operations;
+extern struct file_operations adfs_dir_operations;
+extern struct dentry_operations adfs_dentry_operations;
+extern struct adfs_dir_ops adfs_f_dir_ops;
+extern struct adfs_dir_ops adfs_fplus_dir_ops;
+
+extern int adfs_dir_update(struct super_block *sb, struct object_info *obj);
+
+/* file.c */
+extern struct inode_operations adfs_file_inode_operations;
+extern struct file_operations adfs_file_operations;
+
+extern inline __u32 signed_asl(__u32 val, signed int shift)
+{
+	if (shift >= 0)
+		val <<= shift;
+	else
+		val >>= -shift;
+	return val;
+}
+
+/*
+ * Calculate the address of a block in an object given the block offset
+ * and the object identity.
+ *
+ * The root directory ID should always be looked up in the map [3.4]
+ */
+extern inline int
+__adfs_block_map(struct super_block *sb, unsigned int object_id,
+		 unsigned int block)
+{
+	if (object_id & 255) {
+		unsigned int off;
+
+		off = (object_id & 255) - 1;
+		block += off << ADFS_SB(sb)->s_log2sharesize;
+	}
+
+	return adfs_map_lookup(sb, object_id >> 8, block);
+}
diff --git a/fs/adfs/dir.c b/fs/adfs/dir.c
new file mode 100644
index 0000000..0b4c3a0
--- /dev/null
+++ b/fs/adfs/dir.c
@@ -0,0 +1,302 @@
+/*
+ *  linux/fs/adfs/dir.c
+ *
+ *  Copyright (C) 1999-2000 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  Common directory handling for ADFS
+ */
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/adfs_fs.h>
+#include <linux/time.h>
+#include <linux/stat.h>
+#include <linux/spinlock.h>
+#include <linux/smp_lock.h>
+#include <linux/buffer_head.h>		/* for file_fsync() */
+
+#include "adfs.h"
+
+/*
+ * For future.  This should probably be per-directory.
+ */
+static DEFINE_RWLOCK(adfs_dir_lock);
+
+static int
+adfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+	struct inode *inode = filp->f_dentry->d_inode;
+	struct super_block *sb = inode->i_sb;
+	struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
+	struct object_info obj;
+	struct adfs_dir dir;
+	int ret = 0;
+
+	lock_kernel();	
+
+	if (filp->f_pos >> 32)
+		goto out;
+
+	ret = ops->read(sb, inode->i_ino, inode->i_size, &dir);
+	if (ret)
+		goto out;
+
+	switch ((unsigned long)filp->f_pos) {
+	case 0:
+		if (filldir(dirent, ".", 1, 0, inode->i_ino, DT_DIR) < 0)
+			goto free_out;
+		filp->f_pos += 1;
+
+	case 1:
+		if (filldir(dirent, "..", 2, 1, dir.parent_id, DT_DIR) < 0)
+			goto free_out;
+		filp->f_pos += 1;
+
+	default:
+		break;
+	}
+
+	read_lock(&adfs_dir_lock);
+
+	ret = ops->setpos(&dir, filp->f_pos - 2);
+	if (ret)
+		goto unlock_out;
+	while (ops->getnext(&dir, &obj) == 0) {
+		if (filldir(dirent, obj.name, obj.name_len,
+			    filp->f_pos, obj.file_id, DT_UNKNOWN) < 0)
+			goto unlock_out;
+		filp->f_pos += 1;
+	}
+
+unlock_out:
+	read_unlock(&adfs_dir_lock);
+
+free_out:
+	ops->free(&dir);
+
+out:
+	unlock_kernel();
+	return ret;
+}
+
+int
+adfs_dir_update(struct super_block *sb, struct object_info *obj)
+{
+	int ret = -EINVAL;
+#ifdef CONFIG_ADFS_FS_RW
+	struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
+	struct adfs_dir dir;
+
+	printk(KERN_INFO "adfs_dir_update: object %06X in dir %06X\n",
+		 obj->file_id, obj->parent_id);
+
+	if (!ops->update) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = ops->read(sb, obj->parent_id, 0, &dir);
+	if (ret)
+		goto out;
+
+	write_lock(&adfs_dir_lock);
+	ret = ops->update(&dir, obj);
+	write_unlock(&adfs_dir_lock);
+
+	ops->free(&dir);
+out:
+#endif
+	return ret;
+}
+
+static int
+adfs_match(struct qstr *name, struct object_info *obj)
+{
+	int i;
+
+	if (name->len != obj->name_len)
+		return 0;
+
+	for (i = 0; i < name->len; i++) {
+		char c1, c2;
+
+		c1 = name->name[i];
+		c2 = obj->name[i];
+
+		if (c1 >= 'A' && c1 <= 'Z')
+			c1 += 'a' - 'A';
+		if (c2 >= 'A' && c2 <= 'Z')
+			c2 += 'a' - 'A';
+
+		if (c1 != c2)
+			return 0;
+	}
+	return 1;
+}
+
+static int
+adfs_dir_lookup_byname(struct inode *inode, struct qstr *name, struct object_info *obj)
+{
+	struct super_block *sb = inode->i_sb;
+	struct adfs_dir_ops *ops = ADFS_SB(sb)->s_dir;
+	struct adfs_dir dir;
+	int ret;
+
+	ret = ops->read(sb, inode->i_ino, inode->i_size, &dir);
+	if (ret)
+		goto out;
+
+	if (ADFS_I(inode)->parent_id != dir.parent_id) {
+		adfs_error(sb, "parent directory changed under me! (%lx but got %lx)\n",
+			   ADFS_I(inode)->parent_id, dir.parent_id);
+		ret = -EIO;
+		goto free_out;
+	}
+
+	obj->parent_id = inode->i_ino;
+
+	/*
+	 * '.' is handled by reserved_lookup() in fs/namei.c
+	 */
+	if (name->len == 2 && name->name[0] == '.' && name->name[1] == '.') {
+		/*
+		 * Currently unable to fill in the rest of 'obj',
+		 * but this is better than nothing.  We need to
+		 * ascend one level to find it's parent.
+		 */
+		obj->name_len = 0;
+		obj->file_id  = obj->parent_id;
+		goto free_out;
+	}
+
+	read_lock(&adfs_dir_lock);
+
+	ret = ops->setpos(&dir, 0);
+	if (ret)
+		goto unlock_out;
+
+	ret = -ENOENT;
+	while (ops->getnext(&dir, obj) == 0) {
+		if (adfs_match(name, obj)) {
+			ret = 0;
+			break;
+		}
+	}
+
+unlock_out:
+	read_unlock(&adfs_dir_lock);
+
+free_out:
+	ops->free(&dir);
+out:
+	return ret;
+}
+
+struct file_operations adfs_dir_operations = {
+	.read		= generic_read_dir,
+	.readdir	= adfs_readdir,
+	.fsync		= file_fsync,
+};
+
+static int
+adfs_hash(struct dentry *parent, struct qstr *qstr)
+{
+	const unsigned int name_len = ADFS_SB(parent->d_sb)->s_namelen;
+	const unsigned char *name;
+	unsigned long hash;
+	int i;
+
+	if (qstr->len < name_len)
+		return 0;
+
+	/*
+	 * Truncate the name in place, avoids
+	 * having to define a compare function.
+	 */
+	qstr->len = i = name_len;
+	name = qstr->name;
+	hash = init_name_hash();
+	while (i--) {
+		char c;
+
+		c = *name++;
+		if (c >= 'A' && c <= 'Z')
+			c += 'a' - 'A';
+
+		hash = partial_name_hash(c, hash);
+	}
+	qstr->hash = end_name_hash(hash);
+
+	return 0;
+}
+
+/*
+ * Compare two names, taking note of the name length
+ * requirements of the underlying filesystem.
+ */
+static int
+adfs_compare(struct dentry *parent, struct qstr *entry, struct qstr *name)
+{
+	int i;
+
+	if (entry->len != name->len)
+		return 1;
+
+	for (i = 0; i < name->len; i++) {
+		char a, b;
+
+		a = entry->name[i];
+		b = name->name[i];
+
+		if (a >= 'A' && a <= 'Z')
+			a += 'a' - 'A';
+		if (b >= 'A' && b <= 'Z')
+			b += 'a' - 'A';
+
+		if (a != b)
+			return 1;
+	}
+	return 0;
+}
+
+struct dentry_operations adfs_dentry_operations = {
+	.d_hash		= adfs_hash,
+	.d_compare	= adfs_compare,
+};
+
+static struct dentry *
+adfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
+{
+	struct inode *inode = NULL;
+	struct object_info obj;
+	int error;
+
+	dentry->d_op = &adfs_dentry_operations;	
+	lock_kernel();
+	error = adfs_dir_lookup_byname(dir, &dentry->d_name, &obj);
+	if (error == 0) {
+		error = -EACCES;
+		/*
+		 * This only returns NULL if get_empty_inode
+		 * fails.
+		 */
+		inode = adfs_iget(dir->i_sb, &obj);
+		if (inode)
+			error = 0;
+	}
+	unlock_kernel();
+	d_add(dentry, inode);
+	return ERR_PTR(error);
+}
+
+/*
+ * directories can handle most operations...
+ */
+struct inode_operations adfs_dir_inode_operations = {
+	.lookup		= adfs_lookup,
+	.setattr	= adfs_notify_change,
+};
diff --git a/fs/adfs/dir_f.c b/fs/adfs/dir_f.c
new file mode 100644
index 0000000..bbfc862
--- /dev/null
+++ b/fs/adfs/dir_f.c
@@ -0,0 +1,460 @@
+/*
+ *  linux/fs/adfs/dir_f.c
+ *
+ * Copyright (C) 1997-1999 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  E and F format directory handling
+ */
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/adfs_fs.h>
+#include <linux/time.h>
+#include <linux/stat.h>
+#include <linux/spinlock.h>
+#include <linux/buffer_head.h>
+#include <linux/string.h>
+
+#include "adfs.h"
+#include "dir_f.h"
+
+static void adfs_f_free(struct adfs_dir *dir);
+
+/*
+ * Read an (unaligned) value of length 1..4 bytes
+ */
+static inline unsigned int adfs_readval(unsigned char *p, int len)
+{
+	unsigned int val = 0;
+
+	switch (len) {
+	case 4:		val |= p[3] << 24;
+	case 3:		val |= p[2] << 16;
+	case 2:		val |= p[1] << 8;
+	default:	val |= p[0];
+	}
+	return val;
+}
+
+static inline void adfs_writeval(unsigned char *p, int len, unsigned int val)
+{
+	switch (len) {
+	case 4:		p[3] = val >> 24;
+	case 3:		p[2] = val >> 16;
+	case 2:		p[1] = val >> 8;
+	default:	p[0] = val;
+	}
+}
+
+static inline int adfs_readname(char *buf, char *ptr, int maxlen)
+{
+	char *old_buf = buf;
+
+	while (*ptr >= ' ' && maxlen--) {
+		if (*ptr == '/')
+			*buf++ = '.';
+		else
+			*buf++ = *ptr;
+		ptr++;
+	}
+	*buf = '\0';
+
+	return buf - old_buf;
+}
+
+#define ror13(v) ((v >> 13) | (v << 19))
+
+#define dir_u8(idx)				\
+	({ int _buf = idx >> blocksize_bits;	\
+	   int _off = idx - (_buf << blocksize_bits);\
+	  *(u8 *)(bh[_buf]->b_data + _off);	\
+	})
+
+#define dir_u32(idx)				\
+	({ int _buf = idx >> blocksize_bits;	\
+	   int _off = idx - (_buf << blocksize_bits);\
+	  *(__le32 *)(bh[_buf]->b_data + _off);	\
+	})
+
+#define bufoff(_bh,_idx)			\
+	({ int _buf = _idx >> blocksize_bits;	\
+	   int _off = _idx - (_buf << blocksize_bits);\
+	  (u8 *)(_bh[_buf]->b_data + _off);	\
+	})
+
+/*
+ * There are some algorithms that are nice in
+ * assembler, but a bitch in C...  This is one
+ * of them.
+ */
+static u8
+adfs_dir_checkbyte(const struct adfs_dir *dir)
+{
+	struct buffer_head * const *bh = dir->bh;
+	const int blocksize_bits = dir->sb->s_blocksize_bits;
+	union { __le32 *ptr32; u8 *ptr8; } ptr, end;
+	u32 dircheck = 0;
+	int last = 5 - 26;
+	int i = 0;
+
+	/*
+	 * Accumulate each word up to the last whole
+	 * word of the last directory entry.  This
+	 * can spread across several buffer heads.
+	 */
+	do {
+		last += 26;
+		do {
+			dircheck = le32_to_cpu(dir_u32(i)) ^ ror13(dircheck);
+
+			i += sizeof(u32);
+		} while (i < (last & ~3));
+	} while (dir_u8(last) != 0);
+
+	/*
+	 * Accumulate the last few bytes.  These
+	 * bytes will be within the same bh.
+	 */
+	if (i != last) {
+		ptr.ptr8 = bufoff(bh, i);
+		end.ptr8 = ptr.ptr8 + last - i;
+
+		do
+			dircheck = *ptr.ptr8++ ^ ror13(dircheck);
+		while (ptr.ptr8 < end.ptr8);
+	}
+
+	/*
+	 * The directory tail is in the final bh
+	 * Note that contary to the RISC OS PRMs,
+	 * the first few bytes are NOT included
+	 * in the check.  All bytes are in the
+	 * same bh.
+	 */
+	ptr.ptr8 = bufoff(bh, 2008);
+	end.ptr8 = ptr.ptr8 + 36;
+
+	do {
+		__le32 v = *ptr.ptr32++;
+		dircheck = le32_to_cpu(v) ^ ror13(dircheck);
+	} while (ptr.ptr32 < end.ptr32);
+
+	return (dircheck ^ (dircheck >> 8) ^ (dircheck >> 16) ^ (dircheck >> 24)) & 0xff;
+}
+
+/*
+ * Read and check that a directory is valid
+ */
+static int
+adfs_dir_read(struct super_block *sb, unsigned long object_id,
+	      unsigned int size, struct adfs_dir *dir)
+{
+	const unsigned int blocksize_bits = sb->s_blocksize_bits;
+	int blk = 0;
+
+	/*
+	 * Directories which are not a multiple of 2048 bytes
+	 * are considered bad v2 [3.6]
+	 */
+	if (size & 2047)
+		goto bad_dir;
+
+	size >>= blocksize_bits;
+
+	dir->nr_buffers = 0;
+	dir->sb = sb;
+
+	for (blk = 0; blk < size; blk++) {
+		int phys;
+
+		phys = __adfs_block_map(sb, object_id, blk);
+		if (!phys) {
+			adfs_error(sb, "dir object %lX has a hole at offset %d",
+				   object_id, blk);
+			goto release_buffers;
+		}
+
+		dir->bh[blk] = sb_bread(sb, phys);
+		if (!dir->bh[blk])
+			goto release_buffers;
+	}
+
+	memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
+	memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
+
+	if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
+	    memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
+		goto bad_dir;
+
+	if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
+	    memcmp(&dir->dirhead.startname, "Hugo", 4))
+		goto bad_dir;
+
+	if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
+		goto bad_dir;
+
+	dir->nr_buffers = blk;
+
+	return 0;
+
+bad_dir:
+	adfs_error(sb, "corrupted directory fragment %lX",
+		   object_id);
+release_buffers:
+	for (blk -= 1; blk >= 0; blk -= 1)
+		brelse(dir->bh[blk]);
+
+	dir->sb = NULL;
+
+	return -EIO;
+}
+
+/*
+ * convert a disk-based directory entry to a Linux ADFS directory entry
+ */
+static inline void
+adfs_dir2obj(struct object_info *obj, struct adfs_direntry *de)
+{
+	obj->name_len =	adfs_readname(obj->name, de->dirobname, ADFS_F_NAME_LEN);
+	obj->file_id  = adfs_readval(de->dirinddiscadd, 3);
+	obj->loadaddr = adfs_readval(de->dirload, 4);
+	obj->execaddr = adfs_readval(de->direxec, 4);
+	obj->size     = adfs_readval(de->dirlen,  4);
+	obj->attr     = de->newdiratts;
+}
+
+/*
+ * convert a Linux ADFS directory entry to a disk-based directory entry
+ */
+static inline void
+adfs_obj2dir(struct adfs_direntry *de, struct object_info *obj)
+{
+	adfs_writeval(de->dirinddiscadd, 3, obj->file_id);
+	adfs_writeval(de->dirload, 4, obj->loadaddr);
+	adfs_writeval(de->direxec, 4, obj->execaddr);
+	adfs_writeval(de->dirlen,  4, obj->size);
+	de->newdiratts = obj->attr;
+}
+
+/*
+ * get a directory entry.  Note that the caller is responsible
+ * for holding the relevant locks.
+ */
+static int
+__adfs_dir_get(struct adfs_dir *dir, int pos, struct object_info *obj)
+{
+	struct super_block *sb = dir->sb;
+	struct adfs_direntry de;
+	int thissize, buffer, offset;
+
+	buffer = pos >> sb->s_blocksize_bits;
+
+	if (buffer > dir->nr_buffers)
+		return -EINVAL;
+
+	offset = pos & (sb->s_blocksize - 1);
+	thissize = sb->s_blocksize - offset;
+	if (thissize > 26)
+		thissize = 26;
+
+	memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
+	if (thissize != 26)
+		memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
+		       26 - thissize);
+
+	if (!de.dirobname[0])
+		return -ENOENT;
+
+	adfs_dir2obj(obj, &de);
+
+	return 0;
+}
+
+static int
+__adfs_dir_put(struct adfs_dir *dir, int pos, struct object_info *obj)
+{
+	struct super_block *sb = dir->sb;
+	struct adfs_direntry de;
+	int thissize, buffer, offset;
+
+	buffer = pos >> sb->s_blocksize_bits;
+
+	if (buffer > dir->nr_buffers)
+		return -EINVAL;
+
+	offset = pos & (sb->s_blocksize - 1);
+	thissize = sb->s_blocksize - offset;
+	if (thissize > 26)
+		thissize = 26;
+
+	/*
+	 * Get the entry in total
+	 */
+	memcpy(&de, dir->bh[buffer]->b_data + offset, thissize);
+	if (thissize != 26)
+		memcpy(((char *)&de) + thissize, dir->bh[buffer + 1]->b_data,
+		       26 - thissize);
+
+	/*
+	 * update it
+	 */
+	adfs_obj2dir(&de, obj);
+
+	/*
+	 * Put the new entry back
+	 */
+	memcpy(dir->bh[buffer]->b_data + offset, &de, thissize);
+	if (thissize != 26)
+		memcpy(dir->bh[buffer + 1]->b_data, ((char *)&de) + thissize,
+		       26 - thissize);
+
+	return 0;
+}
+
+/*
+ * the caller is responsible for holding the necessary
+ * locks.
+ */
+static int
+adfs_dir_find_entry(struct adfs_dir *dir, unsigned long object_id)
+{
+	int pos, ret;
+
+	ret = -ENOENT;
+
+	for (pos = 5; pos < ADFS_NUM_DIR_ENTRIES * 26 + 5; pos += 26) {
+		struct object_info obj;
+
+		if (!__adfs_dir_get(dir, pos, &obj))
+			break;
+
+		if (obj.file_id == object_id) {
+			ret = pos;
+			break;
+		}
+	}
+
+	return ret;
+}
+
+static int
+adfs_f_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir)
+{
+	int ret;
+
+	if (sz != ADFS_NEWDIR_SIZE)
+		return -EIO;
+
+	ret = adfs_dir_read(sb, id, sz, dir);
+	if (ret)
+		adfs_error(sb, "unable to read directory");
+	else
+		dir->parent_id = adfs_readval(dir->dirtail.new.dirparent, 3);
+
+	return ret;
+}
+
+static int
+adfs_f_setpos(struct adfs_dir *dir, unsigned int fpos)
+{
+	if (fpos >= ADFS_NUM_DIR_ENTRIES)
+		return -ENOENT;
+
+	dir->pos = 5 + fpos * 26;
+	return 0;
+}
+
+static int
+adfs_f_getnext(struct adfs_dir *dir, struct object_info *obj)
+{
+	unsigned int ret;
+
+	ret = __adfs_dir_get(dir, dir->pos, obj);
+	if (ret == 0)
+		dir->pos += 26;
+
+	return ret;
+}
+
+static int
+adfs_f_update(struct adfs_dir *dir, struct object_info *obj)
+{
+	struct super_block *sb = dir->sb;
+	int ret, i;
+
+	ret = adfs_dir_find_entry(dir, obj->file_id);
+	if (ret < 0) {
+		adfs_error(dir->sb, "unable to locate entry to update");
+		goto out;
+	}
+
+	__adfs_dir_put(dir, ret, obj);
+ 
+	/*
+	 * Increment directory sequence number
+	 */
+	dir->bh[0]->b_data[0] += 1;
+	dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 6] += 1;
+
+	ret = adfs_dir_checkbyte(dir);
+	/*
+	 * Update directory check byte
+	 */
+	dir->bh[dir->nr_buffers - 1]->b_data[sb->s_blocksize - 1] = ret;
+
+#if 1
+	{
+	const unsigned int blocksize_bits = sb->s_blocksize_bits;
+
+	memcpy(&dir->dirhead, bufoff(dir->bh, 0), sizeof(dir->dirhead));
+	memcpy(&dir->dirtail, bufoff(dir->bh, 2007), sizeof(dir->dirtail));
+
+	if (dir->dirhead.startmasseq != dir->dirtail.new.endmasseq ||
+	    memcmp(&dir->dirhead.startname, &dir->dirtail.new.endname, 4))
+		goto bad_dir;
+
+	if (memcmp(&dir->dirhead.startname, "Nick", 4) &&
+	    memcmp(&dir->dirhead.startname, "Hugo", 4))
+		goto bad_dir;
+
+	if (adfs_dir_checkbyte(dir) != dir->dirtail.new.dircheckbyte)
+		goto bad_dir;
+	}
+#endif
+	for (i = dir->nr_buffers - 1; i >= 0; i--)
+		mark_buffer_dirty(dir->bh[i]);
+
+	ret = 0;
+out:
+	return ret;
+#if 1
+bad_dir:
+	adfs_error(dir->sb, "whoops!  I broke a directory!");
+	return -EIO;
+#endif
+}
+
+static void
+adfs_f_free(struct adfs_dir *dir)
+{
+	int i;
+
+	for (i = dir->nr_buffers - 1; i >= 0; i--) {
+		brelse(dir->bh[i]);
+		dir->bh[i] = NULL;
+	}
+
+	dir->nr_buffers = 0;
+	dir->sb = NULL;
+}
+
+struct adfs_dir_ops adfs_f_dir_ops = {
+	.read		= adfs_f_read,
+	.setpos		= adfs_f_setpos,
+	.getnext	= adfs_f_getnext,
+	.update		= adfs_f_update,
+	.free		= adfs_f_free
+};
diff --git a/fs/adfs/dir_f.h b/fs/adfs/dir_f.h
new file mode 100644
index 0000000..e471340
--- /dev/null
+++ b/fs/adfs/dir_f.h
@@ -0,0 +1,65 @@
+/*
+ *  linux/fs/adfs/dir_f.h
+ *
+ *  Copyright (C) 1999 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  Structures of directories on the F format disk
+ */
+#ifndef ADFS_DIR_F_H
+#define ADFS_DIR_F_H
+
+/*
+ * Directory header
+ */
+struct adfs_dirheader {
+	unsigned char startmasseq;
+	unsigned char startname[4];
+};
+
+#define ADFS_NEWDIR_SIZE	2048
+#define ADFS_NUM_DIR_ENTRIES	77
+
+/*
+ * Directory entries
+ */
+struct adfs_direntry {
+#define ADFS_F_NAME_LEN 10
+	char dirobname[ADFS_F_NAME_LEN];
+	__u8 dirload[4];
+	__u8 direxec[4];
+	__u8 dirlen[4];
+	__u8 dirinddiscadd[3];
+	__u8 newdiratts;
+};
+
+/*
+ * Directory tail
+ */
+union adfs_dirtail {
+	struct {
+		unsigned char dirlastmask;
+		char dirname[10];
+		unsigned char dirparent[3];
+		char dirtitle[19];
+		unsigned char reserved[14];
+		unsigned char endmasseq;
+		unsigned char endname[4];
+		unsigned char dircheckbyte;
+	} old;
+	struct {
+		unsigned char dirlastmask;
+		unsigned char reserved[2];
+		unsigned char dirparent[3];
+		char dirtitle[19];
+		char dirname[10];
+		unsigned char endmasseq;
+		unsigned char endname[4];
+		unsigned char dircheckbyte;
+	} new;
+};
+
+#endif
diff --git a/fs/adfs/dir_fplus.c b/fs/adfs/dir_fplus.c
new file mode 100644
index 0000000..1ec644e
--- /dev/null
+++ b/fs/adfs/dir_fplus.c
@@ -0,0 +1,179 @@
+/*
+ *  linux/fs/adfs/dir_fplus.c
+ *
+ *  Copyright (C) 1997-1999 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/adfs_fs.h>
+#include <linux/time.h>
+#include <linux/stat.h>
+#include <linux/spinlock.h>
+#include <linux/buffer_head.h>
+#include <linux/string.h>
+
+#include "adfs.h"
+#include "dir_fplus.h"
+
+static int
+adfs_fplus_read(struct super_block *sb, unsigned int id, unsigned int sz, struct adfs_dir *dir)
+{
+	struct adfs_bigdirheader *h;
+	struct adfs_bigdirtail *t;
+	unsigned long block;
+	unsigned int blk, size;
+	int i, ret = -EIO;
+
+	dir->nr_buffers = 0;
+
+	block = __adfs_block_map(sb, id, 0);
+	if (!block) {
+		adfs_error(sb, "dir object %X has a hole at offset 0", id);
+		goto out;
+	}
+
+	dir->bh[0] = sb_bread(sb, block);
+	if (!dir->bh[0])
+		goto out;
+	dir->nr_buffers += 1;
+
+	h = (struct adfs_bigdirheader *)dir->bh[0]->b_data;
+	size = le32_to_cpu(h->bigdirsize);
+	if (size != sz) {
+		printk(KERN_WARNING "adfs: adfs_fplus_read: directory header size\n"
+				" does not match directory size\n");
+	}
+
+	if (h->bigdirversion[0] != 0 || h->bigdirversion[1] != 0 ||
+	    h->bigdirversion[2] != 0 || size & 2047 ||
+	    h->bigdirstartname != cpu_to_le32(BIGDIRSTARTNAME))
+		goto out;
+
+	size >>= sb->s_blocksize_bits;
+	for (blk = 1; blk < size; blk++) {
+		block = __adfs_block_map(sb, id, blk);
+		if (!block) {
+			adfs_error(sb, "dir object %X has a hole at offset %d", id, blk);
+			goto out;
+		}
+
+		dir->bh[blk] = sb_bread(sb, block);
+		if (!dir->bh[blk])
+			goto out;
+		dir->nr_buffers = blk;
+	}
+
+	t = (struct adfs_bigdirtail *)(dir->bh[size - 1]->b_data + (sb->s_blocksize - 8));
+
+	if (t->bigdirendname != cpu_to_le32(BIGDIRENDNAME) ||
+	    t->bigdirendmasseq != h->startmasseq ||
+	    t->reserved[0] != 0 || t->reserved[1] != 0)
+		goto out;
+
+	dir->parent_id = le32_to_cpu(h->bigdirparent);
+	dir->sb = sb;
+	return 0;
+out:
+	for (i = 0; i < dir->nr_buffers; i++)
+		brelse(dir->bh[i]);
+	dir->sb = NULL;
+	return ret;
+}
+
+static int
+adfs_fplus_setpos(struct adfs_dir *dir, unsigned int fpos)
+{
+	struct adfs_bigdirheader *h = (struct adfs_bigdirheader *)dir->bh[0]->b_data;
+	int ret = -ENOENT;
+
+	if (fpos <= le32_to_cpu(h->bigdirentries)) {
+		dir->pos = fpos;
+		ret = 0;
+	}
+
+	return ret;
+}
+
+static void
+dir_memcpy(struct adfs_dir *dir, unsigned int offset, void *to, int len)
+{
+	struct super_block *sb = dir->sb;
+	unsigned int buffer, partial, remainder;
+
+	buffer = offset >> sb->s_blocksize_bits;
+	offset &= sb->s_blocksize - 1;
+
+	partial = sb->s_blocksize - offset;
+
+	if (partial >= len)
+		memcpy(to, dir->bh[buffer]->b_data + offset, len);
+	else {
+		char *c = (char *)to;
+
+		remainder = len - partial;
+
+		memcpy(c, dir->bh[buffer]->b_data + offset, partial);
+		memcpy(c + partial, dir->bh[buffer + 1]->b_data, remainder);
+	}
+}
+
+static int
+adfs_fplus_getnext(struct adfs_dir *dir, struct object_info *obj)
+{
+	struct adfs_bigdirheader *h = (struct adfs_bigdirheader *)dir->bh[0]->b_data;
+	struct adfs_bigdirentry bde;
+	unsigned int offset;
+	int i, ret = -ENOENT;
+
+	if (dir->pos >= le32_to_cpu(h->bigdirentries))
+		goto out;
+
+	offset = offsetof(struct adfs_bigdirheader, bigdirname);
+	offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3);
+	offset += dir->pos * sizeof(struct adfs_bigdirentry);
+
+	dir_memcpy(dir, offset, &bde, sizeof(struct adfs_bigdirentry));
+
+	obj->loadaddr = le32_to_cpu(bde.bigdirload);
+	obj->execaddr = le32_to_cpu(bde.bigdirexec);
+	obj->size     = le32_to_cpu(bde.bigdirlen);
+	obj->file_id  = le32_to_cpu(bde.bigdirindaddr);
+	obj->attr     = le32_to_cpu(bde.bigdirattr);
+	obj->name_len = le32_to_cpu(bde.bigdirobnamelen);
+
+	offset = offsetof(struct adfs_bigdirheader, bigdirname);
+	offset += ((le32_to_cpu(h->bigdirnamelen) + 4) & ~3);
+	offset += le32_to_cpu(h->bigdirentries) * sizeof(struct adfs_bigdirentry);
+	offset += le32_to_cpu(bde.bigdirobnameptr);
+
+	dir_memcpy(dir, offset, obj->name, obj->name_len);
+	for (i = 0; i < obj->name_len; i++)
+		if (obj->name[i] == '/')
+			obj->name[i] = '.';
+
+	dir->pos += 1;
+	ret = 0;
+out:
+	return ret;
+}
+
+static void
+adfs_fplus_free(struct adfs_dir *dir)
+{
+	int i;
+
+	for (i = 0; i < dir->nr_buffers; i++)
+		brelse(dir->bh[i]);
+	dir->sb = NULL;
+}
+
+struct adfs_dir_ops adfs_fplus_dir_ops = {
+	.read		= adfs_fplus_read,
+	.setpos		= adfs_fplus_setpos,
+	.getnext	= adfs_fplus_getnext,
+	.free		= adfs_fplus_free
+};
diff --git a/fs/adfs/dir_fplus.h b/fs/adfs/dir_fplus.h
new file mode 100644
index 0000000..b55aa41
--- /dev/null
+++ b/fs/adfs/dir_fplus.h
@@ -0,0 +1,45 @@
+/*
+ *  linux/fs/adfs/dir_fplus.h
+ *
+ *  Copyright (C) 1999 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ *  Structures of directories on the F+ format disk
+ */
+
+#define ADFS_FPLUS_NAME_LEN	255
+
+#define BIGDIRSTARTNAME ('S' | 'B' << 8 | 'P' << 16 | 'r' << 24)
+#define BIGDIRENDNAME	('o' | 'v' << 8 | 'e' << 16 | 'n' << 24)
+
+struct adfs_bigdirheader {
+	__u8	startmasseq;
+	__u8	bigdirversion[3];
+	__le32	bigdirstartname;
+	__le32	bigdirnamelen;
+	__le32	bigdirsize;
+	__le32	bigdirentries;
+	__le32	bigdirnamesize;
+	__le32	bigdirparent;
+	char	bigdirname[1];
+};
+
+struct adfs_bigdirentry {
+	__le32	bigdirload;
+	__le32	bigdirexec;
+	__le32	bigdirlen;
+	__le32	bigdirindaddr;
+	__le32	bigdirattr;
+	__le32	bigdirobnamelen;
+	__le32	bigdirobnameptr;
+};
+
+struct adfs_bigdirtail {
+	__le32	bigdirendname;
+	__u8	bigdirendmasseq;
+	__u8	reserved[2];
+	__u8	bigdircheckbyte;
+};
diff --git a/fs/adfs/file.c b/fs/adfs/file.c
new file mode 100644
index 0000000..afebbfd
--- /dev/null
+++ b/fs/adfs/file.c
@@ -0,0 +1,43 @@
+/*
+ *  linux/fs/adfs/file.c
+ *
+ * Copyright (C) 1997-1999 Russell King
+ * from:
+ *
+ *  linux/fs/ext2/file.c
+ *
+ * Copyright (C) 1992, 1993, 1994, 1995
+ * Remy Card (card@masi.ibp.fr)
+ * Laboratoire MASI - Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ *  from
+ *
+ *  linux/fs/minix/file.c
+ *
+ *  Copyright (C) 1991, 1992  Linus Torvalds
+ *
+ *  adfs regular file handling primitives           
+ */
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/fcntl.h>
+#include <linux/time.h>
+#include <linux/stat.h>
+#include <linux/buffer_head.h>			/* for file_fsync() */
+#include <linux/adfs_fs.h>
+
+#include "adfs.h"
+
+struct file_operations adfs_file_operations = {
+	.llseek		= generic_file_llseek,
+	.read		= generic_file_read,
+	.mmap		= generic_file_mmap,
+	.fsync		= file_fsync,
+	.write		= generic_file_write,
+	.sendfile	= generic_file_sendfile,
+};
+
+struct inode_operations adfs_file_inode_operations = {
+	.setattr	= adfs_notify_change,
+};
diff --git a/fs/adfs/inode.c b/fs/adfs/inode.c
new file mode 100644
index 0000000..a02802a
--- /dev/null
+++ b/fs/adfs/inode.c
@@ -0,0 +1,395 @@
+/*
+ *  linux/fs/adfs/inode.c
+ *
+ *  Copyright (C) 1997-1999 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/adfs_fs.h>
+#include <linux/time.h>
+#include <linux/stat.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/smp_lock.h>
+#include <linux/module.h>
+#include <linux/buffer_head.h>
+
+#include "adfs.h"
+
+/*
+ * Lookup/Create a block at offset 'block' into 'inode'.  We currently do
+ * not support creation of new blocks, so we return -EIO for this case.
+ */
+static int
+adfs_get_block(struct inode *inode, sector_t block, struct buffer_head *bh,
+	       int create)
+{
+	if (block < 0)
+		goto abort_negative;
+
+	if (!create) {
+		if (block >= inode->i_blocks)
+			goto abort_toobig;
+
+		block = __adfs_block_map(inode->i_sb, inode->i_ino, block);
+		if (block)
+			map_bh(bh, inode->i_sb, block);
+		return 0;
+	}
+	/* don't support allocation of blocks yet */
+	return -EIO;
+
+abort_negative:
+	adfs_error(inode->i_sb, "block %d < 0", block);
+	return -EIO;
+
+abort_toobig:
+	return 0;
+}
+
+static int adfs_writepage(struct page *page, struct writeback_control *wbc)
+{
+	return block_write_full_page(page, adfs_get_block, wbc);
+}
+
+static int adfs_readpage(struct file *file, struct page *page)
+{
+	return block_read_full_page(page, adfs_get_block);
+}
+
+static int adfs_prepare_write(struct file *file, struct page *page, unsigned int from, unsigned int to)
+{
+	return cont_prepare_write(page, from, to, adfs_get_block,
+		&ADFS_I(page->mapping->host)->mmu_private);
+}
+
+static sector_t _adfs_bmap(struct address_space *mapping, sector_t block)
+{
+	return generic_block_bmap(mapping, block, adfs_get_block);
+}
+
+static struct address_space_operations adfs_aops = {
+	.readpage	= adfs_readpage,
+	.writepage	= adfs_writepage,
+	.sync_page	= block_sync_page,
+	.prepare_write	= adfs_prepare_write,
+	.commit_write	= generic_commit_write,
+	.bmap		= _adfs_bmap
+};
+
+static inline unsigned int
+adfs_filetype(struct inode *inode)
+{
+	unsigned int type;
+
+	if (ADFS_I(inode)->stamped)
+		type = (ADFS_I(inode)->loadaddr >> 8) & 0xfff;
+	else
+		type = (unsigned int) -1;
+
+	return type;
+}
+
+/*
+ * Convert ADFS attributes and filetype to Linux permission.
+ */
+static umode_t
+adfs_atts2mode(struct super_block *sb, struct inode *inode)
+{
+	unsigned int filetype, attr = ADFS_I(inode)->attr;
+	umode_t mode, rmask;
+	struct adfs_sb_info *asb = ADFS_SB(sb);
+
+	if (attr & ADFS_NDA_DIRECTORY) {
+		mode = S_IRUGO & asb->s_owner_mask;
+		return S_IFDIR | S_IXUGO | mode;
+	}
+
+	filetype = adfs_filetype(inode);
+
+	switch (filetype) {
+	case 0xfc0:	/* LinkFS */
+		return S_IFLNK|S_IRWXUGO;
+
+	case 0xfe6:	/* UnixExec */
+		rmask = S_IRUGO | S_IXUGO;
+		break;
+
+	default:
+		rmask = S_IRUGO;
+	}
+
+	mode = S_IFREG;
+
+	if (attr & ADFS_NDA_OWNER_READ)
+		mode |= rmask & asb->s_owner_mask;
+
+	if (attr & ADFS_NDA_OWNER_WRITE)
+		mode |= S_IWUGO & asb->s_owner_mask;
+
+	if (attr & ADFS_NDA_PUBLIC_READ)
+		mode |= rmask & asb->s_other_mask;
+
+	if (attr & ADFS_NDA_PUBLIC_WRITE)
+		mode |= S_IWUGO & asb->s_other_mask;
+	return mode;
+}
+
+/*
+ * Convert Linux permission to ADFS attribute.  We try to do the reverse
+ * of atts2mode, but there is not a 1:1 translation.
+ */
+static int
+adfs_mode2atts(struct super_block *sb, struct inode *inode)
+{
+	umode_t mode;
+	int attr;
+	struct adfs_sb_info *asb = ADFS_SB(sb);
+
+	/* FIXME: should we be able to alter a link? */
+	if (S_ISLNK(inode->i_mode))
+		return ADFS_I(inode)->attr;
+
+	if (S_ISDIR(inode->i_mode))
+		attr = ADFS_NDA_DIRECTORY;
+	else
+		attr = 0;
+
+	mode = inode->i_mode & asb->s_owner_mask;
+	if (mode & S_IRUGO)
+		attr |= ADFS_NDA_OWNER_READ;
+	if (mode & S_IWUGO)
+		attr |= ADFS_NDA_OWNER_WRITE;
+
+	mode = inode->i_mode & asb->s_other_mask;
+	mode &= ~asb->s_owner_mask;
+	if (mode & S_IRUGO)
+		attr |= ADFS_NDA_PUBLIC_READ;
+	if (mode & S_IWUGO)
+		attr |= ADFS_NDA_PUBLIC_WRITE;
+
+	return attr;
+}
+
+/*
+ * Convert an ADFS time to Unix time.  ADFS has a 40-bit centi-second time
+ * referenced to 1 Jan 1900 (til 2248)
+ */
+static void
+adfs_adfs2unix_time(struct timespec *tv, struct inode *inode)
+{
+	unsigned int high, low;
+
+	if (ADFS_I(inode)->stamped == 0)
+		goto cur_time;
+
+	high = ADFS_I(inode)->loadaddr << 24;
+	low  = ADFS_I(inode)->execaddr;
+
+	high |= low >> 8;
+	low  &= 255;
+
+	/* Files dated pre  01 Jan 1970 00:00:00. */
+	if (high < 0x336e996a)
+		goto too_early;
+
+	/* Files dated post 18 Jan 2038 03:14:05. */
+	if (high >= 0x656e9969)
+		goto too_late;
+
+	/* discard 2208988800 (0x336e996a00) seconds of time */
+	high -= 0x336e996a;
+
+	/* convert 40-bit centi-seconds to 32-bit seconds */
+	tv->tv_sec = (((high % 100) << 8) + low) / 100 + (high / 100 << 8);
+	tv->tv_nsec = 0;
+	return;
+
+ cur_time:
+	*tv = CURRENT_TIME_SEC;
+	return;
+
+ too_early:
+	tv->tv_sec = tv->tv_nsec = 0;
+	return;
+
+ too_late:
+	tv->tv_sec = 0x7ffffffd;
+	tv->tv_nsec = 0;
+	return;
+}
+
+/*
+ * Convert an Unix time to ADFS time.  We only do this if the entry has a
+ * time/date stamp already.
+ */
+static void
+adfs_unix2adfs_time(struct inode *inode, unsigned int secs)
+{
+	unsigned int high, low;
+
+	if (ADFS_I(inode)->stamped) {
+		/* convert 32-bit seconds to 40-bit centi-seconds */
+		low  = (secs & 255) * 100;
+		high = (secs / 256) * 100 + (low >> 8) + 0x336e996a;
+
+		ADFS_I(inode)->loadaddr = (high >> 24) |
+				(ADFS_I(inode)->loadaddr & ~0xff);
+		ADFS_I(inode)->execaddr = (low & 255) | (high << 8);
+	}
+}
+
+/*
+ * Fill in the inode information from the object information.
+ *
+ * Note that this is an inode-less filesystem, so we can't use the inode
+ * number to reference the metadata on the media.  Instead, we use the
+ * inode number to hold the object ID, which in turn will tell us where
+ * the data is held.  We also save the parent object ID, and with these
+ * two, we can locate the metadata.
+ *
+ * This does mean that we rely on an objects parent remaining the same at
+ * all times - we cannot cope with a cross-directory rename (yet).
+ */
+struct inode *
+adfs_iget(struct super_block *sb, struct object_info *obj)
+{
+	struct inode *inode;
+
+	inode = new_inode(sb);
+	if (!inode)
+		goto out;
+
+	inode->i_uid	 = ADFS_SB(sb)->s_uid;
+	inode->i_gid	 = ADFS_SB(sb)->s_gid;
+	inode->i_ino	 = obj->file_id;
+	inode->i_size	 = obj->size;
+	inode->i_nlink	 = 2;
+	inode->i_blksize = PAGE_SIZE;
+	inode->i_blocks	 = (inode->i_size + sb->s_blocksize - 1) >>
+			    sb->s_blocksize_bits;
+
+	/*
+	 * we need to save the parent directory ID so that
+	 * write_inode can update the directory information
+	 * for this file.  This will need special handling
+	 * for cross-directory renames.
+	 */
+	ADFS_I(inode)->parent_id = obj->parent_id;
+	ADFS_I(inode)->loadaddr  = obj->loadaddr;
+	ADFS_I(inode)->execaddr  = obj->execaddr;
+	ADFS_I(inode)->attr      = obj->attr;
+	ADFS_I(inode)->stamped	  = ((obj->loadaddr & 0xfff00000) == 0xfff00000);
+
+	inode->i_mode	 = adfs_atts2mode(sb, inode);
+	adfs_adfs2unix_time(&inode->i_mtime, inode);
+	inode->i_atime = inode->i_mtime;
+	inode->i_ctime = inode->i_mtime;
+
+	if (S_ISDIR(inode->i_mode)) {
+		inode->i_op	= &adfs_dir_inode_operations;
+		inode->i_fop	= &adfs_dir_operations;
+	} else if (S_ISREG(inode->i_mode)) {
+		inode->i_op	= &adfs_file_inode_operations;
+		inode->i_fop	= &adfs_file_operations;
+		inode->i_mapping->a_ops = &adfs_aops;
+		ADFS_I(inode)->mmu_private = inode->i_size;
+	}
+
+	insert_inode_hash(inode);
+
+out:
+	return inode;
+}
+
+/*
+ * Validate and convert a changed access mode/time to their ADFS equivalents.
+ * adfs_write_inode will actually write the information back to the directory
+ * later.
+ */
+int
+adfs_notify_change(struct dentry *dentry, struct iattr *attr)
+{
+	struct inode *inode = dentry->d_inode;
+	struct super_block *sb = inode->i_sb;
+	unsigned int ia_valid = attr->ia_valid;
+	int error;
+	
+	lock_kernel();
+
+	error = inode_change_ok(inode, attr);
+
+	/*
+	 * we can't change the UID or GID of any file -
+	 * we have a global UID/GID in the superblock
+	 */
+	if ((ia_valid & ATTR_UID && attr->ia_uid != ADFS_SB(sb)->s_uid) ||
+	    (ia_valid & ATTR_GID && attr->ia_gid != ADFS_SB(sb)->s_gid))
+		error = -EPERM;
+
+	if (error)
+		goto out;
+
+	if (ia_valid & ATTR_SIZE)
+		error = vmtruncate(inode, attr->ia_size);
+
+	if (error)
+		goto out;
+
+	if (ia_valid & ATTR_MTIME) {
+		inode->i_mtime = attr->ia_mtime;
+		adfs_unix2adfs_time(inode, attr->ia_mtime.tv_sec);
+	}
+	/*
+	 * FIXME: should we make these == to i_mtime since we don't
+	 * have the ability to represent them in our filesystem?
+	 */
+	if (ia_valid & ATTR_ATIME)
+		inode->i_atime = attr->ia_atime;
+	if (ia_valid & ATTR_CTIME)
+		inode->i_ctime = attr->ia_ctime;
+	if (ia_valid & ATTR_MODE) {
+		ADFS_I(inode)->attr = adfs_mode2atts(sb, inode);
+		inode->i_mode = adfs_atts2mode(sb, inode);
+	}
+
+	/*
+	 * FIXME: should we be marking this inode dirty even if
+	 * we don't have any metadata to write back?
+	 */
+	if (ia_valid & (ATTR_SIZE | ATTR_MTIME | ATTR_MODE))
+		mark_inode_dirty(inode);
+out:
+	unlock_kernel();
+	return error;
+}
+
+/*
+ * write an existing inode back to the directory, and therefore the disk.
+ * The adfs-specific inode data has already been updated by
+ * adfs_notify_change()
+ */
+int adfs_write_inode(struct inode *inode, int unused)
+{
+	struct super_block *sb = inode->i_sb;
+	struct object_info obj;
+	int ret;
+
+	lock_kernel();
+	obj.file_id	= inode->i_ino;
+	obj.name_len	= 0;
+	obj.parent_id	= ADFS_I(inode)->parent_id;
+	obj.loadaddr	= ADFS_I(inode)->loadaddr;
+	obj.execaddr	= ADFS_I(inode)->execaddr;
+	obj.attr	= ADFS_I(inode)->attr;
+	obj.size	= inode->i_size;
+
+	ret = adfs_dir_update(sb, &obj);
+	unlock_kernel();
+	return ret;
+}
+MODULE_LICENSE("GPL");
diff --git a/fs/adfs/map.c b/fs/adfs/map.c
new file mode 100644
index 0000000..92ab4fb
--- /dev/null
+++ b/fs/adfs/map.c
@@ -0,0 +1,296 @@
+/*
+ *  linux/fs/adfs/map.c
+ *
+ *  Copyright (C) 1997-2002 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/adfs_fs.h>
+#include <linux/spinlock.h>
+#include <linux/buffer_head.h>
+
+#include <asm/unaligned.h>
+
+#include "adfs.h"
+
+/*
+ * The ADFS map is basically a set of sectors.  Each sector is called a
+ * zone which contains a bitstream made up of variable sized fragments.
+ * Each bit refers to a set of bytes in the filesystem, defined by
+ * log2bpmb.  This may be larger or smaller than the sector size, but
+ * the overall size it describes will always be a round number of
+ * sectors.  A fragment id is always idlen bits long.
+ *
+ *  < idlen > <       n        > <1>
+ * +---------+-------//---------+---+
+ * | frag id |  0000....000000  | 1 |
+ * +---------+-------//---------+---+
+ *
+ * The physical disk space used by a fragment is taken from the start of
+ * the fragment id up to and including the '1' bit - ie, idlen + n + 1
+ * bits.
+ *
+ * A fragment id can be repeated multiple times in the whole map for
+ * large or fragmented files.  The first map zone a fragment starts in
+ * is given by fragment id / ids_per_zone - this allows objects to start
+ * from any zone on the disk.
+ *
+ * Free space is described by a linked list of fragments.  Each free
+ * fragment describes free space in the same way as the other fragments,
+ * however, the frag id specifies an offset (in map bits) from the end
+ * of this fragment to the start of the next free fragment.
+ *
+ * Objects stored on the disk are allocated object ids (we use these as
+ * our inode numbers.)  Object ids contain a fragment id and an optional
+ * offset.  This allows a directory fragment to contain small files
+ * associated with that directory.
+ */
+
+/*
+ * For the future...
+ */
+static DEFINE_RWLOCK(adfs_map_lock);
+
+/*
+ * This is fun.  We need to load up to 19 bits from the map at an
+ * arbitary bit alignment.  (We're limited to 19 bits by F+ version 2).
+ */
+#define GET_FRAG_ID(_map,_start,_idmask)				\
+	({								\
+		unsigned char *_m = _map + (_start >> 3);		\
+		u32 _frag = get_unaligned((u32 *)_m);			\
+		_frag >>= (_start & 7);					\
+		_frag & _idmask;					\
+	})
+
+/*
+ * return the map bit offset of the fragment frag_id in the zone dm.
+ * Note that the loop is optimised for best asm code - look at the
+ * output of:
+ *  gcc -D__KERNEL__ -O2 -I../../include -o - -S map.c
+ */
+static int
+lookup_zone(const struct adfs_discmap *dm, const unsigned int idlen,
+	    const unsigned int frag_id, unsigned int *offset)
+{
+	const unsigned int mapsize = dm->dm_endbit;
+	const u32 idmask = (1 << idlen) - 1;
+	unsigned char *map = dm->dm_bh->b_data + 4;
+	unsigned int start = dm->dm_startbit;
+	unsigned int mapptr;
+	u32 frag;
+
+	do {
+		frag = GET_FRAG_ID(map, start, idmask);
+		mapptr = start + idlen;
+
+		/*
+		 * find end of fragment
+		 */
+		{
+			__le32 *_map = (__le32 *)map;
+			u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31);
+			while (v == 0) {
+				mapptr = (mapptr & ~31) + 32;
+				if (mapptr >= mapsize)
+					goto error;
+				v = le32_to_cpu(_map[mapptr >> 5]);
+			}
+
+			mapptr += 1 + ffz(~v);
+		}
+
+		if (frag == frag_id)
+			goto found;
+again:
+		start = mapptr;
+	} while (mapptr < mapsize);
+	return -1;
+
+error:
+	printk(KERN_ERR "adfs: oversized fragment 0x%x at 0x%x-0x%x\n",
+		frag, start, mapptr);
+	return -1;
+
+found:
+	{
+		int length = mapptr - start;
+		if (*offset >= length) {
+			*offset -= length;
+			goto again;
+		}
+	}
+	return start + *offset;
+}
+
+/*
+ * Scan the free space map, for this zone, calculating the total
+ * number of map bits in each free space fragment.
+ *
+ * Note: idmask is limited to 15 bits [3.2]
+ */
+static unsigned int
+scan_free_map(struct adfs_sb_info *asb, struct adfs_discmap *dm)
+{
+	const unsigned int mapsize = dm->dm_endbit + 32;
+	const unsigned int idlen  = asb->s_idlen;
+	const unsigned int frag_idlen = idlen <= 15 ? idlen : 15;
+	const u32 idmask = (1 << frag_idlen) - 1;
+	unsigned char *map = dm->dm_bh->b_data;
+	unsigned int start = 8, mapptr;
+	u32 frag;
+	unsigned long total = 0;
+
+	/*
+	 * get fragment id
+	 */
+	frag = GET_FRAG_ID(map, start, idmask);
+
+	/*
+	 * If the freelink is null, then no free fragments
+	 * exist in this zone.
+	 */
+	if (frag == 0)
+		return 0;
+
+	do {
+		start += frag;
+
+		/*
+		 * get fragment id
+		 */
+		frag = GET_FRAG_ID(map, start, idmask);
+		mapptr = start + idlen;
+
+		/*
+		 * find end of fragment
+		 */
+		{
+			__le32 *_map = (__le32 *)map;
+			u32 v = le32_to_cpu(_map[mapptr >> 5]) >> (mapptr & 31);
+			while (v == 0) {
+				mapptr = (mapptr & ~31) + 32;
+				if (mapptr >= mapsize)
+					goto error;
+				v = le32_to_cpu(_map[mapptr >> 5]);
+			}
+
+			mapptr += 1 + ffz(~v);
+		}
+
+		total += mapptr - start;
+	} while (frag >= idlen + 1);
+
+	if (frag != 0)
+		printk(KERN_ERR "adfs: undersized free fragment\n");
+
+	return total;
+error:
+	printk(KERN_ERR "adfs: oversized free fragment\n");
+	return 0;
+}
+
+static int
+scan_map(struct adfs_sb_info *asb, unsigned int zone,
+	 const unsigned int frag_id, unsigned int mapoff)
+{
+	const unsigned int idlen = asb->s_idlen;
+	struct adfs_discmap *dm, *dm_end;
+	int result;
+
+	dm	= asb->s_map + zone;
+	zone	= asb->s_map_size;
+	dm_end	= asb->s_map + zone;
+
+	do {
+		result = lookup_zone(dm, idlen, frag_id, &mapoff);
+
+		if (result != -1)
+			goto found;
+
+		dm ++;
+		if (dm == dm_end)
+			dm = asb->s_map;
+	} while (--zone > 0);
+
+	return -1;
+found:
+	result -= dm->dm_startbit;
+	result += dm->dm_startblk;
+
+	return result;
+}
+
+/*
+ * calculate the amount of free blocks in the map.
+ *
+ *              n=1
+ *  total_free = E(free_in_zone_n)
+ *              nzones
+ */
+unsigned int
+adfs_map_free(struct super_block *sb)
+{
+	struct adfs_sb_info *asb = ADFS_SB(sb);
+	struct adfs_discmap *dm;
+	unsigned int total = 0;
+	unsigned int zone;
+
+	dm   = asb->s_map;
+	zone = asb->s_map_size;
+
+	do {
+		total += scan_free_map(asb, dm++);
+	} while (--zone > 0);
+
+	return signed_asl(total, asb->s_map2blk);
+}
+
+int
+adfs_map_lookup(struct super_block *sb, unsigned int frag_id,
+		unsigned int offset)
+{
+	struct adfs_sb_info *asb = ADFS_SB(sb);
+	unsigned int zone, mapoff;
+	int result;
+
+	/*
+	 * map & root fragment is special - it starts in the center of the
+	 * disk.  The other fragments start at zone (frag / ids_per_zone)
+	 */
+	if (frag_id == ADFS_ROOT_FRAG)
+		zone = asb->s_map_size >> 1;
+	else
+		zone = frag_id / asb->s_ids_per_zone;
+
+	if (zone >= asb->s_map_size)
+		goto bad_fragment;
+
+	/* Convert sector offset to map offset */
+	mapoff = signed_asl(offset, -asb->s_map2blk);
+
+	read_lock(&adfs_map_lock);
+	result = scan_map(asb, zone, frag_id, mapoff);
+	read_unlock(&adfs_map_lock);
+
+	if (result > 0) {
+		unsigned int secoff;
+
+		/* Calculate sector offset into map block */
+		secoff = offset - signed_asl(mapoff, asb->s_map2blk);
+		return secoff + signed_asl(result, asb->s_map2blk);
+	}
+
+	adfs_error(sb, "fragment 0x%04x at offset %d not found in map",
+		   frag_id, offset);
+	return 0;
+
+bad_fragment:
+	adfs_error(sb, "invalid fragment 0x%04x (zone = %d, max = %d)",
+		   frag_id, zone, asb->s_map_size);
+	return 0;
+}
diff --git a/fs/adfs/super.c b/fs/adfs/super.c
new file mode 100644
index 0000000..2439632
--- /dev/null
+++ b/fs/adfs/super.c
@@ -0,0 +1,508 @@
+/*
+ *  linux/fs/adfs/super.c
+ *
+ *  Copyright (C) 1997-1999 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/adfs_fs.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/stat.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/buffer_head.h>
+#include <linux/vfs.h>
+#include <linux/parser.h>
+#include <linux/bitops.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#include <stdarg.h>
+
+#include "adfs.h"
+#include "dir_f.h"
+#include "dir_fplus.h"
+
+void __adfs_error(struct super_block *sb, const char *function, const char *fmt, ...)
+{
+	char error_buf[128];
+	va_list args;
+
+	va_start(args, fmt);
+	vsprintf(error_buf, fmt, args);
+	va_end(args);
+
+	printk(KERN_CRIT "ADFS-fs error (device %s)%s%s: %s\n",
+		sb->s_id, function ? ": " : "",
+		function ? function : "", error_buf);
+}
+
+static int adfs_checkdiscrecord(struct adfs_discrecord *dr)
+{
+	int i;
+
+	/* sector size must be 256, 512 or 1024 bytes */
+	if (dr->log2secsize != 8 &&
+	    dr->log2secsize != 9 &&
+	    dr->log2secsize != 10)
+		return 1;
+
+	/* idlen must be at least log2secsize + 3 */
+	if (dr->idlen < dr->log2secsize + 3)
+		return 1;
+
+	/* we cannot have such a large disc that we
+	 * are unable to represent sector offsets in
+	 * 32 bits.  This works out at 2.0 TB.
+	 */
+	if (le32_to_cpu(dr->disc_size_high) >> dr->log2secsize)
+		return 1;
+
+	/* idlen must be no greater than 19 v2 [1.0] */
+	if (dr->idlen > 19)
+		return 1;
+
+	/* reserved bytes should be zero */
+	for (i = 0; i < sizeof(dr->unused52); i++)
+		if (dr->unused52[i] != 0)
+			return 1;
+
+	return 0;
+}
+
+static unsigned char adfs_calczonecheck(struct super_block *sb, unsigned char *map)
+{
+	unsigned int v0, v1, v2, v3;
+	int i;
+
+	v0 = v1 = v2 = v3 = 0;
+	for (i = sb->s_blocksize - 4; i; i -= 4) {
+		v0 += map[i]     + (v3 >> 8);
+		v3 &= 0xff;
+		v1 += map[i + 1] + (v0 >> 8);
+		v0 &= 0xff;
+		v2 += map[i + 2] + (v1 >> 8);
+		v1 &= 0xff;
+		v3 += map[i + 3] + (v2 >> 8);
+		v2 &= 0xff;
+	}
+	v0 +=           v3 >> 8;
+	v1 += map[1] + (v0 >> 8);
+	v2 += map[2] + (v1 >> 8);
+	v3 += map[3] + (v2 >> 8);
+
+	return v0 ^ v1 ^ v2 ^ v3;
+}
+
+static int adfs_checkmap(struct super_block *sb, struct adfs_discmap *dm)
+{
+	unsigned char crosscheck = 0, zonecheck = 1;
+	int i;
+
+	for (i = 0; i < ADFS_SB(sb)->s_map_size; i++) {
+		unsigned char *map;
+
+		map = dm[i].dm_bh->b_data;
+
+		if (adfs_calczonecheck(sb, map) != map[0]) {
+			adfs_error(sb, "zone %d fails zonecheck", i);
+			zonecheck = 0;
+		}
+		crosscheck ^= map[3];
+	}
+	if (crosscheck != 0xff)
+		adfs_error(sb, "crosscheck != 0xff");
+	return crosscheck == 0xff && zonecheck;
+}
+
+static void adfs_put_super(struct super_block *sb)
+{
+	int i;
+	struct adfs_sb_info *asb = ADFS_SB(sb);
+
+	for (i = 0; i < asb->s_map_size; i++)
+		brelse(asb->s_map[i].dm_bh);
+	kfree(asb->s_map);
+	kfree(asb);
+	sb->s_fs_info = NULL;
+}
+
+enum {Opt_uid, Opt_gid, Opt_ownmask, Opt_othmask, Opt_err};
+
+static match_table_t tokens = {
+	{Opt_uid, "uid=%u"},
+	{Opt_gid, "gid=%u"},
+	{Opt_ownmask, "ownmask=%o"},
+	{Opt_othmask, "othmask=%o"},
+	{Opt_err, NULL}
+};
+
+static int parse_options(struct super_block *sb, char *options)
+{
+	char *p;
+	struct adfs_sb_info *asb = ADFS_SB(sb);
+	int option;
+
+	if (!options)
+		return 0;
+
+	while ((p = strsep(&options, ",")) != NULL) {
+		substring_t args[MAX_OPT_ARGS];
+		int token;
+		if (!*p)
+			continue;
+
+		token = match_token(p, tokens, args);
+		switch (token) {
+		case Opt_uid:
+			if (match_int(args, &option))
+				return -EINVAL;
+			asb->s_uid = option;
+			break;
+		case Opt_gid:
+			if (match_int(args, &option))
+				return -EINVAL;
+			asb->s_gid = option;
+			break;
+		case Opt_ownmask:
+			if (match_octal(args, &option))
+				return -EINVAL;
+			asb->s_owner_mask = option;
+			break;
+		case Opt_othmask:
+			if (match_octal(args, &option))
+				return -EINVAL;
+			asb->s_other_mask = option;
+			break;
+		default:
+			printk("ADFS-fs: unrecognised mount option \"%s\" "
+					"or missing value\n", p);
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+static int adfs_remount(struct super_block *sb, int *flags, char *data)
+{
+	*flags |= MS_NODIRATIME;
+	return parse_options(sb, data);
+}
+
+static int adfs_statfs(struct super_block *sb, struct kstatfs *buf)
+{
+	struct adfs_sb_info *asb = ADFS_SB(sb);
+
+	buf->f_type    = ADFS_SUPER_MAGIC;
+	buf->f_namelen = asb->s_namelen;
+	buf->f_bsize   = sb->s_blocksize;
+	buf->f_blocks  = asb->s_size;
+	buf->f_files   = asb->s_ids_per_zone * asb->s_map_size;
+	buf->f_bavail  =
+	buf->f_bfree   = adfs_map_free(sb);
+	buf->f_ffree   = (long)(buf->f_bfree * buf->f_files) / (long)buf->f_blocks;
+
+	return 0;
+}
+
+static kmem_cache_t *adfs_inode_cachep;
+
+static struct inode *adfs_alloc_inode(struct super_block *sb)
+{
+	struct adfs_inode_info *ei;
+	ei = (struct adfs_inode_info *)kmem_cache_alloc(adfs_inode_cachep, SLAB_KERNEL);
+	if (!ei)
+		return NULL;
+	return &ei->vfs_inode;
+}
+
+static void adfs_destroy_inode(struct inode *inode)
+{
+	kmem_cache_free(adfs_inode_cachep, ADFS_I(inode));
+}
+
+static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags)
+{
+	struct adfs_inode_info *ei = (struct adfs_inode_info *) foo;
+
+	if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
+	    SLAB_CTOR_CONSTRUCTOR)
+		inode_init_once(&ei->vfs_inode);
+}
+ 
+static int init_inodecache(void)
+{
+	adfs_inode_cachep = kmem_cache_create("adfs_inode_cache",
+					     sizeof(struct adfs_inode_info),
+					     0, SLAB_RECLAIM_ACCOUNT,
+					     init_once, NULL);
+	if (adfs_inode_cachep == NULL)
+		return -ENOMEM;
+	return 0;
+}
+
+static void destroy_inodecache(void)
+{
+	if (kmem_cache_destroy(adfs_inode_cachep))
+		printk(KERN_INFO "adfs_inode_cache: not all structures were freed\n");
+}
+
+static struct super_operations adfs_sops = {
+	.alloc_inode	= adfs_alloc_inode,
+	.destroy_inode	= adfs_destroy_inode,
+	.write_inode	= adfs_write_inode,
+	.put_super	= adfs_put_super,
+	.statfs		= adfs_statfs,
+	.remount_fs	= adfs_remount,
+};
+
+static struct adfs_discmap *adfs_read_map(struct super_block *sb, struct adfs_discrecord *dr)
+{
+	struct adfs_discmap *dm;
+	unsigned int map_addr, zone_size, nzones;
+	int i, zone;
+	struct adfs_sb_info *asb = ADFS_SB(sb);
+
+	nzones    = asb->s_map_size;
+	zone_size = (8 << dr->log2secsize) - le16_to_cpu(dr->zone_spare);
+	map_addr  = (nzones >> 1) * zone_size -
+		     ((nzones > 1) ? ADFS_DR_SIZE_BITS : 0);
+	map_addr  = signed_asl(map_addr, asb->s_map2blk);
+
+	asb->s_ids_per_zone = zone_size / (asb->s_idlen + 1);
+
+	dm = kmalloc(nzones * sizeof(*dm), GFP_KERNEL);
+	if (dm == NULL) {
+		adfs_error(sb, "not enough memory");
+		return NULL;
+	}
+
+	for (zone = 0; zone < nzones; zone++, map_addr++) {
+		dm[zone].dm_startbit = 0;
+		dm[zone].dm_endbit   = zone_size;
+		dm[zone].dm_startblk = zone * zone_size - ADFS_DR_SIZE_BITS;
+		dm[zone].dm_bh       = sb_bread(sb, map_addr);
+
+		if (!dm[zone].dm_bh) {
+			adfs_error(sb, "unable to read map");
+			goto error_free;
+		}
+	}
+
+	/* adjust the limits for the first and last map zones */
+	i = zone - 1;
+	dm[0].dm_startblk = 0;
+	dm[0].dm_startbit = ADFS_DR_SIZE_BITS;
+	dm[i].dm_endbit   = (le32_to_cpu(dr->disc_size_high) << (32 - dr->log2bpmb)) +
+			    (le32_to_cpu(dr->disc_size) >> dr->log2bpmb) +
+			    (ADFS_DR_SIZE_BITS - i * zone_size);
+
+	if (adfs_checkmap(sb, dm))
+		return dm;
+
+	adfs_error(sb, NULL, "map corrupted");
+
+error_free:
+	while (--zone >= 0)
+		brelse(dm[zone].dm_bh);
+
+	kfree(dm);
+	return NULL;
+}
+
+static inline unsigned long adfs_discsize(struct adfs_discrecord *dr, int block_bits)
+{
+	unsigned long discsize;
+
+	discsize  = le32_to_cpu(dr->disc_size_high) << (32 - block_bits);
+	discsize |= le32_to_cpu(dr->disc_size) >> block_bits;
+
+	return discsize;
+}
+
+static int adfs_fill_super(struct super_block *sb, void *data, int silent)
+{
+	struct adfs_discrecord *dr;
+	struct buffer_head *bh;
+	struct object_info root_obj;
+	unsigned char *b_data;
+	struct adfs_sb_info *asb;
+	struct inode *root;
+
+	sb->s_flags |= MS_NODIRATIME;
+
+	asb = kmalloc(sizeof(*asb), GFP_KERNEL);
+	if (!asb)
+		return -ENOMEM;
+	sb->s_fs_info = asb;
+	memset(asb, 0, sizeof(*asb));
+
+	/* set default options */
+	asb->s_uid = 0;
+	asb->s_gid = 0;
+	asb->s_owner_mask = S_IRWXU;
+	asb->s_other_mask = S_IRWXG | S_IRWXO;
+
+	if (parse_options(sb, data))
+		goto error;
+
+	sb_set_blocksize(sb, BLOCK_SIZE);
+	if (!(bh = sb_bread(sb, ADFS_DISCRECORD / BLOCK_SIZE))) {
+		adfs_error(sb, "unable to read superblock");
+		goto error;
+	}
+
+	b_data = bh->b_data + (ADFS_DISCRECORD % BLOCK_SIZE);
+
+	if (adfs_checkbblk(b_data)) {
+		if (!silent)
+			printk("VFS: Can't find an adfs filesystem on dev "
+				"%s.\n", sb->s_id);
+		goto error_free_bh;
+	}
+
+	dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET);
+
+	/*
+	 * Do some sanity checks on the ADFS disc record
+	 */
+	if (adfs_checkdiscrecord(dr)) {
+		if (!silent)
+			printk("VPS: Can't find an adfs filesystem on dev "
+				"%s.\n", sb->s_id);
+		goto error_free_bh;
+	}
+
+	brelse(bh);
+	if (sb_set_blocksize(sb, 1 << dr->log2secsize)) {
+		bh = sb_bread(sb, ADFS_DISCRECORD / sb->s_blocksize);
+		if (!bh) {
+			adfs_error(sb, "couldn't read superblock on "
+				"2nd try.");
+			goto error;
+		}
+		b_data = bh->b_data + (ADFS_DISCRECORD % sb->s_blocksize);
+		if (adfs_checkbblk(b_data)) {
+			adfs_error(sb, "disc record mismatch, very weird!");
+			goto error_free_bh;
+		}
+		dr = (struct adfs_discrecord *)(b_data + ADFS_DR_OFFSET);
+	} else {
+		if (!silent)
+			printk(KERN_ERR "VFS: Unsupported blocksize on dev "
+				"%s.\n", sb->s_id);
+		goto error;
+	}
+
+	/*
+	 * blocksize on this device should now be set to the ADFS log2secsize
+	 */
+
+	sb->s_magic		= ADFS_SUPER_MAGIC;
+	asb->s_idlen		= dr->idlen;
+	asb->s_map_size		= dr->nzones | (dr->nzones_high << 8);
+	asb->s_map2blk		= dr->log2bpmb - dr->log2secsize;
+	asb->s_size    		= adfs_discsize(dr, sb->s_blocksize_bits);
+	asb->s_version 		= dr->format_version;
+	asb->s_log2sharesize	= dr->log2sharesize;
+	
+	asb->s_map = adfs_read_map(sb, dr);
+	if (!asb->s_map)
+		goto error_free_bh;
+
+	brelse(bh);
+
+	/*
+	 * set up enough so that we can read an inode
+	 */
+	sb->s_op = &adfs_sops;
+
+	dr = (struct adfs_discrecord *)(asb->s_map[0].dm_bh->b_data + 4);
+
+	root_obj.parent_id = root_obj.file_id = le32_to_cpu(dr->root);
+	root_obj.name_len  = 0;
+	root_obj.loadaddr  = 0;
+	root_obj.execaddr  = 0;
+	root_obj.size	   = ADFS_NEWDIR_SIZE;
+	root_obj.attr	   = ADFS_NDA_DIRECTORY   | ADFS_NDA_OWNER_READ |
+			     ADFS_NDA_OWNER_WRITE | ADFS_NDA_PUBLIC_READ;
+
+	/*
+	 * If this is a F+ disk with variable length directories,
+	 * get the root_size from the disc record.
+	 */
+	if (asb->s_version) {
+		root_obj.size = le32_to_cpu(dr->root_size);
+		asb->s_dir     = &adfs_fplus_dir_ops;
+		asb->s_namelen = ADFS_FPLUS_NAME_LEN;
+	} else {
+		asb->s_dir     = &adfs_f_dir_ops;
+		asb->s_namelen = ADFS_F_NAME_LEN;
+	}
+
+	root = adfs_iget(sb, &root_obj);
+	sb->s_root = d_alloc_root(root);
+	if (!sb->s_root) {
+		int i;
+		iput(root);
+		for (i = 0; i < asb->s_map_size; i++)
+			brelse(asb->s_map[i].dm_bh);
+		kfree(asb->s_map);
+		adfs_error(sb, "get root inode failed\n");
+		goto error;
+	} else
+		sb->s_root->d_op = &adfs_dentry_operations;
+	return 0;
+
+error_free_bh:
+	brelse(bh);
+error:
+	sb->s_fs_info = NULL;
+	kfree(asb);
+	return -EINVAL;
+}
+
+static struct super_block *adfs_get_sb(struct file_system_type *fs_type,
+	int flags, const char *dev_name, void *data)
+{
+	return get_sb_bdev(fs_type, flags, dev_name, data, adfs_fill_super);
+}
+
+static struct file_system_type adfs_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "adfs",
+	.get_sb		= adfs_get_sb,
+	.kill_sb	= kill_block_super,
+	.fs_flags	= FS_REQUIRES_DEV,
+};
+
+static int __init init_adfs_fs(void)
+{
+	int err = init_inodecache();
+	if (err)
+		goto out1;
+	err = register_filesystem(&adfs_fs_type);
+	if (err)
+		goto out;
+	return 0;
+out:
+	destroy_inodecache();
+out1:
+	return err;
+}
+
+static void __exit exit_adfs_fs(void)
+{
+	unregister_filesystem(&adfs_fs_type);
+	destroy_inodecache();
+}
+
+module_init(init_adfs_fs)
+module_exit(exit_adfs_fs)