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/hpfs/Makefile b/fs/hpfs/Makefile
new file mode 100644
index 0000000..57b786f
--- /dev/null
+++ b/fs/hpfs/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for the Linux hpfs filesystem routines.
+#
+
+obj-$(CONFIG_HPFS_FS) += hpfs.o
+
+hpfs-objs := alloc.o anode.o buffer.o dentry.o dir.o dnode.o ea.o file.o \
+	     inode.o map.o name.o namei.o super.o
diff --git a/fs/hpfs/alloc.c b/fs/hpfs/alloc.c
new file mode 100644
index 0000000..5503e2c
--- /dev/null
+++ b/fs/hpfs/alloc.c
@@ -0,0 +1,456 @@
+/*
+ *  linux/fs/hpfs/alloc.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  HPFS bitmap operations
+ */
+
+#include "hpfs_fn.h"
+
+static int hpfs_alloc_if_possible_nolock(struct super_block *s, secno sec);
+
+/*
+ * Check if a sector is allocated in bitmap
+ * This is really slow. Turned on only if chk==2
+ */
+
+static int chk_if_allocated(struct super_block *s, secno sec, char *msg)
+{
+	struct quad_buffer_head qbh;
+	unsigned *bmp;
+	if (!(bmp = hpfs_map_bitmap(s, sec >> 14, &qbh, "chk"))) goto fail;
+	if ((bmp[(sec & 0x3fff) >> 5] >> (sec & 0x1f)) & 1) {
+		hpfs_error(s, "sector '%s' - %08x not allocated in bitmap", msg, sec);
+		goto fail1;
+	}
+	hpfs_brelse4(&qbh);
+	if (sec >= hpfs_sb(s)->sb_dirband_start && sec < hpfs_sb(s)->sb_dirband_start + hpfs_sb(s)->sb_dirband_size) {
+		unsigned ssec = (sec - hpfs_sb(s)->sb_dirband_start) / 4;
+		if (!(bmp = hpfs_map_dnode_bitmap(s, &qbh))) goto fail;
+		if ((bmp[ssec >> 5] >> (ssec & 0x1f)) & 1) {
+			hpfs_error(s, "sector '%s' - %08x not allocated in directory bitmap", msg, sec);
+			goto fail1;
+		}
+		hpfs_brelse4(&qbh);
+	}
+	return 0;
+	fail1:
+	hpfs_brelse4(&qbh);
+	fail:
+	return 1;
+}
+
+/*
+ * Check if sector(s) have proper number and additionally check if they're
+ * allocated in bitmap.
+ */
+	
+int hpfs_chk_sectors(struct super_block *s, secno start, int len, char *msg)
+{
+	if (start + len < start || start < 0x12 ||
+	    start + len > hpfs_sb(s)->sb_fs_size) {
+	    	hpfs_error(s, "sector(s) '%s' badly placed at %08x", msg, start);
+		return 1;
+	}
+	if (hpfs_sb(s)->sb_chk>=2) {
+		int i;
+		for (i = 0; i < len; i++)
+			if (chk_if_allocated(s, start + i, msg)) return 1;
+	}
+	return 0;
+}
+
+static secno alloc_in_bmp(struct super_block *s, secno near, unsigned n, unsigned forward)
+{
+	struct quad_buffer_head qbh;
+	unsigned *bmp;
+	unsigned bs = near & ~0x3fff;
+	unsigned nr = (near & 0x3fff) & ~(n - 1);
+	/*unsigned mnr;*/
+	unsigned i, q;
+	int a, b;
+	secno ret = 0;
+	if (n != 1 && n != 4) {
+		hpfs_error(s, "Bad allocation size: %d", n);
+		return 0;
+	}
+	lock_super(s);
+	if (bs != ~0x3fff) {
+		if (!(bmp = hpfs_map_bitmap(s, near >> 14, &qbh, "aib"))) goto uls;
+	} else {
+		if (!(bmp = hpfs_map_dnode_bitmap(s, &qbh))) goto uls;
+	}
+	if (!tstbits(bmp, nr, n + forward)) {
+		ret = bs + nr;
+		goto rt;
+	}
+	/*if (!tstbits(bmp, nr + n, n + forward)) {
+		ret = bs + nr + n;
+		goto rt;
+	}*/
+	q = nr + n; b = 0;
+	while ((a = tstbits(bmp, q, n + forward)) != 0) {
+		q += a;
+		if (n != 1) q = ((q-1)&~(n-1))+n;
+		if (!b) {
+			if (q>>5 != nr>>5) {
+				b = 1;
+				q = nr & 0x1f;
+			}
+		} else if (q > nr) break;
+	}
+	if (!a) {
+		ret = bs + q;
+		goto rt;
+	}
+	nr >>= 5;
+	/*for (i = nr + 1; i != nr; i++, i &= 0x1ff) {*/
+	i = nr;
+	do {
+		if (!bmp[i]) goto cont;
+		if (n + forward >= 0x3f && bmp[i] != -1) goto cont;
+		q = i<<5;
+		if (i > 0) {
+			unsigned k = bmp[i-1];
+			while (k & 0x80000000) {
+				q--; k <<= 1;
+			}
+		}
+		if (n != 1) q = ((q-1)&~(n-1))+n;
+		while ((a = tstbits(bmp, q, n + forward)) != 0) {
+			q += a;
+			if (n != 1) q = ((q-1)&~(n-1))+n;
+			if (q>>5 > i) break;
+		}
+		if (!a) {
+			ret = bs + q;
+			goto rt;
+		}
+		cont:
+		i++, i &= 0x1ff;
+	} while (i != nr);
+	rt:
+	if (ret) {
+		if (hpfs_sb(s)->sb_chk && ((ret >> 14) != (bs >> 14) || (bmp[(ret & 0x3fff) >> 5] | ~(((1 << n) - 1) << (ret & 0x1f))) != 0xffffffff)) {
+			hpfs_error(s, "Allocation doesn't work! Wanted %d, allocated at %08x", n, ret);
+			ret = 0;
+			goto b;
+		}
+		bmp[(ret & 0x3fff) >> 5] &= ~(((1 << n) - 1) << (ret & 0x1f));
+		hpfs_mark_4buffers_dirty(&qbh);
+	}
+	b:
+	hpfs_brelse4(&qbh);
+	uls:
+	unlock_super(s);
+	return ret;
+}
+
+/*
+ * Allocation strategy:	1) search place near the sector specified
+ *			2) search bitmap where free sectors last found
+ *			3) search all bitmaps
+ *			4) search all bitmaps ignoring number of pre-allocated
+ *				sectors
+ */
+
+secno hpfs_alloc_sector(struct super_block *s, secno near, unsigned n, int forward, int lock)
+{
+	secno sec;
+	int i;
+	unsigned n_bmps;
+	struct hpfs_sb_info *sbi = hpfs_sb(s);
+	int f_p = 0;
+	int near_bmp;
+	if (forward < 0) {
+		forward = -forward;
+		f_p = 1;
+	}
+	if (lock) hpfs_lock_creation(s);
+	n_bmps = (sbi->sb_fs_size + 0x4000 - 1) >> 14;
+	if (near && near < sbi->sb_fs_size) {
+		if ((sec = alloc_in_bmp(s, near, n, f_p ? forward : forward/4))) goto ret;
+		near_bmp = near >> 14;
+	} else near_bmp = n_bmps / 2;
+	/*
+	if (b != -1) {
+		if ((sec = alloc_in_bmp(s, b<<14, n, f_p ? forward : forward/2))) {
+			b &= 0x0fffffff;
+			goto ret;
+		}
+		if (b > 0x10000000) if ((sec = alloc_in_bmp(s, (b&0xfffffff)<<14, n, f_p ? forward : 0))) goto ret;
+	*/
+	if (!f_p) if (forward > sbi->sb_max_fwd_alloc) forward = sbi->sb_max_fwd_alloc;
+	less_fwd:
+	for (i = 0; i < n_bmps; i++) {
+		if (near_bmp+i < n_bmps && ((sec = alloc_in_bmp(s, (near_bmp+i) << 14, n, forward)))) {
+			sbi->sb_c_bitmap = near_bmp+i;
+			goto ret;
+		}	
+		if (!forward) {
+			if (near_bmp-i-1 >= 0 && ((sec = alloc_in_bmp(s, (near_bmp-i-1) << 14, n, forward)))) {
+				sbi->sb_c_bitmap = near_bmp-i-1;
+				goto ret;
+			}
+		} else {
+			if (near_bmp+i >= n_bmps && ((sec = alloc_in_bmp(s, (near_bmp+i-n_bmps) << 14, n, forward)))) {
+				sbi->sb_c_bitmap = near_bmp+i-n_bmps;
+				goto ret;
+			}
+		}
+		if (i == 1 && sbi->sb_c_bitmap != -1 && ((sec = alloc_in_bmp(s, (sbi->sb_c_bitmap) << 14, n, forward)))) {
+			goto ret;
+		}
+	}
+	if (!f_p) {
+		if (forward) {
+			sbi->sb_max_fwd_alloc = forward * 3 / 4;
+			forward /= 2;
+			goto less_fwd;
+		}
+	}
+	sec = 0;
+	ret:
+	if (sec && f_p) {
+		for (i = 0; i < forward; i++) {
+			if (!hpfs_alloc_if_possible_nolock(s, sec + i + 1)) {
+				hpfs_error(s, "Prealloc doesn't work! Wanted %d, allocated at %08x, can't allocate %d", forward, sec, i);
+				sec = 0;
+				break;
+			}
+		}
+	}
+	if (lock) hpfs_unlock_creation(s);
+	return sec;
+}
+
+static secno alloc_in_dirband(struct super_block *s, secno near, int lock)
+{
+	unsigned nr = near;
+	secno sec;
+	struct hpfs_sb_info *sbi = hpfs_sb(s);
+	if (nr < sbi->sb_dirband_start)
+		nr = sbi->sb_dirband_start;
+	if (nr >= sbi->sb_dirband_start + sbi->sb_dirband_size)
+		nr = sbi->sb_dirband_start + sbi->sb_dirband_size - 4;
+	nr -= sbi->sb_dirband_start;
+	nr >>= 2;
+	if (lock) hpfs_lock_creation(s);
+	sec = alloc_in_bmp(s, (~0x3fff) | nr, 1, 0);
+	if (lock) hpfs_unlock_creation(s);
+	if (!sec) return 0;
+	return ((sec & 0x3fff) << 2) + sbi->sb_dirband_start;
+}
+
+/* Alloc sector if it's free */
+
+static int hpfs_alloc_if_possible_nolock(struct super_block *s, secno sec)
+{
+	struct quad_buffer_head qbh;
+	unsigned *bmp;
+	lock_super(s);
+	if (!(bmp = hpfs_map_bitmap(s, sec >> 14, &qbh, "aip"))) goto end;
+	if (bmp[(sec & 0x3fff) >> 5] & (1 << (sec & 0x1f))) {
+		bmp[(sec & 0x3fff) >> 5] &= ~(1 << (sec & 0x1f));
+		hpfs_mark_4buffers_dirty(&qbh);
+		hpfs_brelse4(&qbh);
+		unlock_super(s);
+		return 1;
+	}
+	hpfs_brelse4(&qbh);
+	end:
+	unlock_super(s);
+	return 0;
+}
+
+int hpfs_alloc_if_possible(struct super_block *s, secno sec)
+{
+	int r;
+	hpfs_lock_creation(s);
+	r = hpfs_alloc_if_possible_nolock(s, sec);
+	hpfs_unlock_creation(s);
+	return r;
+}
+
+/* Free sectors in bitmaps */
+
+void hpfs_free_sectors(struct super_block *s, secno sec, unsigned n)
+{
+	struct quad_buffer_head qbh;
+	unsigned *bmp;
+	struct hpfs_sb_info *sbi = hpfs_sb(s);
+	/*printk("2 - ");*/
+	if (!n) return;
+	if (sec < 0x12) {
+		hpfs_error(s, "Trying to free reserved sector %08x", sec);
+		return;
+	}
+	lock_super(s);
+	sbi->sb_max_fwd_alloc += n > 0xffff ? 0xffff : n;
+	if (sbi->sb_max_fwd_alloc > 0xffffff) sbi->sb_max_fwd_alloc = 0xffffff;
+	new_map:
+	if (!(bmp = hpfs_map_bitmap(s, sec >> 14, &qbh, "free"))) {
+		unlock_super(s);
+		return;
+	}	
+	new_tst:
+	if ((bmp[(sec & 0x3fff) >> 5] >> (sec & 0x1f) & 1)) {
+		hpfs_error(s, "sector %08x not allocated", sec);
+		hpfs_brelse4(&qbh);
+		unlock_super(s);
+		return;
+	}
+	bmp[(sec & 0x3fff) >> 5] |= 1 << (sec & 0x1f);
+	if (!--n) {
+		hpfs_mark_4buffers_dirty(&qbh);
+		hpfs_brelse4(&qbh);
+		unlock_super(s);
+		return;
+	}	
+	if (!(++sec & 0x3fff)) {
+		hpfs_mark_4buffers_dirty(&qbh);
+		hpfs_brelse4(&qbh);
+		goto new_map;
+	}
+	goto new_tst;
+}
+
+/*
+ * Check if there are at least n free dnodes on the filesystem.
+ * Called before adding to dnode. If we run out of space while
+ * splitting dnodes, it would corrupt dnode tree.
+ */
+
+int hpfs_check_free_dnodes(struct super_block *s, int n)
+{
+	int n_bmps = (hpfs_sb(s)->sb_fs_size + 0x4000 - 1) >> 14;
+	int b = hpfs_sb(s)->sb_c_bitmap & 0x0fffffff;
+	int i, j;
+	unsigned *bmp;
+	struct quad_buffer_head qbh;
+	if ((bmp = hpfs_map_dnode_bitmap(s, &qbh))) {
+		for (j = 0; j < 512; j++) {
+			unsigned k;
+			if (!bmp[j]) continue;
+			for (k = bmp[j]; k; k >>= 1) if (k & 1) if (!--n) {
+				hpfs_brelse4(&qbh);
+				return 0;
+			}
+		}
+	}
+	hpfs_brelse4(&qbh);
+	i = 0;
+	if (hpfs_sb(s)->sb_c_bitmap != -1) {
+		bmp = hpfs_map_bitmap(s, b, &qbh, "chkdn1");
+		goto chk_bmp;
+	}
+	chk_next:
+	if (i == b) i++;
+	if (i >= n_bmps) return 1;
+	bmp = hpfs_map_bitmap(s, i, &qbh, "chkdn2");
+	chk_bmp:
+	if (bmp) {
+		for (j = 0; j < 512; j++) {
+			unsigned k;
+			if (!bmp[j]) continue;
+			for (k = 0xf; k; k <<= 4)
+				if ((bmp[j] & k) == k) {
+					if (!--n) {
+						hpfs_brelse4(&qbh);
+						return 0;
+					}
+				}
+		}
+		hpfs_brelse4(&qbh);
+	}
+	i++;
+	goto chk_next;
+}
+
+void hpfs_free_dnode(struct super_block *s, dnode_secno dno)
+{
+	if (hpfs_sb(s)->sb_chk) if (dno & 3) {
+		hpfs_error(s, "hpfs_free_dnode: dnode %08x not aligned", dno);
+		return;
+	}
+	if (dno < hpfs_sb(s)->sb_dirband_start ||
+	    dno >= hpfs_sb(s)->sb_dirband_start + hpfs_sb(s)->sb_dirband_size) {
+		hpfs_free_sectors(s, dno, 4);
+	} else {
+		struct quad_buffer_head qbh;
+		unsigned *bmp;
+		unsigned ssec = (dno - hpfs_sb(s)->sb_dirband_start) / 4;
+		lock_super(s);
+		if (!(bmp = hpfs_map_dnode_bitmap(s, &qbh))) {
+			unlock_super(s);
+			return;
+		}
+		bmp[ssec >> 5] |= 1 << (ssec & 0x1f);
+		hpfs_mark_4buffers_dirty(&qbh);
+		hpfs_brelse4(&qbh);
+		unlock_super(s);
+	}
+}
+
+struct dnode *hpfs_alloc_dnode(struct super_block *s, secno near,
+			 dnode_secno *dno, struct quad_buffer_head *qbh,
+			 int lock)
+{
+	struct dnode *d;
+	if (hpfs_count_one_bitmap(s, hpfs_sb(s)->sb_dmap) > FREE_DNODES_ADD) {
+		if (!(*dno = alloc_in_dirband(s, near, lock)))
+			if (!(*dno = hpfs_alloc_sector(s, near, 4, 0, lock))) return NULL;
+	} else {
+		if (!(*dno = hpfs_alloc_sector(s, near, 4, 0, lock)))
+			if (!(*dno = alloc_in_dirband(s, near, lock))) return NULL;
+	}
+	if (!(d = hpfs_get_4sectors(s, *dno, qbh))) {
+		hpfs_free_dnode(s, *dno);
+		return NULL;
+	}
+	memset(d, 0, 2048);
+	d->magic = DNODE_MAGIC;
+	d->first_free = 52;
+	d->dirent[0] = 32;
+	d->dirent[2] = 8;
+	d->dirent[30] = 1;
+	d->dirent[31] = 255;
+	d->self = *dno;
+	return d;
+}
+
+struct fnode *hpfs_alloc_fnode(struct super_block *s, secno near, fnode_secno *fno,
+			  struct buffer_head **bh)
+{
+	struct fnode *f;
+	if (!(*fno = hpfs_alloc_sector(s, near, 1, FNODE_ALLOC_FWD, 1))) return NULL;
+	if (!(f = hpfs_get_sector(s, *fno, bh))) {
+		hpfs_free_sectors(s, *fno, 1);
+		return NULL;
+	}	
+	memset(f, 0, 512);
+	f->magic = FNODE_MAGIC;
+	f->ea_offs = 0xc4;
+	f->btree.n_free_nodes = 8;
+	f->btree.first_free = 8;
+	return f;
+}
+
+struct anode *hpfs_alloc_anode(struct super_block *s, secno near, anode_secno *ano,
+			  struct buffer_head **bh)
+{
+	struct anode *a;
+	if (!(*ano = hpfs_alloc_sector(s, near, 1, ANODE_ALLOC_FWD, 1))) return NULL;
+	if (!(a = hpfs_get_sector(s, *ano, bh))) {
+		hpfs_free_sectors(s, *ano, 1);
+		return NULL;
+	}
+	memset(a, 0, 512);
+	a->magic = ANODE_MAGIC;
+	a->self = *ano;
+	a->btree.n_free_nodes = 40;
+	a->btree.n_used_nodes = 0;
+	a->btree.first_free = 8;
+	return a;
+}
diff --git a/fs/hpfs/anode.c b/fs/hpfs/anode.c
new file mode 100644
index 0000000..1aa88c4
--- /dev/null
+++ b/fs/hpfs/anode.c
@@ -0,0 +1,491 @@
+/*
+ *  linux/fs/hpfs/anode.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  handling HPFS anode tree that contains file allocation info
+ */
+
+#include "hpfs_fn.h"
+
+/* Find a sector in allocation tree */
+
+secno hpfs_bplus_lookup(struct super_block *s, struct inode *inode,
+		   struct bplus_header *btree, unsigned sec,
+		   struct buffer_head *bh)
+{
+	anode_secno a = -1;
+	struct anode *anode;
+	int i;
+	int c1, c2 = 0;
+	go_down:
+	if (hpfs_sb(s)->sb_chk) if (hpfs_stop_cycles(s, a, &c1, &c2, "hpfs_bplus_lookup")) return -1;
+	if (btree->internal) {
+		for (i = 0; i < btree->n_used_nodes; i++)
+			if (btree->u.internal[i].file_secno > sec) {
+				a = btree->u.internal[i].down;
+				brelse(bh);
+				if (!(anode = hpfs_map_anode(s, a, &bh))) return -1;
+				btree = &anode->btree;
+				goto go_down;
+			}
+		hpfs_error(s, "sector %08x not found in internal anode %08x", sec, a);
+		brelse(bh);
+		return -1;
+	}
+	for (i = 0; i < btree->n_used_nodes; i++)
+		if (btree->u.external[i].file_secno <= sec &&
+		    btree->u.external[i].file_secno + btree->u.external[i].length > sec) {
+			a = btree->u.external[i].disk_secno + sec - btree->u.external[i].file_secno;
+			if (hpfs_sb(s)->sb_chk) if (hpfs_chk_sectors(s, a, 1, "data")) {
+				brelse(bh);
+				return -1;
+			}
+			if (inode) {
+				struct hpfs_inode_info *hpfs_inode = hpfs_i(inode);
+				hpfs_inode->i_file_sec = btree->u.external[i].file_secno;
+				hpfs_inode->i_disk_sec = btree->u.external[i].disk_secno;
+				hpfs_inode->i_n_secs = btree->u.external[i].length;
+			}
+			brelse(bh);
+			return a;
+		}
+	hpfs_error(s, "sector %08x not found in external anode %08x", sec, a);
+	brelse(bh);
+	return -1;
+}
+
+/* Add a sector to tree */
+
+secno hpfs_add_sector_to_btree(struct super_block *s, secno node, int fnod, unsigned fsecno)
+{
+	struct bplus_header *btree;
+	struct anode *anode = NULL, *ranode = NULL;
+	struct fnode *fnode;
+	anode_secno a, na = -1, ra, up = -1;
+	secno se;
+	struct buffer_head *bh, *bh1, *bh2;
+	int n;
+	unsigned fs;
+	int c1, c2 = 0;
+	if (fnod) {
+		if (!(fnode = hpfs_map_fnode(s, node, &bh))) return -1;
+		btree = &fnode->btree;
+	} else {
+		if (!(anode = hpfs_map_anode(s, node, &bh))) return -1;
+		btree = &anode->btree;
+	}
+	a = node;
+	go_down:
+	if ((n = btree->n_used_nodes - 1) < -!!fnod) {
+		hpfs_error(s, "anode %08x has no entries", a);
+		brelse(bh);
+		return -1;
+	}
+	if (btree->internal) {
+		a = btree->u.internal[n].down;
+		btree->u.internal[n].file_secno = -1;
+		mark_buffer_dirty(bh);
+		brelse(bh);
+		if (hpfs_sb(s)->sb_chk)
+			if (hpfs_stop_cycles(s, a, &c1, &c2, "hpfs_add_sector_to_btree #1")) return -1;
+		if (!(anode = hpfs_map_anode(s, a, &bh))) return -1;
+		btree = &anode->btree;
+		goto go_down;
+	}
+	if (n >= 0) {
+		if (btree->u.external[n].file_secno + btree->u.external[n].length != fsecno) {
+			hpfs_error(s, "allocated size %08x, trying to add sector %08x, %cnode %08x",
+				btree->u.external[n].file_secno + btree->u.external[n].length, fsecno,
+				fnod?'f':'a', node);
+			brelse(bh);
+			return -1;
+		}
+		if (hpfs_alloc_if_possible(s, se = btree->u.external[n].disk_secno + btree->u.external[n].length)) {
+			btree->u.external[n].length++;
+			mark_buffer_dirty(bh);
+			brelse(bh);
+			return se;
+		}
+	} else {
+		if (fsecno) {
+			hpfs_error(s, "empty file %08x, trying to add sector %08x", node, fsecno);
+			brelse(bh);
+			return -1;
+		}
+		se = !fnod ? node : (node + 16384) & ~16383;
+	}	
+	if (!(se = hpfs_alloc_sector(s, se, 1, fsecno*ALLOC_M>ALLOC_FWD_MAX ? ALLOC_FWD_MAX : fsecno*ALLOC_M<ALLOC_FWD_MIN ? ALLOC_FWD_MIN : fsecno*ALLOC_M, 1))) {
+		brelse(bh);
+		return -1;
+	}
+	fs = n < 0 ? 0 : btree->u.external[n].file_secno + btree->u.external[n].length;
+	if (!btree->n_free_nodes) {
+		up = a != node ? anode->up : -1;
+		if (!(anode = hpfs_alloc_anode(s, a, &na, &bh1))) {
+			brelse(bh);
+			hpfs_free_sectors(s, se, 1);
+			return -1;
+		}
+		if (a == node && fnod) {
+			anode->up = node;
+			anode->btree.fnode_parent = 1;
+			anode->btree.n_used_nodes = btree->n_used_nodes;
+			anode->btree.first_free = btree->first_free;
+			anode->btree.n_free_nodes = 40 - anode->btree.n_used_nodes;
+			memcpy(&anode->u, &btree->u, btree->n_used_nodes * 12);
+			btree->internal = 1;
+			btree->n_free_nodes = 11;
+			btree->n_used_nodes = 1;
+			btree->first_free = (char *)&(btree->u.internal[1]) - (char *)btree;
+			btree->u.internal[0].file_secno = -1;
+			btree->u.internal[0].down = na;
+			mark_buffer_dirty(bh);
+		} else if (!(ranode = hpfs_alloc_anode(s, /*a*/0, &ra, &bh2))) {
+			brelse(bh);
+			brelse(bh1);
+			hpfs_free_sectors(s, se, 1);
+			hpfs_free_sectors(s, na, 1);
+			return -1;
+		}
+		brelse(bh);
+		bh = bh1;
+		btree = &anode->btree;
+	}
+	btree->n_free_nodes--; n = btree->n_used_nodes++;
+	btree->first_free += 12;
+	btree->u.external[n].disk_secno = se;
+	btree->u.external[n].file_secno = fs;
+	btree->u.external[n].length = 1;
+	mark_buffer_dirty(bh);
+	brelse(bh);
+	if ((a == node && fnod) || na == -1) return se;
+	c2 = 0;
+	while (up != -1) {
+		struct anode *new_anode;
+		if (hpfs_sb(s)->sb_chk)
+			if (hpfs_stop_cycles(s, up, &c1, &c2, "hpfs_add_sector_to_btree #2")) return -1;
+		if (up != node || !fnod) {
+			if (!(anode = hpfs_map_anode(s, up, &bh))) return -1;
+			btree = &anode->btree;
+		} else {
+			if (!(fnode = hpfs_map_fnode(s, up, &bh))) return -1;
+			btree = &fnode->btree;
+		}
+		if (btree->n_free_nodes) {
+			btree->n_free_nodes--; n = btree->n_used_nodes++;
+			btree->first_free += 8;
+			btree->u.internal[n].file_secno = -1;
+			btree->u.internal[n].down = na;
+			btree->u.internal[n-1].file_secno = fs;
+			mark_buffer_dirty(bh);
+			brelse(bh);
+			brelse(bh2);
+			hpfs_free_sectors(s, ra, 1);
+			if ((anode = hpfs_map_anode(s, na, &bh))) {
+				anode->up = up;
+				anode->btree.fnode_parent = up == node && fnod;
+				mark_buffer_dirty(bh);
+				brelse(bh);
+			}
+			return se;
+		}
+		up = up != node ? anode->up : -1;
+		btree->u.internal[btree->n_used_nodes - 1].file_secno = /*fs*/-1;
+		mark_buffer_dirty(bh);
+		brelse(bh);
+		a = na;
+		if ((new_anode = hpfs_alloc_anode(s, a, &na, &bh))) {
+			anode = new_anode;
+			/*anode->up = up != -1 ? up : ra;*/
+			anode->btree.internal = 1;
+			anode->btree.n_used_nodes = 1;
+			anode->btree.n_free_nodes = 59;
+			anode->btree.first_free = 16;
+			anode->btree.u.internal[0].down = a;
+			anode->btree.u.internal[0].file_secno = -1;
+			mark_buffer_dirty(bh);
+			brelse(bh);
+			if ((anode = hpfs_map_anode(s, a, &bh))) {
+				anode->up = na;
+				mark_buffer_dirty(bh);
+				brelse(bh);
+			}
+		} else na = a;
+	}
+	if ((anode = hpfs_map_anode(s, na, &bh))) {
+		anode->up = node;
+		if (fnod) anode->btree.fnode_parent = 1;
+		mark_buffer_dirty(bh);
+		brelse(bh);
+	}
+	if (!fnod) {
+		if (!(anode = hpfs_map_anode(s, node, &bh))) {
+			brelse(bh2);
+			return -1;
+		}
+		btree = &anode->btree;
+	} else {
+		if (!(fnode = hpfs_map_fnode(s, node, &bh))) {
+			brelse(bh2);
+			return -1;
+		}
+		btree = &fnode->btree;
+	}
+	ranode->up = node;
+	memcpy(&ranode->btree, btree, btree->first_free);
+	if (fnod) ranode->btree.fnode_parent = 1;
+	ranode->btree.n_free_nodes = (ranode->btree.internal ? 60 : 40) - ranode->btree.n_used_nodes;
+	if (ranode->btree.internal) for (n = 0; n < ranode->btree.n_used_nodes; n++) {
+		struct anode *unode;
+		if ((unode = hpfs_map_anode(s, ranode->u.internal[n].down, &bh1))) {
+			unode->up = ra;
+			unode->btree.fnode_parent = 0;
+			mark_buffer_dirty(bh1);
+			brelse(bh1);
+		}
+	}
+	btree->internal = 1;
+	btree->n_free_nodes = fnod ? 10 : 58;
+	btree->n_used_nodes = 2;
+	btree->first_free = (char *)&btree->u.internal[2] - (char *)btree;
+	btree->u.internal[0].file_secno = fs;
+	btree->u.internal[0].down = ra;
+	btree->u.internal[1].file_secno = -1;
+	btree->u.internal[1].down = na;
+	mark_buffer_dirty(bh);
+	brelse(bh);
+	mark_buffer_dirty(bh2);
+	brelse(bh2);
+	return se;
+}
+
+/*
+ * Remove allocation tree. Recursion would look much nicer but
+ * I want to avoid it because it can cause stack overflow.
+ */
+
+void hpfs_remove_btree(struct super_block *s, struct bplus_header *btree)
+{
+	struct bplus_header *btree1 = btree;
+	struct anode *anode = NULL;
+	anode_secno ano = 0, oano;
+	struct buffer_head *bh;
+	int level = 0;
+	int pos = 0;
+	int i;
+	int c1, c2 = 0;
+	int d1, d2;
+	go_down:
+	d2 = 0;
+	while (btree1->internal) {
+		ano = btree1->u.internal[pos].down;
+		if (level) brelse(bh);
+		if (hpfs_sb(s)->sb_chk)
+			if (hpfs_stop_cycles(s, ano, &d1, &d2, "hpfs_remove_btree #1"))
+				return;
+		if (!(anode = hpfs_map_anode(s, ano, &bh))) return;
+		btree1 = &anode->btree;
+		level++;
+		pos = 0;
+	}
+	for (i = 0; i < btree1->n_used_nodes; i++)
+		hpfs_free_sectors(s, btree1->u.external[i].disk_secno, btree1->u.external[i].length);
+	go_up:
+	if (!level) return;
+	brelse(bh);
+	if (hpfs_sb(s)->sb_chk)
+		if (hpfs_stop_cycles(s, ano, &c1, &c2, "hpfs_remove_btree #2")) return;
+	hpfs_free_sectors(s, ano, 1);
+	oano = ano;
+	ano = anode->up;
+	if (--level) {
+		if (!(anode = hpfs_map_anode(s, ano, &bh))) return;
+		btree1 = &anode->btree;
+	} else btree1 = btree;
+	for (i = 0; i < btree1->n_used_nodes; i++) {
+		if (btree1->u.internal[i].down == oano) {
+			if ((pos = i + 1) < btree1->n_used_nodes)
+				goto go_down;
+			else
+				goto go_up;
+		}
+	}
+	hpfs_error(s,
+		   "reference to anode %08x not found in anode %08x "
+		   "(probably bad up pointer)",
+		   oano, level ? ano : -1);
+	if (level)
+		brelse(bh);
+}
+
+/* Just a wrapper around hpfs_bplus_lookup .. used for reading eas */
+
+static secno anode_lookup(struct super_block *s, anode_secno a, unsigned sec)
+{
+	struct anode *anode;
+	struct buffer_head *bh;
+	if (!(anode = hpfs_map_anode(s, a, &bh))) return -1;
+	return hpfs_bplus_lookup(s, NULL, &anode->btree, sec, bh);
+}
+
+int hpfs_ea_read(struct super_block *s, secno a, int ano, unsigned pos,
+	    unsigned len, char *buf)
+{
+	struct buffer_head *bh;
+	char *data;
+	secno sec;
+	unsigned l;
+	while (len) {
+		if (ano) {
+			if ((sec = anode_lookup(s, a, pos >> 9)) == -1)
+				return -1;
+		} else sec = a + (pos >> 9);
+		if (hpfs_sb(s)->sb_chk) if (hpfs_chk_sectors(s, sec, 1, "ea #1")) return -1;
+		if (!(data = hpfs_map_sector(s, sec, &bh, (len - 1) >> 9)))
+			return -1;
+		l = 0x200 - (pos & 0x1ff); if (l > len) l = len;
+		memcpy(buf, data + (pos & 0x1ff), l);
+		brelse(bh);
+		buf += l; pos += l; len -= l;
+	}
+	return 0;
+}
+
+int hpfs_ea_write(struct super_block *s, secno a, int ano, unsigned pos,
+	     unsigned len, char *buf)
+{
+	struct buffer_head *bh;
+	char *data;
+	secno sec;
+	unsigned l;
+	while (len) {
+		if (ano) {
+			if ((sec = anode_lookup(s, a, pos >> 9)) == -1)
+				return -1;
+		} else sec = a + (pos >> 9);
+		if (hpfs_sb(s)->sb_chk) if (hpfs_chk_sectors(s, sec, 1, "ea #2")) return -1;
+		if (!(data = hpfs_map_sector(s, sec, &bh, (len - 1) >> 9)))
+			return -1;
+		l = 0x200 - (pos & 0x1ff); if (l > len) l = len;
+		memcpy(data + (pos & 0x1ff), buf, l);
+		mark_buffer_dirty(bh);
+		brelse(bh);
+		buf += l; pos += l; len -= l;
+	}
+	return 0;
+}
+
+void hpfs_ea_remove(struct super_block *s, secno a, int ano, unsigned len)
+{
+	struct anode *anode;
+	struct buffer_head *bh;
+	if (ano) {
+		if (!(anode = hpfs_map_anode(s, a, &bh))) return;
+		hpfs_remove_btree(s, &anode->btree);
+		brelse(bh);
+		hpfs_free_sectors(s, a, 1);
+	} else hpfs_free_sectors(s, a, (len + 511) >> 9);
+}
+
+/* Truncate allocation tree. Doesn't join anodes - I hope it doesn't matter */
+
+void hpfs_truncate_btree(struct super_block *s, secno f, int fno, unsigned secs)
+{
+	struct fnode *fnode;
+	struct anode *anode;
+	struct buffer_head *bh;
+	struct bplus_header *btree;
+	anode_secno node = f;
+	int i, j, nodes;
+	int c1, c2 = 0;
+	if (fno) {
+		if (!(fnode = hpfs_map_fnode(s, f, &bh))) return;
+		btree = &fnode->btree;
+	} else {
+		if (!(anode = hpfs_map_anode(s, f, &bh))) return;
+		btree = &anode->btree;
+	}
+	if (!secs) {
+		hpfs_remove_btree(s, btree);
+		if (fno) {
+			btree->n_free_nodes = 8;
+			btree->n_used_nodes = 0;
+			btree->first_free = 8;
+			btree->internal = 0;
+			mark_buffer_dirty(bh);
+		} else hpfs_free_sectors(s, f, 1);
+		brelse(bh);
+		return;
+	}
+	while (btree->internal) {
+		nodes = btree->n_used_nodes + btree->n_free_nodes;
+		for (i = 0; i < btree->n_used_nodes; i++)
+			if (btree->u.internal[i].file_secno >= secs) goto f;
+		brelse(bh);
+		hpfs_error(s, "internal btree %08x doesn't end with -1", node);
+		return;
+		f:
+		for (j = i + 1; j < btree->n_used_nodes; j++)
+			hpfs_ea_remove(s, btree->u.internal[j].down, 1, 0);
+		btree->n_used_nodes = i + 1;
+		btree->n_free_nodes = nodes - btree->n_used_nodes;
+		btree->first_free = 8 + 8 * btree->n_used_nodes;
+		mark_buffer_dirty(bh);
+		if (btree->u.internal[i].file_secno == secs) {
+			brelse(bh);
+			return;
+		}
+		node = btree->u.internal[i].down;
+		brelse(bh);
+		if (hpfs_sb(s)->sb_chk)
+			if (hpfs_stop_cycles(s, node, &c1, &c2, "hpfs_truncate_btree"))
+				return;
+		if (!(anode = hpfs_map_anode(s, node, &bh))) return;
+		btree = &anode->btree;
+	}	
+	nodes = btree->n_used_nodes + btree->n_free_nodes;
+	for (i = 0; i < btree->n_used_nodes; i++)
+		if (btree->u.external[i].file_secno + btree->u.external[i].length >= secs) goto ff;
+	brelse(bh);
+	return;
+	ff:
+	if (secs <= btree->u.external[i].file_secno) {
+		hpfs_error(s, "there is an allocation error in file %08x, sector %08x", f, secs);
+		if (i) i--;
+	}
+	else if (btree->u.external[i].file_secno + btree->u.external[i].length > secs) {
+		hpfs_free_sectors(s, btree->u.external[i].disk_secno + secs -
+			btree->u.external[i].file_secno, btree->u.external[i].length
+			- secs + btree->u.external[i].file_secno); /* I hope gcc optimizes this :-) */
+		btree->u.external[i].length = secs - btree->u.external[i].file_secno;
+	}
+	for (j = i + 1; j < btree->n_used_nodes; j++)
+		hpfs_free_sectors(s, btree->u.external[j].disk_secno, btree->u.external[j].length);
+	btree->n_used_nodes = i + 1;
+	btree->n_free_nodes = nodes - btree->n_used_nodes;
+	btree->first_free = 8 + 12 * btree->n_used_nodes;
+	mark_buffer_dirty(bh);
+	brelse(bh);
+}
+
+/* Remove file or directory and it's eas - note that directory must
+   be empty when this is called. */
+
+void hpfs_remove_fnode(struct super_block *s, fnode_secno fno)
+{
+	struct buffer_head *bh;
+	struct fnode *fnode;
+	struct extended_attribute *ea;
+	struct extended_attribute *ea_end;
+	if (!(fnode = hpfs_map_fnode(s, fno, &bh))) return;
+	if (!fnode->dirflag) hpfs_remove_btree(s, &fnode->btree);
+	else hpfs_remove_dtree(s, fnode->u.external[0].disk_secno);
+	ea_end = fnode_end_ea(fnode);
+	for (ea = fnode_ea(fnode); ea < ea_end; ea = next_ea(ea))
+		if (ea->indirect)
+			hpfs_ea_remove(s, ea_sec(ea), ea->anode, ea_len(ea));
+	hpfs_ea_ext_remove(s, fnode->ea_secno, fnode->ea_anode, fnode->ea_size_l);
+	brelse(bh);
+	hpfs_free_sectors(s, fno, 1);
+}
diff --git a/fs/hpfs/buffer.c b/fs/hpfs/buffer.c
new file mode 100644
index 0000000..2807aa8
--- /dev/null
+++ b/fs/hpfs/buffer.c
@@ -0,0 +1,175 @@
+/*
+ *  linux/fs/hpfs/buffer.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  general buffer i/o
+ */
+
+#include "hpfs_fn.h"
+
+void hpfs_lock_creation(struct super_block *s)
+{
+#ifdef DEBUG_LOCKS
+	printk("lock creation\n");
+#endif
+	down(&hpfs_sb(s)->hpfs_creation_de);
+}
+
+void hpfs_unlock_creation(struct super_block *s)
+{
+#ifdef DEBUG_LOCKS
+	printk("unlock creation\n");
+#endif
+	up(&hpfs_sb(s)->hpfs_creation_de);
+}
+
+/* Map a sector into a buffer and return pointers to it and to the buffer. */
+
+void *hpfs_map_sector(struct super_block *s, unsigned secno, struct buffer_head **bhp,
+		 int ahead)
+{
+	struct buffer_head *bh;
+
+	cond_resched();
+
+	*bhp = bh = sb_bread(s, secno);
+	if (bh != NULL)
+		return bh->b_data;
+	else {
+		printk("HPFS: hpfs_map_sector: read error\n");
+		return NULL;
+	}
+}
+
+/* Like hpfs_map_sector but don't read anything */
+
+void *hpfs_get_sector(struct super_block *s, unsigned secno, struct buffer_head **bhp)
+{
+	struct buffer_head *bh;
+	/*return hpfs_map_sector(s, secno, bhp, 0);*/
+
+	cond_resched();
+
+	if ((*bhp = bh = sb_getblk(s, secno)) != NULL) {
+		if (!buffer_uptodate(bh)) wait_on_buffer(bh);
+		set_buffer_uptodate(bh);
+		return bh->b_data;
+	} else {
+		printk("HPFS: hpfs_get_sector: getblk failed\n");
+		return NULL;
+	}
+}
+
+/* Map 4 sectors into a 4buffer and return pointers to it and to the buffer. */
+
+void *hpfs_map_4sectors(struct super_block *s, unsigned secno, struct quad_buffer_head *qbh,
+		   int ahead)
+{
+	struct buffer_head *bh;
+	char *data;
+
+	cond_resched();
+
+	if (secno & 3) {
+		printk("HPFS: hpfs_map_4sectors: unaligned read\n");
+		return NULL;
+	}
+
+	qbh->data = data = (char *)kmalloc(2048, GFP_NOFS);
+	if (!data) {
+		printk("HPFS: hpfs_map_4sectors: out of memory\n");
+		goto bail;
+	}
+
+	qbh->bh[0] = bh = sb_bread(s, secno);
+	if (!bh)
+		goto bail0;
+	memcpy(data, bh->b_data, 512);
+
+	qbh->bh[1] = bh = sb_bread(s, secno + 1);
+	if (!bh)
+		goto bail1;
+	memcpy(data + 512, bh->b_data, 512);
+
+	qbh->bh[2] = bh = sb_bread(s, secno + 2);
+	if (!bh)
+		goto bail2;
+	memcpy(data + 2 * 512, bh->b_data, 512);
+
+	qbh->bh[3] = bh = sb_bread(s, secno + 3);
+	if (!bh)
+		goto bail3;
+	memcpy(data + 3 * 512, bh->b_data, 512);
+
+	return data;
+
+ bail3:
+	brelse(qbh->bh[2]);
+ bail2:
+	brelse(qbh->bh[1]);
+ bail1:
+	brelse(qbh->bh[0]);
+ bail0:
+	kfree(data);
+	printk("HPFS: hpfs_map_4sectors: read error\n");
+ bail:
+	return NULL;
+}
+
+/* Don't read sectors */
+
+void *hpfs_get_4sectors(struct super_block *s, unsigned secno,
+                          struct quad_buffer_head *qbh)
+{
+	cond_resched();
+
+	if (secno & 3) {
+		printk("HPFS: hpfs_get_4sectors: unaligned read\n");
+		return NULL;
+	}
+
+	/*return hpfs_map_4sectors(s, secno, qbh, 0);*/
+	if (!(qbh->data = kmalloc(2048, GFP_NOFS))) {
+		printk("HPFS: hpfs_get_4sectors: out of memory\n");
+		return NULL;
+	}
+	if (!(hpfs_get_sector(s, secno, &qbh->bh[0]))) goto bail0;
+	if (!(hpfs_get_sector(s, secno + 1, &qbh->bh[1]))) goto bail1;
+	if (!(hpfs_get_sector(s, secno + 2, &qbh->bh[2]))) goto bail2;
+	if (!(hpfs_get_sector(s, secno + 3, &qbh->bh[3]))) goto bail3;
+	memcpy(qbh->data, qbh->bh[0]->b_data, 512);
+	memcpy(qbh->data + 512, qbh->bh[1]->b_data, 512);
+	memcpy(qbh->data + 2*512, qbh->bh[2]->b_data, 512);
+	memcpy(qbh->data + 3*512, qbh->bh[3]->b_data, 512);
+	return qbh->data;
+
+	bail3:	brelse(qbh->bh[2]);
+	bail2:	brelse(qbh->bh[1]);
+	bail1:	brelse(qbh->bh[0]);
+	bail0:
+	return NULL;
+}
+	
+
+void hpfs_brelse4(struct quad_buffer_head *qbh)
+{
+	brelse(qbh->bh[3]);
+	brelse(qbh->bh[2]);
+	brelse(qbh->bh[1]);
+	brelse(qbh->bh[0]);
+	kfree(qbh->data);
+}	
+
+void hpfs_mark_4buffers_dirty(struct quad_buffer_head *qbh)
+{
+	PRINTK(("hpfs_mark_4buffers_dirty\n"));
+	memcpy(qbh->bh[0]->b_data, qbh->data, 512);
+	memcpy(qbh->bh[1]->b_data, qbh->data + 512, 512);
+	memcpy(qbh->bh[2]->b_data, qbh->data + 2 * 512, 512);
+	memcpy(qbh->bh[3]->b_data, qbh->data + 3 * 512, 512);
+	mark_buffer_dirty(qbh->bh[0]);
+	mark_buffer_dirty(qbh->bh[1]);
+	mark_buffer_dirty(qbh->bh[2]);
+	mark_buffer_dirty(qbh->bh[3]);
+}
diff --git a/fs/hpfs/dentry.c b/fs/hpfs/dentry.c
new file mode 100644
index 0000000..0831912
--- /dev/null
+++ b/fs/hpfs/dentry.c
@@ -0,0 +1,60 @@
+/*
+ *  linux/fs/hpfs/dentry.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  dcache operations
+ */
+
+#include "hpfs_fn.h"
+
+/*
+ * Note: the dentry argument is the parent dentry.
+ */
+
+static int hpfs_hash_dentry(struct dentry *dentry, struct qstr *qstr)
+{
+	unsigned long	 hash;
+	int		 i;
+	unsigned l = qstr->len;
+
+	if (l == 1) if (qstr->name[0]=='.') goto x;
+	if (l == 2) if (qstr->name[0]=='.' || qstr->name[1]=='.') goto x;
+	hpfs_adjust_length((char *)qstr->name, &l);
+	/*if (hpfs_chk_name((char *)qstr->name,&l))*/
+		/*return -ENAMETOOLONG;*/
+		/*return -ENOENT;*/
+	x:
+
+	hash = init_name_hash();
+	for (i = 0; i < l; i++)
+		hash = partial_name_hash(hpfs_upcase(hpfs_sb(dentry->d_sb)->sb_cp_table,qstr->name[i]), hash);
+	qstr->hash = end_name_hash(hash);
+
+	return 0;
+}
+
+static int hpfs_compare_dentry(struct dentry *dentry, struct qstr *a, struct qstr *b)
+{
+	unsigned al=a->len;
+	unsigned bl=b->len;
+	hpfs_adjust_length((char *)a->name, &al);
+	/*hpfs_adjust_length((char *)b->name, &bl);*/
+	/* 'a' is the qstr of an already existing dentry, so the name
+	 * must be valid. 'b' must be validated first.
+	 */
+
+	if (hpfs_chk_name((char *)b->name, &bl)) return 1;
+	if (hpfs_compare_names(dentry->d_sb, (char *)a->name, al, (char *)b->name, bl, 0)) return 1;
+	return 0;
+}
+
+static struct dentry_operations hpfs_dentry_operations = {
+	.d_hash		= hpfs_hash_dentry,
+	.d_compare	= hpfs_compare_dentry,
+};
+
+void hpfs_set_dentry_operations(struct dentry *dentry)
+{
+	dentry->d_op = &hpfs_dentry_operations;
+}
diff --git a/fs/hpfs/dir.c b/fs/hpfs/dir.c
new file mode 100644
index 0000000..0217c3a
--- /dev/null
+++ b/fs/hpfs/dir.c
@@ -0,0 +1,320 @@
+/*
+ *  linux/fs/hpfs/dir.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  directory VFS functions
+ */
+
+#include "hpfs_fn.h"
+
+static int hpfs_dir_release(struct inode *inode, struct file *filp)
+{
+	lock_kernel();
+	hpfs_del_pos(inode, &filp->f_pos);
+	/*hpfs_write_if_changed(inode);*/
+	unlock_kernel();
+	return 0;
+}
+
+/* This is slow, but it's not used often */
+
+static loff_t hpfs_dir_lseek(struct file *filp, loff_t off, int whence)
+{
+	loff_t new_off = off + (whence == 1 ? filp->f_pos : 0);
+	loff_t pos;
+	struct quad_buffer_head qbh;
+	struct inode *i = filp->f_dentry->d_inode;
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(i);
+	struct super_block *s = i->i_sb;
+
+	lock_kernel();
+
+	/*printk("dir lseek\n");*/
+	if (new_off == 0 || new_off == 1 || new_off == 11 || new_off == 12 || new_off == 13) goto ok;
+	down(&i->i_sem);
+	pos = ((loff_t) hpfs_de_as_down_as_possible(s, hpfs_inode->i_dno) << 4) + 1;
+	while (pos != new_off) {
+		if (map_pos_dirent(i, &pos, &qbh)) hpfs_brelse4(&qbh);
+		else goto fail;
+		if (pos == 12) goto fail;
+	}
+	up(&i->i_sem);
+ok:
+	unlock_kernel();
+	return filp->f_pos = new_off;
+fail:
+	up(&i->i_sem);
+	/*printk("illegal lseek: %016llx\n", new_off);*/
+	unlock_kernel();
+	return -ESPIPE;
+}
+
+static int hpfs_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+	struct inode *inode = filp->f_dentry->d_inode;
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(inode);
+	struct quad_buffer_head qbh;
+	struct hpfs_dirent *de;
+	int lc;
+	long old_pos;
+	char *tempname;
+	int c1, c2 = 0;
+	int ret = 0;
+
+	lock_kernel();
+
+	if (hpfs_sb(inode->i_sb)->sb_chk) {
+		if (hpfs_chk_sectors(inode->i_sb, inode->i_ino, 1, "dir_fnode")) {
+			ret = -EFSERROR;
+			goto out;
+		}
+		if (hpfs_chk_sectors(inode->i_sb, hpfs_inode->i_dno, 4, "dir_dnode")) {
+			ret = -EFSERROR;
+			goto out;
+		}
+	}
+	if (hpfs_sb(inode->i_sb)->sb_chk >= 2) {
+		struct buffer_head *bh;
+		struct fnode *fno;
+		int e = 0;
+		if (!(fno = hpfs_map_fnode(inode->i_sb, inode->i_ino, &bh))) {
+			ret = -EIOERROR;
+			goto out;
+		}
+		if (!fno->dirflag) {
+			e = 1;
+			hpfs_error(inode->i_sb, "not a directory, fnode %08x",inode->i_ino);
+		}
+		if (hpfs_inode->i_dno != fno->u.external[0].disk_secno) {
+			e = 1;
+			hpfs_error(inode->i_sb, "corrupted inode: i_dno == %08x, fnode -> dnode == %08x", hpfs_inode->i_dno, fno->u.external[0].disk_secno);
+		}
+		brelse(bh);
+		if (e) {
+			ret = -EFSERROR;
+			goto out;
+		}
+	}
+	lc = hpfs_sb(inode->i_sb)->sb_lowercase;
+	if (filp->f_pos == 12) { /* diff -r requires this (note, that diff -r */
+		filp->f_pos = 13; /* also fails on msdos filesystem in 2.0) */
+		goto out;
+	}
+	if (filp->f_pos == 13) {
+		ret = -ENOENT;
+		goto out;
+	}
+	
+	while (1) {
+		again:
+		/* This won't work when cycle is longer than number of dirents
+		   accepted by filldir, but what can I do?
+		   maybe killall -9 ls helps */
+		if (hpfs_sb(inode->i_sb)->sb_chk)
+			if (hpfs_stop_cycles(inode->i_sb, filp->f_pos, &c1, &c2, "hpfs_readdir")) {
+				ret = -EFSERROR;
+				goto out;
+			}
+		if (filp->f_pos == 12)
+			goto out;
+		if (filp->f_pos == 3 || filp->f_pos == 4 || filp->f_pos == 5) {
+			printk("HPFS: warning: pos==%d\n",(int)filp->f_pos);
+			goto out;
+		}
+		if (filp->f_pos == 0) {
+			if (filldir(dirent, ".", 1, filp->f_pos, inode->i_ino, DT_DIR) < 0)
+				goto out;
+			filp->f_pos = 11;
+		}
+		if (filp->f_pos == 11) {
+			if (filldir(dirent, "..", 2, filp->f_pos, hpfs_inode->i_parent_dir, DT_DIR) < 0)
+				goto out;
+			filp->f_pos = 1;
+		}
+		if (filp->f_pos == 1) {
+			filp->f_pos = ((loff_t) hpfs_de_as_down_as_possible(inode->i_sb, hpfs_inode->i_dno) << 4) + 1;
+			hpfs_add_pos(inode, &filp->f_pos);
+			filp->f_version = inode->i_version;
+		}
+		old_pos = filp->f_pos;
+		if (!(de = map_pos_dirent(inode, &filp->f_pos, &qbh))) {
+			ret = -EIOERROR;
+			goto out;
+		}
+		if (de->first || de->last) {
+			if (hpfs_sb(inode->i_sb)->sb_chk) {
+				if (de->first && !de->last && (de->namelen != 2 || de ->name[0] != 1 || de->name[1] != 1)) hpfs_error(inode->i_sb, "hpfs_readdir: bad ^A^A entry; pos = %08x", old_pos);
+				if (de->last && (de->namelen != 1 || de ->name[0] != 255)) hpfs_error(inode->i_sb, "hpfs_readdir: bad \\377 entry; pos = %08x", old_pos);
+			}
+			hpfs_brelse4(&qbh);
+			goto again;
+		}
+		tempname = hpfs_translate_name(inode->i_sb, de->name, de->namelen, lc, de->not_8x3);
+		if (filldir(dirent, tempname, de->namelen, old_pos, de->fnode, DT_UNKNOWN) < 0) {
+			filp->f_pos = old_pos;
+			if (tempname != (char *)de->name) kfree(tempname);
+			hpfs_brelse4(&qbh);
+			goto out;
+		}
+		if (tempname != (char *)de->name) kfree(tempname);
+		hpfs_brelse4(&qbh);
+	}
+out:
+	unlock_kernel();
+	return ret;
+}
+
+/*
+ * lookup.  Search the specified directory for the specified name, set
+ * *result to the corresponding inode.
+ *
+ * lookup uses the inode number to tell read_inode whether it is reading
+ * the inode of a directory or a file -- file ino's are odd, directory
+ * ino's are even.  read_inode avoids i/o for file inodes; everything
+ * needed is up here in the directory.  (And file fnodes are out in
+ * the boondocks.)
+ *
+ *    - M.P.: this is over, sometimes we've got to read file's fnode for eas
+ *	      inode numbers are just fnode sector numbers; iget lock is used
+ *	      to tell read_inode to read fnode or not.
+ */
+
+struct dentry *hpfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
+{
+	const char *name = dentry->d_name.name;
+	unsigned len = dentry->d_name.len;
+	struct quad_buffer_head qbh;
+	struct hpfs_dirent *de;
+	ino_t ino;
+	int err;
+	struct inode *result = NULL;
+	struct hpfs_inode_info *hpfs_result;
+
+	lock_kernel();
+	if ((err = hpfs_chk_name((char *)name, &len))) {
+		if (err == -ENAMETOOLONG) {
+			unlock_kernel();
+			return ERR_PTR(-ENAMETOOLONG);
+		}
+		goto end_add;
+	}
+
+	/*
+	 * '.' and '..' will never be passed here.
+	 */
+
+	de = map_dirent(dir, hpfs_i(dir)->i_dno, (char *) name, len, NULL, &qbh);
+
+	/*
+	 * This is not really a bailout, just means file not found.
+	 */
+
+	if (!de) goto end;
+
+	/*
+	 * Get inode number, what we're after.
+	 */
+
+	ino = de->fnode;
+
+	/*
+	 * Go find or make an inode.
+	 */
+
+	result = iget_locked(dir->i_sb, ino);
+	if (!result) {
+		hpfs_error(dir->i_sb, "hpfs_lookup: can't get inode");
+		goto bail1;
+	}
+	if (result->i_state & I_NEW) {
+		hpfs_init_inode(result);
+		if (de->directory)
+			hpfs_read_inode(result);
+		else if (de->ea_size && hpfs_sb(dir->i_sb)->sb_eas)
+			hpfs_read_inode(result);
+		else {
+			result->i_mode |= S_IFREG;
+			result->i_mode &= ~0111;
+			result->i_op = &hpfs_file_iops;
+			result->i_fop = &hpfs_file_ops;
+			result->i_nlink = 1;
+		}
+		unlock_new_inode(result);
+	}
+	hpfs_result = hpfs_i(result);
+	if (!de->directory) hpfs_result->i_parent_dir = dir->i_ino;
+
+	hpfs_decide_conv(result, (char *)name, len);
+
+	if (de->has_acl || de->has_xtd_perm) if (!(dir->i_sb->s_flags & MS_RDONLY)) {
+		hpfs_error(result->i_sb, "ACLs or XPERM found. This is probably HPFS386. This driver doesn't support it now. Send me some info on these structures");
+		goto bail1;
+	}
+
+	/*
+	 * Fill in the info from the directory if this is a newly created
+	 * inode.
+	 */
+
+	if (!result->i_ctime.tv_sec) {
+		if (!(result->i_ctime.tv_sec = local_to_gmt(dir->i_sb, de->creation_date)))
+			result->i_ctime.tv_sec = 1;
+		result->i_ctime.tv_nsec = 0;
+		result->i_mtime.tv_sec = local_to_gmt(dir->i_sb, de->write_date);
+		result->i_mtime.tv_nsec = 0;
+		result->i_atime.tv_sec = local_to_gmt(dir->i_sb, de->read_date);
+		result->i_atime.tv_nsec = 0;
+		hpfs_result->i_ea_size = de->ea_size;
+		if (!hpfs_result->i_ea_mode && de->read_only)
+			result->i_mode &= ~0222;
+		if (!de->directory) {
+			if (result->i_size == -1) {
+				result->i_size = de->file_size;
+				result->i_data.a_ops = &hpfs_aops;
+				hpfs_i(result)->mmu_private = result->i_size;
+			/*
+			 * i_blocks should count the fnode and any anodes.
+			 * We count 1 for the fnode and don't bother about
+			 * anodes -- the disk heads are on the directory band
+			 * and we want them to stay there.
+			 */
+				result->i_blocks = 1 + ((result->i_size + 511) >> 9);
+			}
+		}
+	}
+
+	hpfs_brelse4(&qbh);
+
+	/*
+	 * Made it.
+	 */
+
+	end:
+	end_add:
+	hpfs_set_dentry_operations(dentry);
+	unlock_kernel();
+	d_add(dentry, result);
+	return NULL;
+
+	/*
+	 * Didn't.
+	 */
+	bail1:
+	
+	hpfs_brelse4(&qbh);
+	
+	/*bail:*/
+
+	unlock_kernel();
+	return ERR_PTR(-ENOENT);
+}
+
+struct file_operations hpfs_dir_ops =
+{
+	.llseek		= hpfs_dir_lseek,
+	.read		= generic_read_dir,
+	.readdir	= hpfs_readdir,
+	.release	= hpfs_dir_release,
+	.fsync		= hpfs_file_fsync,
+};
diff --git a/fs/hpfs/dnode.c b/fs/hpfs/dnode.c
new file mode 100644
index 0000000..1d21307
--- /dev/null
+++ b/fs/hpfs/dnode.c
@@ -0,0 +1,1080 @@
+/*
+ *  linux/fs/hpfs/dnode.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  handling directory dnode tree - adding, deleteing & searching for dirents
+ */
+
+#include "hpfs_fn.h"
+
+static loff_t get_pos(struct dnode *d, struct hpfs_dirent *fde)
+{
+	struct hpfs_dirent *de;
+	struct hpfs_dirent *de_end = dnode_end_de(d);
+	int i = 1;
+	for (de = dnode_first_de(d); de < de_end; de = de_next_de(de)) {
+		if (de == fde) return ((loff_t) d->self << 4) | (loff_t)i;
+		i++;
+	}
+	printk("HPFS: get_pos: not_found\n");
+	return ((loff_t)d->self << 4) | (loff_t)1;
+}
+
+void hpfs_add_pos(struct inode *inode, loff_t *pos)
+{
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(inode);
+	int i = 0;
+	loff_t **ppos;
+
+	if (hpfs_inode->i_rddir_off)
+		for (; hpfs_inode->i_rddir_off[i]; i++)
+			if (hpfs_inode->i_rddir_off[i] == pos) return;
+	if (!(i&0x0f)) {
+		if (!(ppos = kmalloc((i+0x11) * sizeof(loff_t*), GFP_NOFS))) {
+			printk("HPFS: out of memory for position list\n");
+			return;
+		}
+		if (hpfs_inode->i_rddir_off) {
+			memcpy(ppos, hpfs_inode->i_rddir_off, i * sizeof(loff_t));
+			kfree(hpfs_inode->i_rddir_off);
+		}
+		hpfs_inode->i_rddir_off = ppos;
+	}
+	hpfs_inode->i_rddir_off[i] = pos;
+	hpfs_inode->i_rddir_off[i + 1] = NULL;
+}
+
+void hpfs_del_pos(struct inode *inode, loff_t *pos)
+{
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(inode);
+	loff_t **i, **j;
+
+	if (!hpfs_inode->i_rddir_off) goto not_f;
+	for (i = hpfs_inode->i_rddir_off; *i; i++) if (*i == pos) goto fnd;
+	goto not_f;
+	fnd:
+	for (j = i + 1; *j; j++) ;
+	*i = *(j - 1);
+	*(j - 1) = NULL;
+	if (j - 1 == hpfs_inode->i_rddir_off) {
+		kfree(hpfs_inode->i_rddir_off);
+		hpfs_inode->i_rddir_off = NULL;
+	}
+	return;
+	not_f:
+	/*printk("HPFS: warning: position pointer %p->%08x not found\n", pos, (int)*pos);*/
+	return;
+}
+
+static void for_all_poss(struct inode *inode, void (*f)(loff_t *, loff_t, loff_t),
+			 loff_t p1, loff_t p2)
+{
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(inode);
+	loff_t **i;
+
+	if (!hpfs_inode->i_rddir_off) return;
+	for (i = hpfs_inode->i_rddir_off; *i; i++) (*f)(*i, p1, p2);
+	return;
+}
+
+static void hpfs_pos_subst(loff_t *p, loff_t f, loff_t t)
+{
+	if (*p == f) *p = t;
+}
+
+/*void hpfs_hpfs_pos_substd(loff_t *p, loff_t f, loff_t t)
+{
+	if ((*p & ~0x3f) == (f & ~0x3f)) *p = (t & ~0x3f) | (*p & 0x3f);
+}*/
+
+static void hpfs_pos_ins(loff_t *p, loff_t d, loff_t c)
+{
+	if ((*p & ~0x3f) == (d & ~0x3f) && (*p & 0x3f) >= (d & 0x3f)) {
+		int n = (*p & 0x3f) + c;
+		if (n > 0x3f) printk("HPFS: hpfs_pos_ins: %08x + %d\n", (int)*p, (int)c >> 8);
+		else *p = (*p & ~0x3f) | n;
+	}
+}
+
+static void hpfs_pos_del(loff_t *p, loff_t d, loff_t c)
+{
+	if ((*p & ~0x3f) == (d & ~0x3f) && (*p & 0x3f) >= (d & 0x3f)) {
+		int n = (*p & 0x3f) - c;
+		if (n < 1) printk("HPFS: hpfs_pos_ins: %08x - %d\n", (int)*p, (int)c >> 8);
+		else *p = (*p & ~0x3f) | n;
+	}
+}
+
+static struct hpfs_dirent *dnode_pre_last_de(struct dnode *d)
+{
+	struct hpfs_dirent *de, *de_end, *dee = NULL, *deee = NULL;
+	de_end = dnode_end_de(d);
+	for (de = dnode_first_de(d); de < de_end; de = de_next_de(de)) {
+		deee = dee; dee = de;
+	}	
+	return deee;
+}
+
+static struct hpfs_dirent *dnode_last_de(struct dnode *d)
+{
+	struct hpfs_dirent *de, *de_end, *dee = NULL;
+	de_end = dnode_end_de(d);
+	for (de = dnode_first_de(d); de < de_end; de = de_next_de(de)) {
+		dee = de;
+	}	
+	return dee;
+}
+
+static void set_last_pointer(struct super_block *s, struct dnode *d, dnode_secno ptr)
+{
+	struct hpfs_dirent *de;
+	if (!(de = dnode_last_de(d))) {
+		hpfs_error(s, "set_last_pointer: empty dnode %08x", d->self);
+		return;
+	}
+	if (hpfs_sb(s)->sb_chk) {
+		if (de->down) {
+			hpfs_error(s, "set_last_pointer: dnode %08x has already last pointer %08x",
+				d->self, de_down_pointer(de));
+			return;
+		}
+		if (de->length != 32) {
+			hpfs_error(s, "set_last_pointer: bad last dirent in dnode %08x", d->self);
+			return;
+		}
+	}
+	if (ptr) {
+		if ((d->first_free += 4) > 2048) {
+			hpfs_error(s,"set_last_pointer: too long dnode %08x", d->self);
+			d->first_free -= 4;
+			return;
+		}
+		de->length = 36;
+		de->down = 1;
+		*(dnode_secno *)((char *)de + 32) = ptr;
+	}
+}
+
+/* Add an entry to dnode and don't care if it grows over 2048 bytes */
+
+struct hpfs_dirent *hpfs_add_de(struct super_block *s, struct dnode *d, unsigned char *name,
+				unsigned namelen, secno down_ptr)
+{
+	struct hpfs_dirent *de;
+	struct hpfs_dirent *de_end = dnode_end_de(d);
+	unsigned d_size = de_size(namelen, down_ptr);
+	for (de = dnode_first_de(d); de < de_end; de = de_next_de(de)) {
+		int c = hpfs_compare_names(s, name, namelen, de->name, de->namelen, de->last);
+		if (!c) {
+			hpfs_error(s, "name (%c,%d) already exists in dnode %08x", *name, namelen, d->self);
+			return NULL;
+		}
+		if (c < 0) break;
+	}
+	memmove((char *)de + d_size, de, (char *)de_end - (char *)de);
+	memset(de, 0, d_size);
+	if (down_ptr) {
+		*(int *)((char *)de + d_size - 4) = down_ptr;
+		de->down = 1;
+	}
+	de->length = d_size;
+	if (down_ptr) de->down = 1;
+	de->not_8x3 = hpfs_is_name_long(name, namelen);
+	de->namelen = namelen;
+	memcpy(de->name, name, namelen);
+	d->first_free += d_size;
+	return de;
+}
+
+/* Delete dirent and don't care about its subtree */
+
+static void hpfs_delete_de(struct super_block *s, struct dnode *d,
+			   struct hpfs_dirent *de)
+{
+	if (de->last) {
+		hpfs_error(s, "attempt to delete last dirent in dnode %08x", d->self);
+		return;
+	}
+	d->first_free -= de->length;
+	memmove(de, de_next_de(de), d->first_free + (char *)d - (char *)de);
+}
+
+static void fix_up_ptrs(struct super_block *s, struct dnode *d)
+{
+	struct hpfs_dirent *de;
+	struct hpfs_dirent *de_end = dnode_end_de(d);
+	dnode_secno dno = d->self;
+	for (de = dnode_first_de(d); de < de_end; de = de_next_de(de))
+		if (de->down) {
+			struct quad_buffer_head qbh;
+			struct dnode *dd;
+			if ((dd = hpfs_map_dnode(s, de_down_pointer(de), &qbh))) {
+				if (dd->up != dno || dd->root_dnode) {
+					dd->up = dno;
+					dd->root_dnode = 0;
+					hpfs_mark_4buffers_dirty(&qbh);
+				}
+				hpfs_brelse4(&qbh);
+			}
+		}
+}
+
+/* Add an entry to dnode and do dnode splitting if required */
+
+static int hpfs_add_to_dnode(struct inode *i, dnode_secno dno,
+			     unsigned char *name, unsigned namelen,
+			     struct hpfs_dirent *new_de, dnode_secno down_ptr)
+{
+	struct quad_buffer_head qbh, qbh1, qbh2;
+	struct dnode *d, *ad, *rd, *nd = NULL;
+	dnode_secno adno, rdno;
+	struct hpfs_dirent *de;
+	struct hpfs_dirent nde;
+	char *nname;
+	int h;
+	int pos;
+	struct buffer_head *bh;
+	struct fnode *fnode;
+	int c1, c2 = 0;
+	if (!(nname = kmalloc(256, GFP_NOFS))) {
+		printk("HPFS: out of memory, can't add to dnode\n");
+		return 1;
+	}
+	go_up:
+	if (namelen >= 256) {
+		hpfs_error(i->i_sb, "hpfs_add_to_dnode: namelen == %d", namelen);
+		if (nd) kfree(nd);
+		kfree(nname);
+		return 1;
+	}
+	if (!(d = hpfs_map_dnode(i->i_sb, dno, &qbh))) {
+		if (nd) kfree(nd);
+		kfree(nname);
+		return 1;
+	}
+	go_up_a:
+	if (hpfs_sb(i->i_sb)->sb_chk)
+		if (hpfs_stop_cycles(i->i_sb, dno, &c1, &c2, "hpfs_add_to_dnode")) {
+			hpfs_brelse4(&qbh);
+			if (nd) kfree(nd);
+			kfree(nname);
+			return 1;
+		}
+	if (d->first_free + de_size(namelen, down_ptr) <= 2048) {
+		loff_t t;
+		copy_de(de=hpfs_add_de(i->i_sb, d, name, namelen, down_ptr), new_de);
+		t = get_pos(d, de);
+		for_all_poss(i, hpfs_pos_ins, t, 1);
+		for_all_poss(i, hpfs_pos_subst, 4, t);
+		for_all_poss(i, hpfs_pos_subst, 5, t + 1);
+		hpfs_mark_4buffers_dirty(&qbh);
+		hpfs_brelse4(&qbh);
+		if (nd) kfree(nd);
+		kfree(nname);
+		return 0;
+	}
+	if (!nd) if (!(nd = kmalloc(0x924, GFP_NOFS))) {
+		/* 0x924 is a max size of dnode after adding a dirent with
+		   max name length. We alloc this only once. There must
+		   not be any error while splitting dnodes, otherwise the
+		   whole directory, not only file we're adding, would
+		   be lost. */
+		printk("HPFS: out of memory for dnode splitting\n");
+		hpfs_brelse4(&qbh);
+		kfree(nname);
+		return 1;
+	}	
+	memcpy(nd, d, d->first_free);
+	copy_de(de = hpfs_add_de(i->i_sb, nd, name, namelen, down_ptr), new_de);
+	for_all_poss(i, hpfs_pos_ins, get_pos(nd, de), 1);
+	h = ((char *)dnode_last_de(nd) - (char *)nd) / 2 + 10;
+	if (!(ad = hpfs_alloc_dnode(i->i_sb, d->up, &adno, &qbh1, 0))) {
+		hpfs_error(i->i_sb, "unable to alloc dnode - dnode tree will be corrupted");
+		hpfs_brelse4(&qbh);
+		kfree(nd);
+		kfree(nname);
+		return 1;
+	}
+	i->i_size += 2048;
+	i->i_blocks += 4;
+	pos = 1;
+	for (de = dnode_first_de(nd); (char *)de_next_de(de) - (char *)nd < h; de = de_next_de(de)) {
+		copy_de(hpfs_add_de(i->i_sb, ad, de->name, de->namelen, de->down ? de_down_pointer(de) : 0), de);
+		for_all_poss(i, hpfs_pos_subst, ((loff_t)dno << 4) | pos, ((loff_t)adno << 4) | pos);
+		pos++;
+	}
+	copy_de(new_de = &nde, de);
+	memcpy(name = nname, de->name, namelen = de->namelen);
+	for_all_poss(i, hpfs_pos_subst, ((loff_t)dno << 4) | pos, 4);
+	down_ptr = adno;
+	set_last_pointer(i->i_sb, ad, de->down ? de_down_pointer(de) : 0);
+	de = de_next_de(de);
+	memmove((char *)nd + 20, de, nd->first_free + (char *)nd - (char *)de);
+	nd->first_free -= (char *)de - (char *)nd - 20;
+	memcpy(d, nd, nd->first_free);
+	for_all_poss(i, hpfs_pos_del, (loff_t)dno << 4, pos);
+	fix_up_ptrs(i->i_sb, ad);
+	if (!d->root_dnode) {
+		dno = ad->up = d->up;
+		hpfs_mark_4buffers_dirty(&qbh);
+		hpfs_brelse4(&qbh);
+		hpfs_mark_4buffers_dirty(&qbh1);
+		hpfs_brelse4(&qbh1);
+		goto go_up;
+	}
+	if (!(rd = hpfs_alloc_dnode(i->i_sb, d->up, &rdno, &qbh2, 0))) {
+		hpfs_error(i->i_sb, "unable to alloc dnode - dnode tree will be corrupted");
+		hpfs_brelse4(&qbh);
+		hpfs_brelse4(&qbh1);
+		kfree(nd);
+		kfree(nname);
+		return 1;
+	}
+	i->i_size += 2048;
+	i->i_blocks += 4;
+	rd->root_dnode = 1;
+	rd->up = d->up;
+	if (!(fnode = hpfs_map_fnode(i->i_sb, d->up, &bh))) {
+		hpfs_free_dnode(i->i_sb, rdno);
+		hpfs_brelse4(&qbh);
+		hpfs_brelse4(&qbh1);
+		hpfs_brelse4(&qbh2);
+		kfree(nd);
+		kfree(nname);
+		return 1;
+	}
+	fnode->u.external[0].disk_secno = rdno;
+	mark_buffer_dirty(bh);
+	brelse(bh);
+	d->up = ad->up = hpfs_i(i)->i_dno = rdno;
+	d->root_dnode = ad->root_dnode = 0;
+	hpfs_mark_4buffers_dirty(&qbh);
+	hpfs_brelse4(&qbh);
+	hpfs_mark_4buffers_dirty(&qbh1);
+	hpfs_brelse4(&qbh1);
+	qbh = qbh2;
+	set_last_pointer(i->i_sb, rd, dno);
+	dno = rdno;
+	d = rd;
+	goto go_up_a;
+}
+
+/*
+ * Add an entry to directory btree.
+ * I hate such crazy directory structure.
+ * It's easy to read but terrible to write.
+ * I wrote this directory code 4 times.
+ * I hope, now it's finally bug-free.
+ */
+
+int hpfs_add_dirent(struct inode *i, unsigned char *name, unsigned namelen,
+		    struct hpfs_dirent *new_de, int cdepth)
+{
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(i);
+	struct dnode *d;
+	struct hpfs_dirent *de, *de_end;
+	struct quad_buffer_head qbh;
+	dnode_secno dno;
+	int c;
+	int c1, c2 = 0;
+	dno = hpfs_inode->i_dno;
+	down:
+	if (hpfs_sb(i->i_sb)->sb_chk)
+		if (hpfs_stop_cycles(i->i_sb, dno, &c1, &c2, "hpfs_add_dirent")) return 1;
+	if (!(d = hpfs_map_dnode(i->i_sb, dno, &qbh))) return 1;
+	de_end = dnode_end_de(d);
+	for (de = dnode_first_de(d); de < de_end; de = de_next_de(de)) {
+		if (!(c = hpfs_compare_names(i->i_sb, name, namelen, de->name, de->namelen, de->last))) {
+			hpfs_brelse4(&qbh);
+			return -1;
+		}	
+		if (c < 0) {
+			if (de->down) {
+				dno = de_down_pointer(de);
+				hpfs_brelse4(&qbh);
+				goto down;
+			}
+			break;
+		}
+	}
+	hpfs_brelse4(&qbh);
+	if (!cdepth) hpfs_lock_creation(i->i_sb);
+	if (hpfs_check_free_dnodes(i->i_sb, FREE_DNODES_ADD)) {
+		c = 1;
+		goto ret;
+	}	
+	i->i_version++;
+	c = hpfs_add_to_dnode(i, dno, name, namelen, new_de, 0);
+	ret:
+	if (!cdepth) hpfs_unlock_creation(i->i_sb);
+	return c;
+}
+
+/* 
+ * Find dirent with higher name in 'from' subtree and move it to 'to' dnode.
+ * Return the dnode we moved from (to be checked later if it's empty)
+ */
+
+static secno move_to_top(struct inode *i, dnode_secno from, dnode_secno to)
+{
+	dnode_secno dno, ddno;
+	dnode_secno chk_up = to;
+	struct dnode *dnode;
+	struct quad_buffer_head qbh;
+	struct hpfs_dirent *de, *nde;
+	int a;
+	loff_t t;
+	int c1, c2 = 0;
+	dno = from;
+	while (1) {
+		if (hpfs_sb(i->i_sb)->sb_chk)
+			if (hpfs_stop_cycles(i->i_sb, dno, &c1, &c2, "move_to_top"))
+				return 0;
+		if (!(dnode = hpfs_map_dnode(i->i_sb, dno, &qbh))) return 0;
+		if (hpfs_sb(i->i_sb)->sb_chk) {
+			if (dnode->up != chk_up) {
+				hpfs_error(i->i_sb, "move_to_top: up pointer from %08x should be %08x, is %08x",
+					dno, chk_up, dnode->up);
+				hpfs_brelse4(&qbh);
+				return 0;
+			}
+			chk_up = dno;
+		}
+		if (!(de = dnode_last_de(dnode))) {
+			hpfs_error(i->i_sb, "move_to_top: dnode %08x has no last de", dno);
+			hpfs_brelse4(&qbh);
+			return 0;
+		}
+		if (!de->down) break;
+		dno = de_down_pointer(de);
+		hpfs_brelse4(&qbh);
+	}
+	while (!(de = dnode_pre_last_de(dnode))) {
+		dnode_secno up = dnode->up;
+		hpfs_brelse4(&qbh);
+		hpfs_free_dnode(i->i_sb, dno);
+		i->i_size -= 2048;
+		i->i_blocks -= 4;
+		for_all_poss(i, hpfs_pos_subst, ((loff_t)dno << 4) | 1, 5);
+		if (up == to) return to;
+		if (!(dnode = hpfs_map_dnode(i->i_sb, up, &qbh))) return 0;
+		if (dnode->root_dnode) {
+			hpfs_error(i->i_sb, "move_to_top: got to root_dnode while moving from %08x to %08x", from, to);
+			hpfs_brelse4(&qbh);
+			return 0;
+		}
+		de = dnode_last_de(dnode);
+		if (!de || !de->down) {
+			hpfs_error(i->i_sb, "move_to_top: dnode %08x doesn't point down to %08x", up, dno);
+			hpfs_brelse4(&qbh);
+			return 0;
+		}
+		dnode->first_free -= 4;
+		de->length -= 4;
+		de->down = 0;
+		hpfs_mark_4buffers_dirty(&qbh);
+		dno = up;
+	}
+	t = get_pos(dnode, de);
+	for_all_poss(i, hpfs_pos_subst, t, 4);
+	for_all_poss(i, hpfs_pos_subst, t + 1, 5);
+	if (!(nde = kmalloc(de->length, GFP_NOFS))) {
+		hpfs_error(i->i_sb, "out of memory for dirent - directory will be corrupted");
+		hpfs_brelse4(&qbh);
+		return 0;
+	}
+	memcpy(nde, de, de->length);
+	ddno = de->down ? de_down_pointer(de) : 0;
+	hpfs_delete_de(i->i_sb, dnode, de);
+	set_last_pointer(i->i_sb, dnode, ddno);
+	hpfs_mark_4buffers_dirty(&qbh);
+	hpfs_brelse4(&qbh);
+	a = hpfs_add_to_dnode(i, to, nde->name, nde->namelen, nde, from);
+	kfree(nde);
+	if (a) return 0;
+	return dno;
+}
+
+/* 
+ * Check if a dnode is empty and delete it from the tree
+ * (chkdsk doesn't like empty dnodes)
+ */
+
+static void delete_empty_dnode(struct inode *i, dnode_secno dno)
+{
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(i);
+	struct quad_buffer_head qbh;
+	struct dnode *dnode;
+	dnode_secno down, up, ndown;
+	int p;
+	struct hpfs_dirent *de;
+	int c1, c2 = 0;
+	try_it_again:
+	if (hpfs_stop_cycles(i->i_sb, dno, &c1, &c2, "delete_empty_dnode")) return;
+	if (!(dnode = hpfs_map_dnode(i->i_sb, dno, &qbh))) return;
+	if (dnode->first_free > 56) goto end;
+	if (dnode->first_free == 52 || dnode->first_free == 56) {
+		struct hpfs_dirent *de_end;
+		int root = dnode->root_dnode;
+		up = dnode->up;
+		de = dnode_first_de(dnode);
+		down = de->down ? de_down_pointer(de) : 0;
+		if (hpfs_sb(i->i_sb)->sb_chk) if (root && !down) {
+			hpfs_error(i->i_sb, "delete_empty_dnode: root dnode %08x is empty", dno);
+			goto end;
+		}
+		hpfs_brelse4(&qbh);
+		hpfs_free_dnode(i->i_sb, dno);
+		i->i_size -= 2048;
+		i->i_blocks -= 4;
+		if (root) {
+			struct fnode *fnode;
+			struct buffer_head *bh;
+			struct dnode *d1;
+			struct quad_buffer_head qbh1;
+			if (hpfs_sb(i->i_sb)->sb_chk) if (up != i->i_ino) {
+				hpfs_error(i->i_sb, "bad pointer to fnode, dnode %08x, pointing to %08x, should be %08x", dno, up, i->i_ino);
+				return;
+			}
+			if ((d1 = hpfs_map_dnode(i->i_sb, down, &qbh1))) {
+				d1->up = up;
+				d1->root_dnode = 1;
+				hpfs_mark_4buffers_dirty(&qbh1);
+				hpfs_brelse4(&qbh1);
+			}
+			if ((fnode = hpfs_map_fnode(i->i_sb, up, &bh))) {
+				fnode->u.external[0].disk_secno = down;
+				mark_buffer_dirty(bh);
+				brelse(bh);
+			}
+			hpfs_inode->i_dno = down;
+			for_all_poss(i, hpfs_pos_subst, ((loff_t)dno << 4) | 1, (loff_t) 12);
+			return;
+		}
+		if (!(dnode = hpfs_map_dnode(i->i_sb, up, &qbh))) return;
+		p = 1;
+		de_end = dnode_end_de(dnode);
+		for (de = dnode_first_de(dnode); de < de_end; de = de_next_de(de), p++)
+			if (de->down) if (de_down_pointer(de) == dno) goto fnd;
+		hpfs_error(i->i_sb, "delete_empty_dnode: pointer to dnode %08x not found in dnode %08x", dno, up);
+		goto end;
+		fnd:
+		for_all_poss(i, hpfs_pos_subst, ((loff_t)dno << 4) | 1, ((loff_t)up << 4) | p);
+		if (!down) {
+			de->down = 0;
+			de->length -= 4;
+			dnode->first_free -= 4;
+			memmove(de_next_de(de), (char *)de_next_de(de) + 4,
+				(char *)dnode + dnode->first_free - (char *)de_next_de(de));
+		} else {
+			struct dnode *d1;
+			struct quad_buffer_head qbh1;
+			*(dnode_secno *) ((void *) de + de->length - 4) = down;
+			if ((d1 = hpfs_map_dnode(i->i_sb, down, &qbh1))) {
+				d1->up = up;
+				hpfs_mark_4buffers_dirty(&qbh1);
+				hpfs_brelse4(&qbh1);
+			}
+		}
+	} else {
+		hpfs_error(i->i_sb, "delete_empty_dnode: dnode %08x, first_free == %03x", dno, dnode->first_free);
+		goto end;
+	}
+
+	if (!de->last) {
+		struct hpfs_dirent *de_next = de_next_de(de);
+		struct hpfs_dirent *de_cp;
+		struct dnode *d1;
+		struct quad_buffer_head qbh1;
+		if (!de_next->down) goto endm;
+		ndown = de_down_pointer(de_next);
+		if (!(de_cp = kmalloc(de->length, GFP_NOFS))) {
+			printk("HPFS: out of memory for dtree balancing\n");
+			goto endm;
+		}
+		memcpy(de_cp, de, de->length);
+		hpfs_delete_de(i->i_sb, dnode, de);
+		hpfs_mark_4buffers_dirty(&qbh);
+		hpfs_brelse4(&qbh);
+		for_all_poss(i, hpfs_pos_subst, ((loff_t)up << 4) | p, 4);
+		for_all_poss(i, hpfs_pos_del, ((loff_t)up << 4) | p, 1);
+		if (de_cp->down) if ((d1 = hpfs_map_dnode(i->i_sb, de_down_pointer(de_cp), &qbh1))) {
+			d1->up = ndown;
+			hpfs_mark_4buffers_dirty(&qbh1);
+			hpfs_brelse4(&qbh1);
+		}
+		hpfs_add_to_dnode(i, ndown, de_cp->name, de_cp->namelen, de_cp, de_cp->down ? de_down_pointer(de_cp) : 0);
+		/*printk("UP-TO-DNODE: %08x (ndown = %08x, down = %08x, dno = %08x)\n", up, ndown, down, dno);*/
+		dno = up;
+		kfree(de_cp);
+		goto try_it_again;
+	} else {
+		struct hpfs_dirent *de_prev = dnode_pre_last_de(dnode);
+		struct hpfs_dirent *de_cp;
+		struct dnode *d1;
+		struct quad_buffer_head qbh1;
+		dnode_secno dlp;
+		if (!de_prev) {
+			hpfs_error(i->i_sb, "delete_empty_dnode: empty dnode %08x", up);
+			hpfs_mark_4buffers_dirty(&qbh);
+			hpfs_brelse4(&qbh);
+			dno = up;
+			goto try_it_again;
+		}
+		if (!de_prev->down) goto endm;
+		ndown = de_down_pointer(de_prev);
+		if ((d1 = hpfs_map_dnode(i->i_sb, ndown, &qbh1))) {
+			struct hpfs_dirent *del = dnode_last_de(d1);
+			dlp = del->down ? de_down_pointer(del) : 0;
+			if (!dlp && down) {
+				if (d1->first_free > 2044) {
+					if (hpfs_sb(i->i_sb)->sb_chk >= 2) {
+						printk("HPFS: warning: unbalanced dnode tree, see hpfs.txt 4 more info\n");
+						printk("HPFS: warning: terminating balancing operation\n");
+					}
+					hpfs_brelse4(&qbh1);
+					goto endm;
+				}
+				if (hpfs_sb(i->i_sb)->sb_chk >= 2) {
+					printk("HPFS: warning: unbalanced dnode tree, see hpfs.txt 4 more info\n");
+					printk("HPFS: warning: goin'on\n");
+				}
+				del->length += 4;
+				del->down = 1;
+				d1->first_free += 4;
+			}
+			if (dlp && !down) {
+				del->length -= 4;
+				del->down = 0;
+				d1->first_free -= 4;
+			} else if (down)
+				*(dnode_secno *) ((void *) del + del->length - 4) = down;
+		} else goto endm;
+		if (!(de_cp = kmalloc(de_prev->length, GFP_NOFS))) {
+			printk("HPFS: out of memory for dtree balancing\n");
+			hpfs_brelse4(&qbh1);
+			goto endm;
+		}
+		hpfs_mark_4buffers_dirty(&qbh1);
+		hpfs_brelse4(&qbh1);
+		memcpy(de_cp, de_prev, de_prev->length);
+		hpfs_delete_de(i->i_sb, dnode, de_prev);
+		if (!de_prev->down) {
+			de_prev->length += 4;
+			de_prev->down = 1;
+			dnode->first_free += 4;
+		}
+		*(dnode_secno *) ((void *) de_prev + de_prev->length - 4) = ndown;
+		hpfs_mark_4buffers_dirty(&qbh);
+		hpfs_brelse4(&qbh);
+		for_all_poss(i, hpfs_pos_subst, ((loff_t)up << 4) | (p - 1), 4);
+		for_all_poss(i, hpfs_pos_subst, ((loff_t)up << 4) | p, ((loff_t)up << 4) | (p - 1));
+		if (down) if ((d1 = hpfs_map_dnode(i->i_sb, de_down_pointer(de), &qbh1))) {
+			d1->up = ndown;
+			hpfs_mark_4buffers_dirty(&qbh1);
+			hpfs_brelse4(&qbh1);
+		}
+		hpfs_add_to_dnode(i, ndown, de_cp->name, de_cp->namelen, de_cp, dlp);
+		dno = up;
+		kfree(de_cp);
+		goto try_it_again;
+	}
+	endm:
+	hpfs_mark_4buffers_dirty(&qbh);
+	end:
+	hpfs_brelse4(&qbh);
+}
+
+
+/* Delete dirent from directory */
+
+int hpfs_remove_dirent(struct inode *i, dnode_secno dno, struct hpfs_dirent *de,
+		       struct quad_buffer_head *qbh, int depth)
+{
+	struct dnode *dnode = qbh->data;
+	dnode_secno down = 0;
+	int lock = 0;
+	loff_t t;
+	if (de->first || de->last) {
+		hpfs_error(i->i_sb, "hpfs_remove_dirent: attempt to delete first or last dirent in dnode %08x", dno);
+		hpfs_brelse4(qbh);
+		return 1;
+	}
+	if (de->down) down = de_down_pointer(de);
+	if (depth && (de->down || (de == dnode_first_de(dnode) && de_next_de(de)->last))) {
+		lock = 1;
+		hpfs_lock_creation(i->i_sb);
+		if (hpfs_check_free_dnodes(i->i_sb, FREE_DNODES_DEL)) {
+			hpfs_brelse4(qbh);
+			hpfs_unlock_creation(i->i_sb);
+			return 2;
+		}
+	}
+	i->i_version++;
+	for_all_poss(i, hpfs_pos_del, (t = get_pos(dnode, de)) + 1, 1);
+	hpfs_delete_de(i->i_sb, dnode, de);
+	hpfs_mark_4buffers_dirty(qbh);
+	hpfs_brelse4(qbh);
+	if (down) {
+		dnode_secno a = move_to_top(i, down, dno);
+		for_all_poss(i, hpfs_pos_subst, 5, t);
+		if (a) delete_empty_dnode(i, a);
+		if (lock) hpfs_unlock_creation(i->i_sb);
+		return !a;
+	}
+	delete_empty_dnode(i, dno);
+	if (lock) hpfs_unlock_creation(i->i_sb);
+	return 0;
+}
+
+void hpfs_count_dnodes(struct super_block *s, dnode_secno dno, int *n_dnodes,
+		       int *n_subdirs, int *n_items)
+{
+	struct dnode *dnode;
+	struct quad_buffer_head qbh;
+	struct hpfs_dirent *de;
+	dnode_secno ptr, odno = 0;
+	int c1, c2 = 0;
+	int d1, d2 = 0;
+	go_down:
+	if (n_dnodes) (*n_dnodes)++;
+	if (hpfs_sb(s)->sb_chk)
+		if (hpfs_stop_cycles(s, dno, &c1, &c2, "hpfs_count_dnodes #1")) return;
+	ptr = 0;
+	go_up:
+	if (!(dnode = hpfs_map_dnode(s, dno, &qbh))) return;
+	if (hpfs_sb(s)->sb_chk) if (odno && odno != -1 && dnode->up != odno)
+		hpfs_error(s, "hpfs_count_dnodes: bad up pointer; dnode %08x, down %08x points to %08x", odno, dno, dnode->up);
+	de = dnode_first_de(dnode);
+	if (ptr) while(1) {
+		if (de->down) if (de_down_pointer(de) == ptr) goto process_de;
+		if (de->last) {
+			hpfs_brelse4(&qbh);
+			hpfs_error(s, "hpfs_count_dnodes: pointer to dnode %08x not found in dnode %08x, got here from %08x",
+				ptr, dno, odno);
+			return;
+		}
+		de = de_next_de(de);
+	}
+	next_de:
+	if (de->down) {
+		odno = dno;
+		dno = de_down_pointer(de);
+		hpfs_brelse4(&qbh);
+		goto go_down;
+	}
+	process_de:
+	if (!de->first && !de->last && de->directory && n_subdirs) (*n_subdirs)++;
+	if (!de->first && !de->last && n_items) (*n_items)++;
+	if ((de = de_next_de(de)) < dnode_end_de(dnode)) goto next_de;
+	ptr = dno;
+	dno = dnode->up;
+	if (dnode->root_dnode) {
+		hpfs_brelse4(&qbh);
+		return;
+	}
+	hpfs_brelse4(&qbh);
+	if (hpfs_sb(s)->sb_chk)
+		if (hpfs_stop_cycles(s, ptr, &d1, &d2, "hpfs_count_dnodes #2")) return;
+	odno = -1;
+	goto go_up;
+}
+
+static struct hpfs_dirent *map_nth_dirent(struct super_block *s, dnode_secno dno, int n,
+					  struct quad_buffer_head *qbh, struct dnode **dn)
+{
+	int i;
+	struct hpfs_dirent *de, *de_end;
+	struct dnode *dnode;
+	dnode = hpfs_map_dnode(s, dno, qbh);
+	if (!dnode) return NULL;
+	if (dn) *dn=dnode;
+	de = dnode_first_de(dnode);
+	de_end = dnode_end_de(dnode);
+	for (i = 1; de < de_end; i++, de = de_next_de(de)) {
+		if (i == n) {
+			return de;
+		}	
+		if (de->last) break;
+	}
+	hpfs_brelse4(qbh);
+	hpfs_error(s, "map_nth_dirent: n too high; dnode = %08x, requested %08x", dno, n);
+	return NULL;
+}
+
+dnode_secno hpfs_de_as_down_as_possible(struct super_block *s, dnode_secno dno)
+{
+	struct quad_buffer_head qbh;
+	dnode_secno d = dno;
+	dnode_secno up = 0;
+	struct hpfs_dirent *de;
+	int c1, c2 = 0;
+
+	again:
+	if (hpfs_sb(s)->sb_chk)
+		if (hpfs_stop_cycles(s, d, &c1, &c2, "hpfs_de_as_down_as_possible"))
+			return d;
+	if (!(de = map_nth_dirent(s, d, 1, &qbh, NULL))) return dno;
+	if (hpfs_sb(s)->sb_chk)
+		if (up && ((struct dnode *)qbh.data)->up != up)
+			hpfs_error(s, "hpfs_de_as_down_as_possible: bad up pointer; dnode %08x, down %08x points to %08x", up, d, ((struct dnode *)qbh.data)->up);
+	if (!de->down) {
+		hpfs_brelse4(&qbh);
+		return d;
+	}
+	up = d;
+	d = de_down_pointer(de);
+	hpfs_brelse4(&qbh);
+	goto again;
+}
+
+struct hpfs_dirent *map_pos_dirent(struct inode *inode, loff_t *posp,
+				   struct quad_buffer_head *qbh)
+{
+	loff_t pos;
+	unsigned c;
+	dnode_secno dno;
+	struct hpfs_dirent *de, *d;
+	struct hpfs_dirent *up_de;
+	struct hpfs_dirent *end_up_de;
+	struct dnode *dnode;
+	struct dnode *up_dnode;
+	struct quad_buffer_head qbh0;
+
+	pos = *posp;
+	dno = pos >> 6 << 2;
+	pos &= 077;
+	if (!(de = map_nth_dirent(inode->i_sb, dno, pos, qbh, &dnode)))
+		goto bail;
+
+	/* Going to the next dirent */
+	if ((d = de_next_de(de)) < dnode_end_de(dnode)) {
+		if (!(++*posp & 077)) {
+			hpfs_error(inode->i_sb, "map_pos_dirent: pos crossed dnode boundary; pos = %08x", *posp);
+			goto bail;
+		}
+		/* We're going down the tree */
+		if (d->down) {
+			*posp = ((loff_t) hpfs_de_as_down_as_possible(inode->i_sb, de_down_pointer(d)) << 4) + 1;
+		}
+	
+		return de;
+	}
+
+	/* Going up */
+	if (dnode->root_dnode) goto bail;
+
+	if (!(up_dnode = hpfs_map_dnode(inode->i_sb, dnode->up, &qbh0)))
+		goto bail;
+
+	end_up_de = dnode_end_de(up_dnode);
+	c = 0;
+	for (up_de = dnode_first_de(up_dnode); up_de < end_up_de;
+	     up_de = de_next_de(up_de)) {
+		if (!(++c & 077)) hpfs_error(inode->i_sb,
+			"map_pos_dirent: pos crossed dnode boundary; dnode = %08x", dnode->up);
+		if (up_de->down && de_down_pointer(up_de) == dno) {
+			*posp = ((loff_t) dnode->up << 4) + c;
+			hpfs_brelse4(&qbh0);
+			return de;
+		}
+	}
+	
+	hpfs_error(inode->i_sb, "map_pos_dirent: pointer to dnode %08x not found in parent dnode %08x",
+		dno, dnode->up);
+	hpfs_brelse4(&qbh0);
+	
+	bail:
+	*posp = 12;
+	return de;
+}
+
+/* Find a dirent in tree */
+
+struct hpfs_dirent *map_dirent(struct inode *inode, dnode_secno dno, char *name, unsigned len,
+			       dnode_secno *dd, struct quad_buffer_head *qbh)
+{
+	struct dnode *dnode;
+	struct hpfs_dirent *de;
+	struct hpfs_dirent *de_end;
+	int c1, c2 = 0;
+
+	if (!S_ISDIR(inode->i_mode)) hpfs_error(inode->i_sb, "map_dirent: not a directory\n");
+	again:
+	if (hpfs_sb(inode->i_sb)->sb_chk)
+		if (hpfs_stop_cycles(inode->i_sb, dno, &c1, &c2, "map_dirent")) return NULL;
+	if (!(dnode = hpfs_map_dnode(inode->i_sb, dno, qbh))) return NULL;
+	
+	de_end = dnode_end_de(dnode);
+	for (de = dnode_first_de(dnode); de < de_end; de = de_next_de(de)) {
+		int t = hpfs_compare_names(inode->i_sb, name, len, de->name, de->namelen, de->last);
+		if (!t) {
+			if (dd) *dd = dno;
+			return de;
+		}
+		if (t < 0) {
+			if (de->down) {
+				dno = de_down_pointer(de);
+				hpfs_brelse4(qbh);
+				goto again;
+			}
+		break;
+		}
+	}
+	hpfs_brelse4(qbh);
+	return NULL;
+}
+
+/*
+ * Remove empty directory. In normal cases it is only one dnode with two
+ * entries, but we must handle also such obscure cases when it's a tree
+ * of empty dnodes.
+ */
+
+void hpfs_remove_dtree(struct super_block *s, dnode_secno dno)
+{
+	struct quad_buffer_head qbh;
+	struct dnode *dnode;
+	struct hpfs_dirent *de;
+	dnode_secno d1, d2, rdno = dno;
+	while (1) {
+		if (!(dnode = hpfs_map_dnode(s, dno, &qbh))) return;
+		de = dnode_first_de(dnode);
+		if (de->last) {
+			if (de->down) d1 = de_down_pointer(de);
+			else goto error;
+			hpfs_brelse4(&qbh);
+			hpfs_free_dnode(s, dno);
+			dno = d1;
+		} else break;
+	}
+	if (!de->first) goto error;
+	d1 = de->down ? de_down_pointer(de) : 0;
+	de = de_next_de(de);
+	if (!de->last) goto error;
+	d2 = de->down ? de_down_pointer(de) : 0;
+	hpfs_brelse4(&qbh);
+	hpfs_free_dnode(s, dno);
+	do {
+		while (d1) {
+			if (!(dnode = hpfs_map_dnode(s, dno = d1, &qbh))) return;
+			de = dnode_first_de(dnode);
+			if (!de->last) goto error;
+			d1 = de->down ? de_down_pointer(de) : 0;
+			hpfs_brelse4(&qbh);
+			hpfs_free_dnode(s, dno);
+		}
+		d1 = d2;
+		d2 = 0;
+	} while (d1);
+	return;
+	error:
+	hpfs_brelse4(&qbh);
+	hpfs_free_dnode(s, dno);
+	hpfs_error(s, "directory %08x is corrupted or not empty", rdno);
+}
+
+/* 
+ * Find dirent for specified fnode. Use truncated 15-char name in fnode as
+ * a help for searching.
+ */
+
+struct hpfs_dirent *map_fnode_dirent(struct super_block *s, fnode_secno fno,
+				     struct fnode *f, struct quad_buffer_head *qbh)
+{
+	char *name1;
+	char *name2;
+	int name1len, name2len;
+	struct dnode *d;
+	dnode_secno dno, downd;
+	struct fnode *upf;
+	struct buffer_head *bh;
+	struct hpfs_dirent *de, *de_end;
+	int c;
+	int c1, c2 = 0;
+	int d1, d2 = 0;
+	name1 = f->name;
+	if (!(name2 = kmalloc(256, GFP_NOFS))) {
+		printk("HPFS: out of memory, can't map dirent\n");
+		return NULL;
+	}
+	if (f->len <= 15)
+		memcpy(name2, name1, name1len = name2len = f->len);
+	else {
+		memcpy(name2, name1, 15);
+		memset(name2 + 15, 0xff, 256 - 15);
+		/*name2[15] = 0xff;*/
+		name1len = 15; name2len = 256;
+	}
+	if (!(upf = hpfs_map_fnode(s, f->up, &bh))) {
+		kfree(name2);
+		return NULL;
+	}	
+	if (!upf->dirflag) {
+		brelse(bh);
+		hpfs_error(s, "fnode %08x has non-directory parent %08x", fno, f->up);
+		kfree(name2);
+		return NULL;
+	}
+	dno = upf->u.external[0].disk_secno;
+	brelse(bh);
+	go_down:
+	downd = 0;
+	go_up:
+	if (!(d = hpfs_map_dnode(s, dno, qbh))) {
+		kfree(name2);
+		return NULL;
+	}
+	de_end = dnode_end_de(d);
+	de = dnode_first_de(d);
+	if (downd) {
+		while (de < de_end) {
+			if (de->down) if (de_down_pointer(de) == downd) goto f;
+			de = de_next_de(de);
+		}
+		hpfs_error(s, "pointer to dnode %08x not found in dnode %08x", downd, dno);
+		hpfs_brelse4(qbh);
+		kfree(name2);
+		return NULL;
+	}
+	next_de:
+	if (de->fnode == fno) {
+		kfree(name2);
+		return de;
+	}
+	c = hpfs_compare_names(s, name1, name1len, de->name, de->namelen, de->last);
+	if (c < 0 && de->down) {
+		dno = de_down_pointer(de);
+		hpfs_brelse4(qbh);
+		if (hpfs_sb(s)->sb_chk)
+			if (hpfs_stop_cycles(s, dno, &c1, &c2, "map_fnode_dirent #1")) {
+			kfree(name2);
+			return NULL;
+		}
+		goto go_down;
+	}
+	f:
+	if (de->fnode == fno) {
+		kfree(name2);
+		return de;
+	}
+	c = hpfs_compare_names(s, name2, name2len, de->name, de->namelen, de->last);
+	if (c < 0 && !de->last) goto not_found;
+	if ((de = de_next_de(de)) < de_end) goto next_de;
+	if (d->root_dnode) goto not_found;
+	downd = dno;
+	dno = d->up;
+	hpfs_brelse4(qbh);
+	if (hpfs_sb(s)->sb_chk)
+		if (hpfs_stop_cycles(s, downd, &d1, &d2, "map_fnode_dirent #2")) {
+			kfree(name2);
+			return NULL;
+		}
+	goto go_up;
+	not_found:
+	hpfs_brelse4(qbh);
+	hpfs_error(s, "dirent for fnode %08x not found", fno);
+	kfree(name2);
+	return NULL;
+}
diff --git a/fs/hpfs/ea.c b/fs/hpfs/ea.c
new file mode 100644
index 0000000..66339dc
--- /dev/null
+++ b/fs/hpfs/ea.c
@@ -0,0 +1,363 @@
+/*
+ *  linux/fs/hpfs/ea.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  handling extended attributes
+ */
+
+#include "hpfs_fn.h"
+
+/* Remove external extended attributes. ano specifies whether a is a 
+   direct sector where eas starts or an anode */
+
+void hpfs_ea_ext_remove(struct super_block *s, secno a, int ano, unsigned len)
+{
+	unsigned pos = 0;
+	while (pos < len) {
+		char ex[4 + 255 + 1 + 8];
+		struct extended_attribute *ea = (struct extended_attribute *)ex;
+		if (pos + 4 > len) {
+			hpfs_error(s, "EAs don't end correctly, %s %08x, len %08x",
+				ano ? "anode" : "sectors", a, len);
+			return;
+		}
+		if (hpfs_ea_read(s, a, ano, pos, 4, ex)) return;
+		if (ea->indirect) {
+			if (ea->valuelen != 8) {
+				hpfs_error(s, "ea->indirect set while ea->valuelen!=8, %s %08x, pos %08x",
+					ano ? "anode" : "sectors", a, pos);
+				return;
+			}
+			if (hpfs_ea_read(s, a, ano, pos + 4, ea->namelen + 9, ex+4))
+				return;
+			hpfs_ea_remove(s, ea_sec(ea), ea->anode, ea_len(ea));
+		}
+		pos += ea->namelen + ea->valuelen + 5;
+	}
+	if (!ano) hpfs_free_sectors(s, a, (len+511) >> 9);
+	else {
+		struct buffer_head *bh;
+		struct anode *anode;
+		if ((anode = hpfs_map_anode(s, a, &bh))) {
+			hpfs_remove_btree(s, &anode->btree);
+			brelse(bh);
+			hpfs_free_sectors(s, a, 1);
+		}
+	}
+}
+
+static char *get_indirect_ea(struct super_block *s, int ano, secno a, int size)
+{
+	char *ret;
+	if (!(ret = kmalloc(size + 1, GFP_NOFS))) {
+		printk("HPFS: out of memory for EA\n");
+		return NULL;
+	}
+	if (hpfs_ea_read(s, a, ano, 0, size, ret)) {
+		kfree(ret);
+		return NULL;
+	}
+	ret[size] = 0;
+	return ret;
+}
+
+static void set_indirect_ea(struct super_block *s, int ano, secno a, char *data,
+			    int size)
+{
+	hpfs_ea_write(s, a, ano, 0, size, data);
+}
+
+/* Read an extended attribute named 'key' into the provided buffer */
+
+int hpfs_read_ea(struct super_block *s, struct fnode *fnode, char *key,
+		char *buf, int size)
+{
+	unsigned pos;
+	int ano, len;
+	secno a;
+	struct extended_attribute *ea;
+	struct extended_attribute *ea_end = fnode_end_ea(fnode);
+	for (ea = fnode_ea(fnode); ea < ea_end; ea = next_ea(ea))
+		if (!strcmp(ea->name, key)) {
+			if (ea->indirect)
+				goto indirect;
+			if (ea->valuelen >= size)
+				return -EINVAL;
+			memcpy(buf, ea_data(ea), ea->valuelen);
+			buf[ea->valuelen] = 0;
+			return 0;
+		}
+	a = fnode->ea_secno;
+	len = fnode->ea_size_l;
+	ano = fnode->ea_anode;
+	pos = 0;
+	while (pos < len) {
+		char ex[4 + 255 + 1 + 8];
+		ea = (struct extended_attribute *)ex;
+		if (pos + 4 > len) {
+			hpfs_error(s, "EAs don't end correctly, %s %08x, len %08x",
+				ano ? "anode" : "sectors", a, len);
+			return -EIO;
+		}
+		if (hpfs_ea_read(s, a, ano, pos, 4, ex)) return -EIO;
+		if (hpfs_ea_read(s, a, ano, pos + 4, ea->namelen + 1 + (ea->indirect ? 8 : 0), ex + 4))
+			return -EIO;
+		if (!strcmp(ea->name, key)) {
+			if (ea->indirect)
+				goto indirect;
+			if (ea->valuelen >= size)
+				return -EINVAL;
+			if (hpfs_ea_read(s, a, ano, pos + 4 + ea->namelen + 1, ea->valuelen, buf))
+				return -EIO;
+			buf[ea->valuelen] = 0;
+			return 0;
+		}
+		pos += ea->namelen + ea->valuelen + 5;
+	}
+	return -ENOENT;
+indirect:
+	if (ea_len(ea) >= size)
+		return -EINVAL;
+	if (hpfs_ea_read(s, ea_sec(ea), ea->anode, 0, ea_len(ea), buf))
+		return -EIO;
+	buf[ea_len(ea)] = 0;
+	return 0;
+}
+
+/* Read an extended attribute named 'key' */
+char *hpfs_get_ea(struct super_block *s, struct fnode *fnode, char *key, int *size)
+{
+	char *ret;
+	unsigned pos;
+	int ano, len;
+	secno a;
+	struct extended_attribute *ea;
+	struct extended_attribute *ea_end = fnode_end_ea(fnode);
+	for (ea = fnode_ea(fnode); ea < ea_end; ea = next_ea(ea))
+		if (!strcmp(ea->name, key)) {
+			if (ea->indirect)
+				return get_indirect_ea(s, ea->anode, ea_sec(ea), *size = ea_len(ea));
+			if (!(ret = kmalloc((*size = ea->valuelen) + 1, GFP_NOFS))) {
+				printk("HPFS: out of memory for EA\n");
+				return NULL;
+			}
+			memcpy(ret, ea_data(ea), ea->valuelen);
+			ret[ea->valuelen] = 0;
+			return ret;
+		}
+	a = fnode->ea_secno;
+	len = fnode->ea_size_l;
+	ano = fnode->ea_anode;
+	pos = 0;
+	while (pos < len) {
+		char ex[4 + 255 + 1 + 8];
+		ea = (struct extended_attribute *)ex;
+		if (pos + 4 > len) {
+			hpfs_error(s, "EAs don't end correctly, %s %08x, len %08x",
+				ano ? "anode" : "sectors", a, len);
+			return NULL;
+		}
+		if (hpfs_ea_read(s, a, ano, pos, 4, ex)) return NULL;
+		if (hpfs_ea_read(s, a, ano, pos + 4, ea->namelen + 1 + (ea->indirect ? 8 : 0), ex + 4))
+			return NULL;
+		if (!strcmp(ea->name, key)) {
+			if (ea->indirect)
+				return get_indirect_ea(s, ea->anode, ea_sec(ea), *size = ea_len(ea));
+			if (!(ret = kmalloc((*size = ea->valuelen) + 1, GFP_NOFS))) {
+				printk("HPFS: out of memory for EA\n");
+				return NULL;
+			}
+			if (hpfs_ea_read(s, a, ano, pos + 4 + ea->namelen + 1, ea->valuelen, ret)) {
+				kfree(ret);
+				return NULL;
+			}
+			ret[ea->valuelen] = 0;
+			return ret;
+		}
+		pos += ea->namelen + ea->valuelen + 5;
+	}
+	return NULL;
+}
+
+/* 
+ * Update or create extended attribute 'key' with value 'data'. Note that
+ * when this ea exists, it MUST have the same size as size of data.
+ * This driver can't change sizes of eas ('cause I just don't need it).
+ */
+
+void hpfs_set_ea(struct inode *inode, struct fnode *fnode, char *key, char *data, int size)
+{
+	fnode_secno fno = inode->i_ino;
+	struct super_block *s = inode->i_sb;
+	unsigned pos;
+	int ano, len;
+	secno a;
+	unsigned char h[4];
+	struct extended_attribute *ea;
+	struct extended_attribute *ea_end = fnode_end_ea(fnode);
+	for (ea = fnode_ea(fnode); ea < ea_end; ea = next_ea(ea))
+		if (!strcmp(ea->name, key)) {
+			if (ea->indirect) {
+				if (ea_len(ea) == size)
+					set_indirect_ea(s, ea->anode, ea_sec(ea), data, size);
+			} else if (ea->valuelen == size) {
+				memcpy(ea_data(ea), data, size);
+			}
+			return;
+		}
+	a = fnode->ea_secno;
+	len = fnode->ea_size_l;
+	ano = fnode->ea_anode;
+	pos = 0;
+	while (pos < len) {
+		char ex[4 + 255 + 1 + 8];
+		ea = (struct extended_attribute *)ex;
+		if (pos + 4 > len) {
+			hpfs_error(s, "EAs don't end correctly, %s %08x, len %08x",
+				ano ? "anode" : "sectors", a, len);
+			return;
+		}
+		if (hpfs_ea_read(s, a, ano, pos, 4, ex)) return;
+		if (hpfs_ea_read(s, a, ano, pos + 4, ea->namelen + 1 + (ea->indirect ? 8 : 0), ex + 4))
+			return;
+		if (!strcmp(ea->name, key)) {
+			if (ea->indirect) {
+				if (ea_len(ea) == size)
+					set_indirect_ea(s, ea->anode, ea_sec(ea), data, size);
+			}
+			else {
+				if (ea->valuelen == size)
+					hpfs_ea_write(s, a, ano, pos + 4 + ea->namelen + 1, size, data);
+			}
+			return;
+		}
+		pos += ea->namelen + ea->valuelen + 5;
+	}
+	if (!fnode->ea_offs) {
+		/*if (fnode->ea_size_s) {
+			hpfs_error(s, "fnode %08x: ea_size_s == %03x, ea_offs == 0",
+				inode->i_ino, fnode->ea_size_s);
+			return;
+		}*/
+		fnode->ea_offs = 0xc4;
+	}
+	if (fnode->ea_offs < 0xc4 || fnode->ea_offs + fnode->acl_size_s + fnode->ea_size_s > 0x200) {
+		hpfs_error(s, "fnode %08x: ea_offs == %03x, ea_size_s == %03x",
+			inode->i_ino, fnode->ea_offs, fnode->ea_size_s);
+		return;
+	}
+	if ((fnode->ea_size_s || !fnode->ea_size_l) &&
+	     fnode->ea_offs + fnode->acl_size_s + fnode->ea_size_s + strlen(key) + size + 5 <= 0x200) {
+		ea = fnode_end_ea(fnode);
+		*(char *)ea = 0;
+		ea->namelen = strlen(key);
+		ea->valuelen = size;
+		strcpy(ea->name, key);
+		memcpy(ea_data(ea), data, size);
+		fnode->ea_size_s += strlen(key) + size + 5;
+		goto ret;
+	}
+	/* Most the code here is 99.9993422% unused. I hope there are no bugs.
+	   But what .. HPFS.IFS has also bugs in ea management. */
+	if (fnode->ea_size_s && !fnode->ea_size_l) {
+		secno n;
+		struct buffer_head *bh;
+		char *data;
+		if (!(n = hpfs_alloc_sector(s, fno, 1, 0, 1))) return;
+		if (!(data = hpfs_get_sector(s, n, &bh))) {
+			hpfs_free_sectors(s, n, 1);
+			return;
+		}
+		memcpy(data, fnode_ea(fnode), fnode->ea_size_s);
+		fnode->ea_size_l = fnode->ea_size_s;
+		fnode->ea_size_s = 0;
+		fnode->ea_secno = n;
+		fnode->ea_anode = 0;
+		mark_buffer_dirty(bh);
+		brelse(bh);
+	}
+	pos = fnode->ea_size_l + 5 + strlen(key) + size;
+	len = (fnode->ea_size_l + 511) >> 9;
+	if (pos >= 30000) goto bail;
+	while (((pos + 511) >> 9) > len) {
+		if (!len) {
+			if (!(fnode->ea_secno = hpfs_alloc_sector(s, fno, 1, 0, 1)))
+				goto bail;
+			fnode->ea_anode = 0;
+			len++;
+		} else if (!fnode->ea_anode) {
+			if (hpfs_alloc_if_possible(s, fnode->ea_secno + len)) {
+				len++;
+			} else {
+				/* Aargh... don't know how to create ea anodes :-( */
+				/*struct buffer_head *bh;
+				struct anode *anode;
+				anode_secno a_s;
+				if (!(anode = hpfs_alloc_anode(s, fno, &a_s, &bh)))
+					goto bail;
+				anode->up = fno;
+				anode->btree.fnode_parent = 1;
+				anode->btree.n_free_nodes--;
+				anode->btree.n_used_nodes++;
+				anode->btree.first_free += 12;
+				anode->u.external[0].disk_secno = fnode->ea_secno;
+				anode->u.external[0].file_secno = 0;
+				anode->u.external[0].length = len;
+				mark_buffer_dirty(bh);
+				brelse(bh);
+				fnode->ea_anode = 1;
+				fnode->ea_secno = a_s;*/
+				secno new_sec;
+				int i;
+				if (!(new_sec = hpfs_alloc_sector(s, fno, 1, 1 - ((pos + 511) >> 9), 1)))
+					goto bail;
+				for (i = 0; i < len; i++) {
+					struct buffer_head *bh1, *bh2;
+					void *b1, *b2;
+					if (!(b1 = hpfs_map_sector(s, fnode->ea_secno + i, &bh1, len - i - 1))) {
+						hpfs_free_sectors(s, new_sec, (pos + 511) >> 9);
+						goto bail;
+					}
+					if (!(b2 = hpfs_get_sector(s, new_sec + i, &bh2))) {
+						brelse(bh1);
+						hpfs_free_sectors(s, new_sec, (pos + 511) >> 9);
+						goto bail;
+					}
+					memcpy(b2, b1, 512);
+					brelse(bh1);
+					mark_buffer_dirty(bh2);
+					brelse(bh2);
+				}
+				hpfs_free_sectors(s, fnode->ea_secno, len);
+				fnode->ea_secno = new_sec;
+				len = (pos + 511) >> 9;
+			}
+		}
+		if (fnode->ea_anode) {
+			if (hpfs_add_sector_to_btree(s, fnode->ea_secno,
+						     0, len) != -1) {
+				len++;
+			} else {
+				goto bail;
+			}
+		}
+	}
+	h[0] = 0;
+	h[1] = strlen(key);
+	h[2] = size & 0xff;
+	h[3] = size >> 8;
+	if (hpfs_ea_write(s, fnode->ea_secno, fnode->ea_anode, fnode->ea_size_l, 4, h)) goto bail;
+	if (hpfs_ea_write(s, fnode->ea_secno, fnode->ea_anode, fnode->ea_size_l + 4, h[1] + 1, key)) goto bail;
+	if (hpfs_ea_write(s, fnode->ea_secno, fnode->ea_anode, fnode->ea_size_l + 5 + h[1], size, data)) goto bail;
+	fnode->ea_size_l = pos;
+	ret:
+	hpfs_i(inode)->i_ea_size += 5 + strlen(key) + size;
+	return;
+	bail:
+	if (fnode->ea_secno)
+		if (fnode->ea_anode) hpfs_truncate_btree(s, fnode->ea_secno, 1, (fnode->ea_size_l + 511) >> 9);
+		else hpfs_free_sectors(s, fnode->ea_secno + ((fnode->ea_size_l + 511) >> 9), len - ((fnode->ea_size_l + 511) >> 9));
+	else fnode->ea_secno = fnode->ea_size_l = 0;
+}
+	
diff --git a/fs/hpfs/file.c b/fs/hpfs/file.c
new file mode 100644
index 0000000..ab144da
--- /dev/null
+++ b/fs/hpfs/file.c
@@ -0,0 +1,140 @@
+/*
+ *  linux/fs/hpfs/file.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  file VFS functions
+ */
+
+#include "hpfs_fn.h"
+
+#define BLOCKS(size) (((size) + 511) >> 9)
+
+static int hpfs_file_release(struct inode *inode, struct file *file)
+{
+	lock_kernel();
+	hpfs_write_if_changed(inode);
+	unlock_kernel();
+	return 0;
+}
+
+int hpfs_file_fsync(struct file *file, struct dentry *dentry, int datasync)
+{
+	/*return file_fsync(file, dentry);*/
+	return 0; /* Don't fsync :-) */
+}
+
+/*
+ * generic_file_read often calls bmap with non-existing sector,
+ * so we must ignore such errors.
+ */
+
+static secno hpfs_bmap(struct inode *inode, unsigned file_secno)
+{
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(inode);
+	unsigned n, disk_secno;
+	struct fnode *fnode;
+	struct buffer_head *bh;
+	if (BLOCKS(hpfs_i(inode)->mmu_private) <= file_secno) return 0;
+	n = file_secno - hpfs_inode->i_file_sec;
+	if (n < hpfs_inode->i_n_secs) return hpfs_inode->i_disk_sec + n;
+	if (!(fnode = hpfs_map_fnode(inode->i_sb, inode->i_ino, &bh))) return 0;
+	disk_secno = hpfs_bplus_lookup(inode->i_sb, inode, &fnode->btree, file_secno, bh);
+	if (disk_secno == -1) return 0;
+	if (hpfs_chk_sectors(inode->i_sb, disk_secno, 1, "bmap")) return 0;
+	return disk_secno;
+}
+
+static void hpfs_truncate(struct inode *i)
+{
+	if (IS_IMMUTABLE(i)) return /*-EPERM*/;
+	lock_kernel();
+	hpfs_i(i)->i_n_secs = 0;
+	i->i_blocks = 1 + ((i->i_size + 511) >> 9);
+	hpfs_i(i)->mmu_private = i->i_size;
+	hpfs_truncate_btree(i->i_sb, i->i_ino, 1, ((i->i_size + 511) >> 9));
+	hpfs_write_inode(i);
+	hpfs_i(i)->i_n_secs = 0;
+	unlock_kernel();
+}
+
+static int hpfs_get_block(struct inode *inode, sector_t iblock, struct buffer_head *bh_result, int create)
+{
+	secno s;
+	s = hpfs_bmap(inode, iblock);
+	if (s) {
+		map_bh(bh_result, inode->i_sb, s);
+		return 0;
+	}
+	if (!create) return 0;
+	if (iblock<<9 != hpfs_i(inode)->mmu_private) {
+		BUG();
+		return -EIO;
+	}
+	if ((s = hpfs_add_sector_to_btree(inode->i_sb, inode->i_ino, 1, inode->i_blocks - 1)) == -1) {
+		hpfs_truncate_btree(inode->i_sb, inode->i_ino, 1, inode->i_blocks - 1);
+		return -ENOSPC;
+	}
+	inode->i_blocks++;
+	hpfs_i(inode)->mmu_private += 512;
+	set_buffer_new(bh_result);
+	map_bh(bh_result, inode->i_sb, s);
+	return 0;
+}
+
+static int hpfs_writepage(struct page *page, struct writeback_control *wbc)
+{
+	return block_write_full_page(page,hpfs_get_block, wbc);
+}
+static int hpfs_readpage(struct file *file, struct page *page)
+{
+	return block_read_full_page(page,hpfs_get_block);
+}
+static int hpfs_prepare_write(struct file *file, struct page *page, unsigned from, unsigned to)
+{
+	return cont_prepare_write(page,from,to,hpfs_get_block,
+		&hpfs_i(page->mapping->host)->mmu_private);
+}
+static sector_t _hpfs_bmap(struct address_space *mapping, sector_t block)
+{
+	return generic_block_bmap(mapping,block,hpfs_get_block);
+}
+struct address_space_operations hpfs_aops = {
+	.readpage = hpfs_readpage,
+	.writepage = hpfs_writepage,
+	.sync_page = block_sync_page,
+	.prepare_write = hpfs_prepare_write,
+	.commit_write = generic_commit_write,
+	.bmap = _hpfs_bmap
+};
+
+static ssize_t hpfs_file_write(struct file *file, const char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	ssize_t retval;
+
+	retval = generic_file_write(file, buf, count, ppos);
+	if (retval > 0) {
+		struct inode *inode = file->f_dentry->d_inode;
+		inode->i_mtime = CURRENT_TIME_SEC;
+		hpfs_i(inode)->i_dirty = 1;
+	}
+	return retval;
+}
+
+struct file_operations hpfs_file_ops =
+{
+	.llseek		= generic_file_llseek,
+	.read		= generic_file_read,
+	.write		= hpfs_file_write,
+	.mmap		= generic_file_mmap,
+	.release	= hpfs_file_release,
+	.fsync		= hpfs_file_fsync,
+	.sendfile	= generic_file_sendfile,
+};
+
+struct inode_operations hpfs_file_iops =
+{
+	.truncate	= hpfs_truncate,
+	.setattr	= hpfs_notify_change,
+};
diff --git a/fs/hpfs/hpfs.h b/fs/hpfs/hpfs.h
new file mode 100644
index 0000000..0e84c73
--- /dev/null
+++ b/fs/hpfs/hpfs.h
@@ -0,0 +1,493 @@
+/*
+ *  linux/fs/hpfs/hpfs.h
+ *
+ *  HPFS structures by Chris Smith, 1993
+ *
+ *  a little bit modified by Mikulas Patocka, 1998-1999
+ */
+
+/* The paper
+
+     Duncan, Roy
+     Design goals and implementation of the new High Performance File System
+     Microsoft Systems Journal  Sept 1989  v4 n5 p1(13)
+
+   describes what HPFS looked like when it was new, and it is the source
+   of most of the information given here.  The rest is conjecture.
+
+   For definitive information on the Duncan paper, see it, not this file.
+   For definitive information on HPFS, ask somebody else -- this is guesswork.
+   There are certain to be many mistakes. */
+
+/* Notation */
+
+typedef unsigned secno;			/* sector number, partition relative */
+
+typedef secno dnode_secno;		/* sector number of a dnode */
+typedef secno fnode_secno;		/* sector number of an fnode */
+typedef secno anode_secno;		/* sector number of an anode */
+
+typedef u32 time32_t;		/* 32-bit time_t type */
+
+/* sector 0 */
+
+/* The boot block is very like a FAT boot block, except that the
+   29h signature byte is 28h instead, and the ID string is "HPFS". */
+
+#define BB_MAGIC 0xaa55
+
+struct hpfs_boot_block
+{
+  unsigned char jmp[3];
+  unsigned char oem_id[8];
+  unsigned char bytes_per_sector[2];	/* 512 */
+  unsigned char sectors_per_cluster;
+  unsigned char n_reserved_sectors[2];
+  unsigned char n_fats;
+  unsigned char n_rootdir_entries[2];
+  unsigned char n_sectors_s[2];
+  unsigned char media_byte;
+  unsigned short sectors_per_fat;
+  unsigned short sectors_per_track;
+  unsigned short heads_per_cyl;
+  unsigned int n_hidden_sectors;
+  unsigned int n_sectors_l;		/* size of partition */
+  unsigned char drive_number;
+  unsigned char mbz;
+  unsigned char sig_28h;		/* 28h */
+  unsigned char vol_serno[4];
+  unsigned char vol_label[11];
+  unsigned char sig_hpfs[8];		/* "HPFS    " */
+  unsigned char pad[448];
+  unsigned short magic;			/* aa55 */
+};
+
+
+/* sector 16 */
+
+/* The super block has the pointer to the root directory. */
+
+#define SB_MAGIC 0xf995e849
+
+struct hpfs_super_block
+{
+  unsigned magic;			/* f995 e849 */
+  unsigned magic1;			/* fa53 e9c5, more magic? */
+  /*unsigned huh202;*/			/* ?? 202 = N. of B. in 1.00390625 S.*/
+  char version;				/* version of a filesystem  usually 2 */
+  char funcversion;			/* functional version - oldest version
+  					   of filesystem that can understand
+					   this disk */
+  unsigned short int zero;		/* 0 */
+  fnode_secno root;			/* fnode of root directory */
+  secno n_sectors;			/* size of filesystem */
+  unsigned n_badblocks;			/* number of bad blocks */
+  secno bitmaps;			/* pointers to free space bit maps */
+  unsigned zero1;			/* 0 */
+  secno badblocks;			/* bad block list */
+  unsigned zero3;			/* 0 */
+  time32_t last_chkdsk;			/* date last checked, 0 if never */
+  /*unsigned zero4;*/			/* 0 */
+  time32_t last_optimize;			/* date last optimized, 0 if never */
+  secno n_dir_band;			/* number of sectors in dir band */
+  secno dir_band_start;			/* first sector in dir band */
+  secno dir_band_end;			/* last sector in dir band */
+  secno dir_band_bitmap;		/* free space map, 1 dnode per bit */
+  char volume_name[32];			/* not used */
+  secno user_id_table;			/* 8 preallocated sectors - user id */
+  unsigned zero6[103];			/* 0 */
+};
+
+
+/* sector 17 */
+
+/* The spare block has pointers to spare sectors.  */
+
+#define SP_MAGIC 0xf9911849
+
+struct hpfs_spare_block
+{
+  unsigned magic;			/* f991 1849 */
+  unsigned magic1;			/* fa52 29c5, more magic? */
+
+  unsigned dirty: 1;			/* 0 clean, 1 "improperly stopped" */
+  /*unsigned flag1234: 4;*/		/* unknown flags */
+  unsigned sparedir_used: 1;		/* spare dirblks used */
+  unsigned hotfixes_used: 1;		/* hotfixes used */
+  unsigned bad_sector: 1;		/* bad sector, corrupted disk (???) */
+  unsigned bad_bitmap: 1;		/* bad bitmap */
+  unsigned fast: 1;			/* partition was fast formatted */
+  unsigned old_wrote: 1;		/* old version wrote to partion */
+  unsigned old_wrote_1: 1;		/* old version wrote to partion (?) */
+  unsigned install_dasd_limits: 1;	/* HPFS386 flags */
+  unsigned resynch_dasd_limits: 1;
+  unsigned dasd_limits_operational: 1;
+  unsigned multimedia_active: 1;
+  unsigned dce_acls_active: 1;
+  unsigned dasd_limits_dirty: 1;
+  unsigned flag67: 2;
+  unsigned char mm_contlgulty;
+  unsigned char unused;
+
+  secno hotfix_map;			/* info about remapped bad sectors */
+  unsigned n_spares_used;		/* number of hotfixes */
+  unsigned n_spares;			/* number of spares in hotfix map */
+  unsigned n_dnode_spares_free;		/* spare dnodes unused */
+  unsigned n_dnode_spares;		/* length of spare_dnodes[] list,
+					   follows in this block*/
+  secno code_page_dir;			/* code page directory block */
+  unsigned n_code_pages;		/* number of code pages */
+  /*unsigned large_numbers[2];*/	/* ?? */
+  unsigned super_crc;			/* on HPFS386 and LAN Server this is
+  					   checksum of superblock, on normal
+					   OS/2 unused */
+  unsigned spare_crc;			/* on HPFS386 checksum of spareblock */
+  unsigned zero1[15];			/* unused */
+  dnode_secno spare_dnodes[100];	/* emergency free dnode list */
+  unsigned zero2[1];			/* room for more? */
+};
+
+/* The bad block list is 4 sectors long.  The first word must be zero,
+   the remaining words give n_badblocks bad block numbers.
+   I bet you can see it coming... */
+
+#define BAD_MAGIC 0
+       
+/* The hotfix map is 4 sectors long.  It looks like
+
+       secno from[n_spares];
+       secno to[n_spares];
+
+   The to[] list is initialized to point to n_spares preallocated empty
+   sectors.  The from[] list contains the sector numbers of bad blocks
+   which have been remapped to corresponding sectors in the to[] list.
+   n_spares_used gives the length of the from[] list. */
+
+
+/* Sectors 18 and 19 are preallocated and unused.
+   Maybe they're spares for 16 and 17, but simple substitution fails. */
+
+
+/* The code page info pointed to by the spare block consists of an index
+   block and blocks containing uppercasing tables.  I don't know what
+   these are for (CHKDSK, maybe?) -- OS/2 does not seem to use them
+   itself.  Linux doesn't use them either. */
+
+/* block pointed to by spareblock->code_page_dir */
+
+#define CP_DIR_MAGIC 0x494521f7
+
+struct code_page_directory
+{
+  unsigned magic;			/* 4945 21f7 */
+  unsigned n_code_pages;		/* number of pointers following */
+  unsigned zero1[2];
+  struct {
+    unsigned short ix;			/* index */
+    unsigned short code_page_number;	/* code page number */
+    unsigned bounds;			/* matches corresponding word
+					   in data block */
+    secno code_page_data;		/* sector number of a code_page_data
+					   containing c.p. array */
+    unsigned short index;		/* index in c.p. array in that sector*/
+    unsigned short unknown;		/* some unknown value; usually 0;
+    					   2 in Japanese version */
+  } array[31];				/* unknown length */
+};
+
+/* blocks pointed to by code_page_directory */
+
+#define CP_DATA_MAGIC 0x894521f7
+
+struct code_page_data
+{
+  unsigned magic;			/* 8945 21f7 */
+  unsigned n_used;			/* # elements used in c_p_data[] */
+  unsigned bounds[3];			/* looks a bit like
+					     (beg1,end1), (beg2,end2)
+					   one byte each */
+  unsigned short offs[3];		/* offsets from start of sector
+					   to start of c_p_data[ix] */
+  struct {
+    unsigned short ix;			/* index */
+    unsigned short code_page_number;	/* code page number */
+    unsigned short unknown;		/* the same as in cp directory */
+    unsigned char map[128];		/* upcase table for chars 80..ff */
+    unsigned short zero2;
+  } code_page[3];
+  unsigned char incognita[78];
+};
+
+
+/* Free space bitmaps are 4 sectors long, which is 16384 bits.
+   16384 sectors is 8 meg, and each 8 meg band has a 4-sector bitmap.
+   Bit order in the maps is little-endian.  0 means taken, 1 means free.
+
+   Bit map sectors are marked allocated in the bit maps, and so are sectors 
+   off the end of the partition.
+
+   Band 0 is sectors 0-3fff, its map is in sectors 18-1b.
+   Band 1 is 4000-7fff, its map is in 7ffc-7fff.
+   Band 2 is 8000-ffff, its map is in 8000-8003.
+   The remaining bands have maps in their first (even) or last (odd) 4 sectors
+     -- if the last, partial, band is odd its map is in its last 4 sectors.
+
+   The bitmap locations are given in a table pointed to by the super block.
+   No doubt they aren't constrained to be at 18, 7ffc, 8000, ...; that is
+   just where they usually are.
+
+   The "directory band" is a bunch of sectors preallocated for dnodes.
+   It has a 4-sector free space bitmap of its own.  Each bit in the map
+   corresponds to one 4-sector dnode, bit 0 of the map corresponding to
+   the first 4 sectors of the directory band.  The entire band is marked
+   allocated in the main bitmap.   The super block gives the locations
+   of the directory band and its bitmap.  ("band" doesn't mean it is
+   8 meg long; it isn't.)  */
+
+
+/* dnode: directory.  4 sectors long */
+
+/* A directory is a tree of dnodes.  The fnode for a directory
+   contains one pointer, to the root dnode of the tree.  The fnode
+   never moves, the dnodes do the B-tree thing, splitting and merging
+   as files are added and removed.  */
+
+#define DNODE_MAGIC   0x77e40aae
+
+struct dnode {
+  unsigned magic;			/* 77e4 0aae */
+  unsigned first_free;			/* offset from start of dnode to
+					   first free dir entry */
+  unsigned root_dnode:1;		/* Is it root dnode? */
+  unsigned increment_me:31;		/* some kind of activity counter?
+					   Neither HPFS.IFS nor CHKDSK cares
+					   if you change this word */
+  secno up;				/* (root dnode) directory's fnode
+					   (nonroot) parent dnode */
+  dnode_secno self;			/* pointer to this dnode */
+  unsigned char dirent[2028];		/* one or more dirents */
+};
+
+struct hpfs_dirent {
+  unsigned short length;		/* offset to next dirent */
+  unsigned first: 1;			/* set on phony ^A^A (".") entry */
+  unsigned has_acl: 1;
+  unsigned down: 1;			/* down pointer present (after name) */
+  unsigned last: 1;			/* set on phony \377 entry */
+  unsigned has_ea: 1;			/* entry has EA */
+  unsigned has_xtd_perm: 1;		/* has extended perm list (???) */
+  unsigned has_explicit_acl: 1;
+  unsigned has_needea: 1;		/* ?? some EA has NEEDEA set
+					   I have no idea why this is
+					   interesting in a dir entry */
+  unsigned read_only: 1;		/* dos attrib */
+  unsigned hidden: 1;			/* dos attrib */
+  unsigned system: 1;			/* dos attrib */
+  unsigned flag11: 1;			/* would be volume label dos attrib */
+  unsigned directory: 1;		/* dos attrib */
+  unsigned archive: 1;			/* dos attrib */
+  unsigned not_8x3: 1;			/* name is not 8.3 */
+  unsigned flag15: 1;
+  fnode_secno fnode;			/* fnode giving allocation info */
+  time32_t write_date;			/* mtime */
+  unsigned file_size;			/* file length, bytes */
+  time32_t read_date;			/* atime */
+  time32_t creation_date;			/* ctime */
+  unsigned ea_size;			/* total EA length, bytes */
+  unsigned char no_of_acls : 3;		/* number of ACL's */
+  unsigned char reserver : 5;
+  unsigned char ix;			/* code page index (of filename), see
+					   struct code_page_data */
+  unsigned char namelen, name[1];	/* file name */
+  /* dnode_secno down;	  btree down pointer, if present,
+     			  follows name on next word boundary, or maybe it
+			  precedes next dirent, which is on a word boundary. */
+};
+
+
+/* B+ tree: allocation info in fnodes and anodes */
+
+/* dnodes point to fnodes which are responsible for listing the sectors
+   assigned to the file.  This is done with trees of (length,address)
+   pairs.  (Actually triples, of (length, file-address, disk-address)
+   which can represent holes.  Find out if HPFS does that.)
+   At any rate, fnodes contain a small tree; if subtrees are needed
+   they occupy essentially a full block in anodes.  A leaf-level tree node
+   has 3-word entries giving sector runs, a non-leaf node has 2-word
+   entries giving subtree pointers.  A flag in the header says which. */
+
+struct bplus_leaf_node
+{
+  unsigned file_secno;			/* first file sector in extent */
+  unsigned length;			/* length, sectors */
+  secno disk_secno;			/* first corresponding disk sector */
+};
+
+struct bplus_internal_node
+{
+  unsigned file_secno;			/* subtree maps sectors < this  */
+  anode_secno down;			/* pointer to subtree */
+};
+
+struct bplus_header
+{
+  unsigned hbff: 1;	/* high bit of first free entry offset */
+  unsigned flag1: 1;
+  unsigned flag2: 1;
+  unsigned flag3: 1;
+  unsigned flag4: 1;
+  unsigned fnode_parent: 1;		/* ? we're pointed to by an fnode,
+					   the data btree or some ea or the
+					   main ea bootage pointer ea_secno */
+					/* also can get set in fnodes, which
+					   may be a chkdsk glitch or may mean
+					   this bit is irrelevant in fnodes,
+					   or this interpretation is all wet */
+  unsigned binary_search: 1;		/* suggest binary search (unused) */
+  unsigned internal: 1;			/* 1 -> (internal) tree of anodes
+					   0 -> (leaf) list of extents */
+  unsigned char fill[3];
+  unsigned char n_free_nodes;		/* free nodes in following array */
+  unsigned char n_used_nodes;		/* used nodes in following array */
+  unsigned short first_free;		/* offset from start of header to
+					   first free node in array */
+  union {
+    struct bplus_internal_node internal[0]; /* (internal) 2-word entries giving
+					       subtree pointers */
+    struct bplus_leaf_node external[0];	    /* (external) 3-word entries giving
+					       sector runs */
+  } u;
+};
+
+/* fnode: root of allocation b+ tree, and EA's */
+
+/* Every file and every directory has one fnode, pointed to by the directory
+   entry and pointing to the file's sectors or directory's root dnode.  EA's
+   are also stored here, and there are said to be ACL's somewhere here too. */
+
+#define FNODE_MAGIC 0xf7e40aae
+
+struct fnode
+{
+  unsigned magic;			/* f7e4 0aae */
+  unsigned zero1[2];			/* read history */
+  unsigned char len, name[15];		/* true length, truncated name */
+  fnode_secno up;			/* pointer to file's directory fnode */
+  /*unsigned zero2[3];*/
+  secno acl_size_l;
+  secno acl_secno;
+  unsigned short acl_size_s;
+  char acl_anode;
+  char zero2;				/* history bit count */
+  unsigned ea_size_l;			/* length of disk-resident ea's */
+  secno ea_secno;			/* first sector of disk-resident ea's*/
+  unsigned short ea_size_s;		/* length of fnode-resident ea's */
+
+  unsigned flag0: 1;
+  unsigned ea_anode: 1;			/* 1 -> ea_secno is an anode */
+  unsigned flag2: 1;
+  unsigned flag3: 1;
+  unsigned flag4: 1;
+  unsigned flag5: 1;
+  unsigned flag6: 1;
+  unsigned flag7: 1;
+  unsigned dirflag: 1;			/* 1 -> directory.  first & only extent
+					   points to dnode. */
+  unsigned flag9: 1;
+  unsigned flag10: 1;
+  unsigned flag11: 1;
+  unsigned flag12: 1;
+  unsigned flag13: 1;
+  unsigned flag14: 1;
+  unsigned flag15: 1;
+
+  struct bplus_header btree;		/* b+ tree, 8 extents or 12 subtrees */
+  union {
+    struct bplus_leaf_node external[8];
+    struct bplus_internal_node internal[12];
+  } u;
+
+  unsigned file_size;			/* file length, bytes */
+  unsigned n_needea;			/* number of EA's with NEEDEA set */
+  char user_id[16];			/* unused */
+  unsigned short ea_offs;		/* offset from start of fnode
+					   to first fnode-resident ea */
+  char dasd_limit_treshhold;
+  char dasd_limit_delta;
+  unsigned dasd_limit;
+  unsigned dasd_usage;
+  /*unsigned zero5[2];*/
+  unsigned char ea[316];		/* zero or more EA's, packed together
+					   with no alignment padding.
+					   (Do not use this name, get here
+					   via fnode + ea_offs. I think.) */
+};
+
+
+/* anode: 99.44% pure allocation tree */
+
+#define ANODE_MAGIC 0x37e40aae
+
+struct anode
+{
+  unsigned magic;			/* 37e4 0aae */
+  anode_secno self;			/* pointer to this anode */
+  secno up;				/* parent anode or fnode */
+
+  struct bplus_header btree;		/* b+tree, 40 extents or 60 subtrees */
+  union {
+    struct bplus_leaf_node external[40];
+    struct bplus_internal_node internal[60];
+  } u;
+
+  unsigned fill[3];			/* unused */
+};
+
+
+/* extended attributes.
+
+   A file's EA info is stored as a list of (name,value) pairs.  It is
+   usually in the fnode, but (if it's large) it is moved to a single
+   sector run outside the fnode, or to multiple runs with an anode tree
+   that points to them.
+
+   The value of a single EA is stored along with the name, or (if large)
+   it is moved to a single sector run, or multiple runs pointed to by an
+   anode tree, pointed to by the value field of the (name,value) pair.
+
+   Flags in the EA tell whether the value is immediate, in a single sector
+   run, or in multiple runs.  Flags in the fnode tell whether the EA list
+   is immediate, in a single run, or in multiple runs. */
+
+struct extended_attribute
+{
+  unsigned indirect: 1;			/* 1 -> value gives sector number
+					   where real value starts */
+  unsigned anode: 1;			/* 1 -> sector is an anode
+					   that points to fragmented value */
+  unsigned flag2: 1;
+  unsigned flag3: 1;
+  unsigned flag4: 1;
+  unsigned flag5: 1;
+  unsigned flag6: 1;
+  unsigned needea: 1;			/* required ea */
+  unsigned char namelen;		/* length of name, bytes */
+  unsigned short valuelen;		/* length of value, bytes */
+  unsigned char name[0];
+  /*
+    unsigned char name[namelen];	ascii attrib name
+    unsigned char nul;			terminating '\0', not counted
+    unsigned char value[valuelen];	value, arbitrary
+      if this.indirect, valuelen is 8 and the value is
+        unsigned length;		real length of value, bytes
+        secno secno;			sector address where it starts
+      if this.anode, the above sector number is the root of an anode tree
+        which points to the value.
+  */
+};
+
+/*
+   Local Variables:
+   comment-column: 40
+   End:
+*/
diff --git a/fs/hpfs/hpfs_fn.h b/fs/hpfs/hpfs_fn.h
new file mode 100644
index 0000000..6628c3b
--- /dev/null
+++ b/fs/hpfs/hpfs_fn.h
@@ -0,0 +1,338 @@
+/*
+ *  linux/fs/hpfs/hpfs_fn.h
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  function headers
+ */
+
+//#define DBG
+//#define DEBUG_LOCKS
+
+#include <linux/pagemap.h>
+#include <linux/buffer_head.h>
+#include <linux/hpfs_fs.h>
+#include <linux/slab.h>
+#include <linux/smp_lock.h>
+
+#include "hpfs.h"
+
+#define EIOERROR  EIO
+#define EFSERROR  EPERM
+#define EMEMERROR ENOMEM
+
+#define ANODE_ALLOC_FWD	512
+#define FNODE_ALLOC_FWD	0
+#define ALLOC_FWD_MIN	16
+#define ALLOC_FWD_MAX	128
+#define ALLOC_M		1
+#define FNODE_RD_AHEAD	16
+#define ANODE_RD_AHEAD	16
+#define DNODE_RD_AHEAD	4
+
+#define FREE_DNODES_ADD	58
+#define FREE_DNODES_DEL	29
+
+#define CHKCOND(x,y) if (!(x)) printk y
+
+#ifdef DBG
+#define PRINTK(x) printk x
+#else
+#undef PRINTK
+#define PRINTK(x)
+#endif
+
+struct hpfs_inode_info {
+	loff_t mmu_private;
+	ino_t i_parent_dir;	/* (directories) gives fnode of parent dir */
+	unsigned i_dno;		/* (directories) root dnode */
+	unsigned i_dpos;	/* (directories) temp for readdir */
+	unsigned i_dsubdno;	/* (directories) temp for readdir */
+	unsigned i_file_sec;	/* (files) minimalist cache of alloc info */
+	unsigned i_disk_sec;	/* (files) minimalist cache of alloc info */
+	unsigned i_n_secs;	/* (files) minimalist cache of alloc info */
+	unsigned i_ea_size;	/* size of extended attributes */
+	unsigned i_conv : 2;	/* (files) crlf->newline hackery */
+	unsigned i_ea_mode : 1;	/* file's permission is stored in ea */
+	unsigned i_ea_uid : 1;	/* file's uid is stored in ea */
+	unsigned i_ea_gid : 1;	/* file's gid is stored in ea */
+	unsigned i_dirty : 1;
+	struct semaphore i_sem;
+	struct semaphore i_parent;
+	loff_t **i_rddir_off;
+	struct inode vfs_inode;
+};
+
+struct hpfs_sb_info {
+	ino_t sb_root;			/* inode number of root dir */
+	unsigned sb_fs_size;		/* file system size, sectors */
+	unsigned sb_bitmaps;		/* sector number of bitmap list */
+	unsigned sb_dirband_start;	/* directory band start sector */
+	unsigned sb_dirband_size;	/* directory band size, dnodes */
+	unsigned sb_dmap;		/* sector number of dnode bit map */
+	unsigned sb_n_free;		/* free blocks for statfs, or -1 */
+	unsigned sb_n_free_dnodes;	/* free dnodes for statfs, or -1 */
+	uid_t sb_uid;			/* uid from mount options */
+	gid_t sb_gid;			/* gid from mount options */
+	umode_t sb_mode;		/* mode from mount options */
+	unsigned sb_conv : 2;		/* crlf->newline hackery */
+	unsigned sb_eas : 2;		/* eas: 0-ignore, 1-ro, 2-rw */
+	unsigned sb_err : 2;		/* on errs: 0-cont, 1-ro, 2-panic */
+	unsigned sb_chk : 2;		/* checks: 0-no, 1-normal, 2-strict */
+	unsigned sb_lowercase : 1;	/* downcase filenames hackery */
+	unsigned sb_was_error : 1;	/* there was an error, set dirty flag */
+	unsigned sb_chkdsk : 2;		/* chkdsk: 0-no, 1-on errs, 2-allways */
+	unsigned char *sb_cp_table;	/* code page tables: */
+					/* 	128 bytes uppercasing table & */
+					/*	128 bytes lowercasing table */
+	unsigned *sb_bmp_dir;		/* main bitmap directory */
+	unsigned sb_c_bitmap;		/* current bitmap */
+	unsigned sb_max_fwd_alloc;	/* max forwad allocation */
+	struct semaphore hpfs_creation_de; /* when creating dirents, nobody else
+					   can alloc blocks */
+	/*unsigned sb_mounting : 1;*/
+	int sb_timeshift;
+};
+
+/*
+ * conv= options
+ */
+
+#define CONV_BINARY 0			/* no conversion */
+#define CONV_TEXT 1			/* crlf->newline */
+#define CONV_AUTO 2			/* decide based on file contents */
+
+/* Four 512-byte buffers and the 2k block obtained by concatenating them */
+
+struct quad_buffer_head {
+	struct buffer_head *bh[4];
+	void *data;
+};
+
+/* The b-tree down pointer from a dir entry */
+
+static inline dnode_secno de_down_pointer (struct hpfs_dirent *de)
+{
+  CHKCOND(de->down,("HPFS: de_down_pointer: !de->down\n"));
+  return *(dnode_secno *) ((void *) de + de->length - 4);
+}
+
+/* The first dir entry in a dnode */
+
+static inline struct hpfs_dirent *dnode_first_de (struct dnode *dnode)
+{
+  return (void *) dnode->dirent;
+}
+
+/* The end+1 of the dir entries */
+
+static inline struct hpfs_dirent *dnode_end_de (struct dnode *dnode)
+{
+  CHKCOND(dnode->first_free>=0x14 && dnode->first_free<=0xa00,("HPFS: dnode_end_de: dnode->first_free = %d\n",(int)dnode->first_free));
+  return (void *) dnode + dnode->first_free;
+}
+
+/* The dir entry after dir entry de */
+
+static inline struct hpfs_dirent *de_next_de (struct hpfs_dirent *de)
+{
+  CHKCOND(de->length>=0x20 && de->length<0x800,("HPFS: de_next_de: de->length = %d\n",(int)de->length));
+  return (void *) de + de->length;
+}
+
+static inline struct extended_attribute *fnode_ea(struct fnode *fnode)
+{
+	return (struct extended_attribute *)((char *)fnode + fnode->ea_offs + fnode->acl_size_s);
+}
+
+static inline struct extended_attribute *fnode_end_ea(struct fnode *fnode)
+{
+	return (struct extended_attribute *)((char *)fnode + fnode->ea_offs + fnode->acl_size_s + fnode->ea_size_s);
+}
+
+static inline struct extended_attribute *next_ea(struct extended_attribute *ea)
+{
+	return (struct extended_attribute *)((char *)ea + 5 + ea->namelen + ea->valuelen);
+}
+
+static inline secno ea_sec(struct extended_attribute *ea)
+{
+	return *(secno *)((char *)ea + 9 + ea->namelen);
+}
+
+static inline secno ea_len(struct extended_attribute *ea)
+{
+	return *(secno *)((char *)ea + 5 + ea->namelen);
+}
+
+static inline char *ea_data(struct extended_attribute *ea)
+{
+	return (char *)((char *)ea + 5 + ea->namelen);
+}
+
+static inline unsigned de_size(int namelen, secno down_ptr)
+{
+	return ((0x1f + namelen + 3) & ~3) + (down_ptr ? 4 : 0);
+}
+
+static inline void copy_de(struct hpfs_dirent *dst, struct hpfs_dirent *src)
+{
+	int a;
+	int n;
+	if (!dst || !src) return;
+	a = dst->down;
+	n = dst->not_8x3;
+	memcpy((char *)dst + 2, (char *)src + 2, 28);
+	dst->down = a;
+	dst->not_8x3 = n;
+}
+
+static inline unsigned tstbits(unsigned *bmp, unsigned b, unsigned n)
+{
+	int i;
+	if ((b >= 0x4000) || (b + n - 1 >= 0x4000)) return n;
+	if (!((bmp[(b & 0x3fff) >> 5] >> (b & 0x1f)) & 1)) return 1;
+	for (i = 1; i < n; i++)
+		if (/*b+i < 0x4000 &&*/ !((bmp[((b+i) & 0x3fff) >> 5] >> ((b+i) & 0x1f)) & 1))
+			return i + 1;
+	return 0;
+}
+
+/* alloc.c */
+
+int hpfs_chk_sectors(struct super_block *, secno, int, char *);
+secno hpfs_alloc_sector(struct super_block *, secno, unsigned, int, int);
+int hpfs_alloc_if_possible(struct super_block *, secno);
+void hpfs_free_sectors(struct super_block *, secno, unsigned);
+int hpfs_check_free_dnodes(struct super_block *, int);
+void hpfs_free_dnode(struct super_block *, secno);
+struct dnode *hpfs_alloc_dnode(struct super_block *, secno, dnode_secno *, struct quad_buffer_head *, int);
+struct fnode *hpfs_alloc_fnode(struct super_block *, secno, fnode_secno *, struct buffer_head **);
+struct anode *hpfs_alloc_anode(struct super_block *, secno, anode_secno *, struct buffer_head **);
+
+/* anode.c */
+
+secno hpfs_bplus_lookup(struct super_block *, struct inode *, struct bplus_header *, unsigned, struct buffer_head *);
+secno hpfs_add_sector_to_btree(struct super_block *, secno, int, unsigned);
+void hpfs_remove_btree(struct super_block *, struct bplus_header *);
+int hpfs_ea_read(struct super_block *, secno, int, unsigned, unsigned, char *);
+int hpfs_ea_write(struct super_block *, secno, int, unsigned, unsigned, char *);
+void hpfs_ea_remove(struct super_block *, secno, int, unsigned);
+void hpfs_truncate_btree(struct super_block *, secno, int, unsigned);
+void hpfs_remove_fnode(struct super_block *, fnode_secno fno);
+
+/* buffer.c */
+
+void hpfs_lock_creation(struct super_block *);
+void hpfs_unlock_creation(struct super_block *);
+void *hpfs_map_sector(struct super_block *, unsigned, struct buffer_head **, int);
+void *hpfs_get_sector(struct super_block *, unsigned, struct buffer_head **);
+void *hpfs_map_4sectors(struct super_block *, unsigned, struct quad_buffer_head *, int);
+void *hpfs_get_4sectors(struct super_block *, unsigned, struct quad_buffer_head *);
+void hpfs_brelse4(struct quad_buffer_head *);
+void hpfs_mark_4buffers_dirty(struct quad_buffer_head *);
+
+/* dentry.c */
+
+void hpfs_set_dentry_operations(struct dentry *);
+
+/* dir.c */
+
+struct dentry *hpfs_lookup(struct inode *, struct dentry *, struct nameidata *);
+extern struct file_operations hpfs_dir_ops;
+
+/* dnode.c */
+
+void hpfs_add_pos(struct inode *, loff_t *);
+void hpfs_del_pos(struct inode *, loff_t *);
+struct hpfs_dirent *hpfs_add_de(struct super_block *, struct dnode *, unsigned char *, unsigned, secno);
+int hpfs_add_dirent(struct inode *, unsigned char *, unsigned, struct hpfs_dirent *, int);
+int hpfs_remove_dirent(struct inode *, dnode_secno, struct hpfs_dirent *, struct quad_buffer_head *, int);
+void hpfs_count_dnodes(struct super_block *, dnode_secno, int *, int *, int *);
+dnode_secno hpfs_de_as_down_as_possible(struct super_block *, dnode_secno dno);
+struct hpfs_dirent *map_pos_dirent(struct inode *, loff_t *, struct quad_buffer_head *);
+struct hpfs_dirent *map_dirent(struct inode *, dnode_secno, char *, unsigned, dnode_secno *, struct quad_buffer_head *);
+void hpfs_remove_dtree(struct super_block *, dnode_secno);
+struct hpfs_dirent *map_fnode_dirent(struct super_block *, fnode_secno, struct fnode *, struct quad_buffer_head *);
+
+/* ea.c */
+
+void hpfs_ea_ext_remove(struct super_block *, secno, int, unsigned);
+int hpfs_read_ea(struct super_block *, struct fnode *, char *, char *, int);
+char *hpfs_get_ea(struct super_block *, struct fnode *, char *, int *);
+void hpfs_set_ea(struct inode *, struct fnode *, char *, char *, int);
+
+/* file.c */
+
+int hpfs_file_fsync(struct file *, struct dentry *, int);
+extern struct file_operations hpfs_file_ops;
+extern struct inode_operations hpfs_file_iops;
+extern struct address_space_operations hpfs_aops;
+
+/* inode.c */
+
+void hpfs_init_inode(struct inode *);
+void hpfs_read_inode(struct inode *);
+void hpfs_write_inode(struct inode *);
+void hpfs_write_inode_nolock(struct inode *);
+int hpfs_notify_change(struct dentry *, struct iattr *);
+void hpfs_write_if_changed(struct inode *);
+void hpfs_delete_inode(struct inode *);
+
+/* map.c */
+
+unsigned *hpfs_map_dnode_bitmap(struct super_block *, struct quad_buffer_head *);
+unsigned *hpfs_map_bitmap(struct super_block *, unsigned, struct quad_buffer_head *, char *);
+char *hpfs_load_code_page(struct super_block *, secno);
+secno *hpfs_load_bitmap_directory(struct super_block *, secno bmp);
+struct fnode *hpfs_map_fnode(struct super_block *s, ino_t, struct buffer_head **);
+struct anode *hpfs_map_anode(struct super_block *s, anode_secno, struct buffer_head **);
+struct dnode *hpfs_map_dnode(struct super_block *s, dnode_secno, struct quad_buffer_head *);
+dnode_secno hpfs_fnode_dno(struct super_block *s, ino_t ino);
+
+/* name.c */
+
+unsigned char hpfs_upcase(unsigned char *, unsigned char);
+int hpfs_chk_name(unsigned char *, unsigned *);
+char *hpfs_translate_name(struct super_block *, unsigned char *, unsigned, int, int);
+int hpfs_compare_names(struct super_block *, unsigned char *, unsigned, unsigned char *, unsigned, int);
+int hpfs_is_name_long(unsigned char *, unsigned);
+void hpfs_adjust_length(unsigned char *, unsigned *);
+void hpfs_decide_conv(struct inode *, unsigned char *, unsigned);
+
+/* namei.c */
+
+extern struct inode_operations hpfs_dir_iops;
+extern struct address_space_operations hpfs_symlink_aops;
+
+static inline struct hpfs_inode_info *hpfs_i(struct inode *inode)
+{
+	return list_entry(inode, struct hpfs_inode_info, vfs_inode);
+}
+
+static inline struct hpfs_sb_info *hpfs_sb(struct super_block *sb)
+{
+	return sb->s_fs_info;
+}
+
+/* super.c */
+
+void hpfs_error(struct super_block *, char *, ...);
+int hpfs_stop_cycles(struct super_block *, int, int *, int *, char *);
+unsigned hpfs_count_one_bitmap(struct super_block *, secno);
+
+/*
+ * local time (HPFS) to GMT (Unix)
+ */
+
+static inline time_t local_to_gmt(struct super_block *s, time32_t t)
+{
+	extern struct timezone sys_tz;
+	return t + sys_tz.tz_minuteswest * 60 + hpfs_sb(s)->sb_timeshift;
+}
+
+static inline time32_t gmt_to_local(struct super_block *s, time_t t)
+{
+	extern struct timezone sys_tz;
+	return t - sys_tz.tz_minuteswest * 60 - hpfs_sb(s)->sb_timeshift;
+}
diff --git a/fs/hpfs/inode.c b/fs/hpfs/inode.c
new file mode 100644
index 0000000..38b1741
--- /dev/null
+++ b/fs/hpfs/inode.c
@@ -0,0 +1,291 @@
+/*
+ *  linux/fs/hpfs/inode.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  inode VFS functions
+ */
+
+#include "hpfs_fn.h"
+
+void hpfs_init_inode(struct inode *i)
+{
+	struct super_block *sb = i->i_sb;
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(i);
+
+	i->i_uid = hpfs_sb(sb)->sb_uid;
+	i->i_gid = hpfs_sb(sb)->sb_gid;
+	i->i_mode = hpfs_sb(sb)->sb_mode;
+	hpfs_inode->i_conv = hpfs_sb(sb)->sb_conv;
+	i->i_blksize = 512;
+	i->i_size = -1;
+	i->i_blocks = -1;
+	
+	hpfs_inode->i_dno = 0;
+	hpfs_inode->i_n_secs = 0;
+	hpfs_inode->i_file_sec = 0;
+	hpfs_inode->i_disk_sec = 0;
+	hpfs_inode->i_dpos = 0;
+	hpfs_inode->i_dsubdno = 0;
+	hpfs_inode->i_ea_mode = 0;
+	hpfs_inode->i_ea_uid = 0;
+	hpfs_inode->i_ea_gid = 0;
+	hpfs_inode->i_ea_size = 0;
+
+	hpfs_inode->i_rddir_off = NULL;
+	hpfs_inode->i_dirty = 0;
+
+	i->i_ctime.tv_sec = i->i_ctime.tv_nsec = 0;
+	i->i_mtime.tv_sec = i->i_mtime.tv_nsec = 0;
+	i->i_atime.tv_sec = i->i_atime.tv_nsec = 0;
+}
+
+void hpfs_read_inode(struct inode *i)
+{
+	struct buffer_head *bh;
+	struct fnode *fnode;
+	struct super_block *sb = i->i_sb;
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(i);
+	unsigned char *ea;
+	int ea_size;
+
+	if (!(fnode = hpfs_map_fnode(sb, i->i_ino, &bh))) {
+		/*i->i_mode |= S_IFREG;
+		i->i_mode &= ~0111;
+		i->i_op = &hpfs_file_iops;
+		i->i_fop = &hpfs_file_ops;
+		i->i_nlink = 0;*/
+		make_bad_inode(i);
+		return;
+	}
+	if (hpfs_sb(i->i_sb)->sb_eas) {
+		if ((ea = hpfs_get_ea(i->i_sb, fnode, "UID", &ea_size))) {
+			if (ea_size == 2) {
+				i->i_uid = le16_to_cpu(*(u16*)ea);
+				hpfs_inode->i_ea_uid = 1;
+			}
+			kfree(ea);
+		}
+		if ((ea = hpfs_get_ea(i->i_sb, fnode, "GID", &ea_size))) {
+			if (ea_size == 2) {
+				i->i_gid = le16_to_cpu(*(u16*)ea);
+				hpfs_inode->i_ea_gid = 1;
+			}
+			kfree(ea);
+		}
+		if ((ea = hpfs_get_ea(i->i_sb, fnode, "SYMLINK", &ea_size))) {
+			kfree(ea);
+			i->i_mode = S_IFLNK | 0777;
+			i->i_op = &page_symlink_inode_operations;
+			i->i_data.a_ops = &hpfs_symlink_aops;
+			i->i_nlink = 1;
+			i->i_size = ea_size;
+			i->i_blocks = 1;
+			brelse(bh);
+			return;
+		}
+		if ((ea = hpfs_get_ea(i->i_sb, fnode, "MODE", &ea_size))) {
+			int rdev = 0;
+			umode_t mode = hpfs_sb(sb)->sb_mode;
+			if (ea_size == 2) {
+				mode = le16_to_cpu(*(u16*)ea);
+				hpfs_inode->i_ea_mode = 1;
+			}
+			kfree(ea);
+			i->i_mode = mode;
+			if (S_ISBLK(mode) || S_ISCHR(mode)) {
+				if ((ea = hpfs_get_ea(i->i_sb, fnode, "DEV", &ea_size))) {
+					if (ea_size == 4)
+						rdev = le32_to_cpu(*(u32*)ea);
+					kfree(ea);
+				}
+			}
+			if (S_ISBLK(mode) || S_ISCHR(mode) || S_ISFIFO(mode) || S_ISSOCK(mode)) {
+				brelse(bh);
+				i->i_nlink = 1;
+				i->i_size = 0;
+				i->i_blocks = 1;
+				init_special_inode(i, mode,
+					new_decode_dev(rdev));
+				return;
+			}
+		}
+	}
+	if (fnode->dirflag) {
+		unsigned n_dnodes, n_subdirs;
+		i->i_mode |= S_IFDIR;
+		i->i_op = &hpfs_dir_iops;
+		i->i_fop = &hpfs_dir_ops;
+		hpfs_inode->i_parent_dir = fnode->up;
+		hpfs_inode->i_dno = fnode->u.external[0].disk_secno;
+		if (hpfs_sb(sb)->sb_chk >= 2) {
+			struct buffer_head *bh0;
+			if (hpfs_map_fnode(sb, hpfs_inode->i_parent_dir, &bh0)) brelse(bh0);
+		}
+		n_dnodes = 0; n_subdirs = 0;
+		hpfs_count_dnodes(i->i_sb, hpfs_inode->i_dno, &n_dnodes, &n_subdirs, NULL);
+		i->i_blocks = 4 * n_dnodes;
+		i->i_size = 2048 * n_dnodes;
+		i->i_nlink = 2 + n_subdirs;
+	} else {
+		i->i_mode |= S_IFREG;
+		if (!hpfs_inode->i_ea_mode) i->i_mode &= ~0111;
+		i->i_op = &hpfs_file_iops;
+		i->i_fop = &hpfs_file_ops;
+		i->i_nlink = 1;
+		i->i_size = fnode->file_size;
+		i->i_blocks = ((i->i_size + 511) >> 9) + 1;
+		i->i_data.a_ops = &hpfs_aops;
+		hpfs_i(i)->mmu_private = i->i_size;
+	}
+	brelse(bh);
+}
+
+static void hpfs_write_inode_ea(struct inode *i, struct fnode *fnode)
+{
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(i);
+	/*if (fnode->acl_size_l || fnode->acl_size_s) {
+		   Some unknown structures like ACL may be in fnode,
+		   we'd better not overwrite them
+		hpfs_error(i->i_sb, "fnode %08x has some unknown HPFS386 stuctures", i->i_ino);
+	} else*/ if (hpfs_sb(i->i_sb)->sb_eas >= 2) {
+		u32 ea;
+		if ((i->i_uid != hpfs_sb(i->i_sb)->sb_uid) || hpfs_inode->i_ea_uid) {
+			ea = cpu_to_le32(i->i_uid);
+			hpfs_set_ea(i, fnode, "UID", (char*)&ea, 2);
+			hpfs_inode->i_ea_uid = 1;
+		}
+		if ((i->i_gid != hpfs_sb(i->i_sb)->sb_gid) || hpfs_inode->i_ea_gid) {
+			ea = cpu_to_le32(i->i_gid);
+			hpfs_set_ea(i, fnode, "GID", (char *)&ea, 2);
+			hpfs_inode->i_ea_gid = 1;
+		}
+		if (!S_ISLNK(i->i_mode))
+			if ((i->i_mode != ((hpfs_sb(i->i_sb)->sb_mode & ~(S_ISDIR(i->i_mode) ? 0 : 0111))
+			  | (S_ISDIR(i->i_mode) ? S_IFDIR : S_IFREG))
+			  && i->i_mode != ((hpfs_sb(i->i_sb)->sb_mode & ~(S_ISDIR(i->i_mode) ? 0222 : 0333))
+			  | (S_ISDIR(i->i_mode) ? S_IFDIR : S_IFREG))) || hpfs_inode->i_ea_mode) {
+				ea = cpu_to_le32(i->i_mode);
+				hpfs_set_ea(i, fnode, "MODE", (char *)&ea, 2);
+				hpfs_inode->i_ea_mode = 1;
+			}
+		if (S_ISBLK(i->i_mode) || S_ISCHR(i->i_mode)) {
+			ea = cpu_to_le32(new_encode_dev(i->i_rdev));
+			hpfs_set_ea(i, fnode, "DEV", (char *)&ea, 4);
+		}
+	}
+}
+
+void hpfs_write_inode(struct inode *i)
+{
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(i);
+	struct inode *parent;
+	if (i->i_ino == hpfs_sb(i->i_sb)->sb_root) return;
+	if (hpfs_inode->i_rddir_off && !atomic_read(&i->i_count)) {
+		if (*hpfs_inode->i_rddir_off) printk("HPFS: write_inode: some position still there\n");
+		kfree(hpfs_inode->i_rddir_off);
+		hpfs_inode->i_rddir_off = NULL;
+	}
+	down(&hpfs_inode->i_parent);
+	if (!i->i_nlink) {
+		up(&hpfs_inode->i_parent);
+		return;
+	}
+	parent = iget_locked(i->i_sb, hpfs_inode->i_parent_dir);
+	if (parent) {
+		hpfs_inode->i_dirty = 0;
+		if (parent->i_state & I_NEW) {
+			hpfs_init_inode(parent);
+			hpfs_read_inode(parent);
+			unlock_new_inode(parent);
+		}
+		down(&hpfs_inode->i_sem);
+		hpfs_write_inode_nolock(i);
+		up(&hpfs_inode->i_sem);
+		iput(parent);
+	} else {
+		mark_inode_dirty(i);
+	}
+	up(&hpfs_inode->i_parent);
+}
+
+void hpfs_write_inode_nolock(struct inode *i)
+{
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(i);
+	struct buffer_head *bh;
+	struct fnode *fnode;
+	struct quad_buffer_head qbh;
+	struct hpfs_dirent *de;
+	if (i->i_ino == hpfs_sb(i->i_sb)->sb_root) return;
+	if (!(fnode = hpfs_map_fnode(i->i_sb, i->i_ino, &bh))) return;
+	if (i->i_ino != hpfs_sb(i->i_sb)->sb_root && i->i_nlink) {
+		if (!(de = map_fnode_dirent(i->i_sb, i->i_ino, fnode, &qbh))) {
+			brelse(bh);
+			return;
+		}
+	} else de = NULL;
+	if (S_ISREG(i->i_mode)) {
+		fnode->file_size = i->i_size;
+		if (de) de->file_size = i->i_size;
+	} else if (S_ISDIR(i->i_mode)) {
+		fnode->file_size = 0;
+		if (de) de->file_size = 0;
+	}
+	hpfs_write_inode_ea(i, fnode);
+	if (de) {
+		de->write_date = gmt_to_local(i->i_sb, i->i_mtime.tv_sec);
+		de->read_date = gmt_to_local(i->i_sb, i->i_atime.tv_sec);
+		de->creation_date = gmt_to_local(i->i_sb, i->i_ctime.tv_sec);
+		de->read_only = !(i->i_mode & 0222);
+		de->ea_size = hpfs_inode->i_ea_size;
+		hpfs_mark_4buffers_dirty(&qbh);
+		hpfs_brelse4(&qbh);
+	}
+	if (S_ISDIR(i->i_mode)) {
+		if ((de = map_dirent(i, hpfs_inode->i_dno, "\001\001", 2, NULL, &qbh))) {
+			de->write_date = gmt_to_local(i->i_sb, i->i_mtime.tv_sec);
+			de->read_date = gmt_to_local(i->i_sb, i->i_atime.tv_sec);
+			de->creation_date = gmt_to_local(i->i_sb, i->i_ctime.tv_sec);
+			de->read_only = !(i->i_mode & 0222);
+			de->ea_size = /*hpfs_inode->i_ea_size*/0;
+			de->file_size = 0;
+			hpfs_mark_4buffers_dirty(&qbh);
+			hpfs_brelse4(&qbh);
+		} else hpfs_error(i->i_sb, "directory %08x doesn't have '.' entry", i->i_ino);
+	}
+	mark_buffer_dirty(bh);
+	brelse(bh);
+}
+
+int hpfs_notify_change(struct dentry *dentry, struct iattr *attr)
+{
+	struct inode *inode = dentry->d_inode;
+	int error=0;
+	lock_kernel();
+	if ( ((attr->ia_valid & ATTR_SIZE) && attr->ia_size > inode->i_size) ||
+	     (hpfs_sb(inode->i_sb)->sb_root == inode->i_ino) ) {
+		error = -EINVAL;
+	} else if ((error = inode_change_ok(inode, attr))) {
+	} else if ((error = inode_setattr(inode, attr))) {
+	} else {
+		hpfs_write_inode(inode);
+	}
+	unlock_kernel();
+	return error;
+}
+
+void hpfs_write_if_changed(struct inode *inode)
+{
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(inode);
+
+	if (hpfs_inode->i_dirty)
+		hpfs_write_inode(inode);
+}
+
+void hpfs_delete_inode(struct inode *inode)
+{
+	lock_kernel();
+	hpfs_remove_fnode(inode->i_sb, inode->i_ino);
+	unlock_kernel();
+	clear_inode(inode);
+}
diff --git a/fs/hpfs/map.c b/fs/hpfs/map.c
new file mode 100644
index 0000000..0fecdac
--- /dev/null
+++ b/fs/hpfs/map.c
@@ -0,0 +1,275 @@
+/*
+ *  linux/fs/hpfs/map.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  mapping structures to memory with some minimal checks
+ */
+
+#include "hpfs_fn.h"
+
+unsigned *hpfs_map_dnode_bitmap(struct super_block *s, struct quad_buffer_head *qbh)
+{
+	return hpfs_map_4sectors(s, hpfs_sb(s)->sb_dmap, qbh, 0);
+}
+
+unsigned int *hpfs_map_bitmap(struct super_block *s, unsigned bmp_block,
+			 struct quad_buffer_head *qbh, char *id)
+{
+	secno sec;
+	if (hpfs_sb(s)->sb_chk) if (bmp_block * 16384 > hpfs_sb(s)->sb_fs_size) {
+		hpfs_error(s, "hpfs_map_bitmap called with bad parameter: %08x at %s", bmp_block, id);
+		return NULL;
+	}
+	sec = hpfs_sb(s)->sb_bmp_dir[bmp_block];
+	if (!sec || sec > hpfs_sb(s)->sb_fs_size-4) {
+		hpfs_error(s, "invalid bitmap block pointer %08x -> %08x at %s", bmp_block, sec, id);
+		return NULL;
+	}
+	return hpfs_map_4sectors(s, sec, qbh, 4);
+}
+
+/*
+ * Load first code page into kernel memory, return pointer to 256-byte array,
+ * first 128 bytes are uppercasing table for chars 128-255, next 128 bytes are
+ * lowercasing table
+ */
+
+char *hpfs_load_code_page(struct super_block *s, secno cps)
+{
+	struct buffer_head *bh;
+	secno cpds;
+	unsigned cpi;
+	unsigned char *ptr;
+	unsigned char *cp_table;
+	int i;
+	struct code_page_data *cpd;
+	struct code_page_directory *cp = hpfs_map_sector(s, cps, &bh, 0);
+	if (!cp) return NULL;
+	if (cp->magic != CP_DIR_MAGIC) {
+		printk("HPFS: Code page directory magic doesn't match (magic = %08x)\n", cp->magic);
+		brelse(bh);
+		return NULL;
+	}
+	if (!cp->n_code_pages) {
+		printk("HPFS: n_code_pages == 0\n");
+		brelse(bh);
+		return NULL;
+	}
+	cpds = cp->array[0].code_page_data;
+	cpi = cp->array[0].index;
+	brelse(bh);
+
+	if (cpi >= 3) {
+		printk("HPFS: Code page index out of array\n");
+		return NULL;
+	}
+	
+	if (!(cpd = hpfs_map_sector(s, cpds, &bh, 0))) return NULL;
+	if ((unsigned)cpd->offs[cpi] > 0x178) {
+		printk("HPFS: Code page index out of sector\n");
+		brelse(bh);
+		return NULL;
+	}
+	ptr = (char *)cpd + cpd->offs[cpi] + 6;
+	if (!(cp_table = kmalloc(256, GFP_KERNEL))) {
+		printk("HPFS: out of memory for code page table\n");
+		brelse(bh);
+		return NULL;
+	}
+	memcpy(cp_table, ptr, 128);
+	brelse(bh);
+
+	/* Try to build lowercasing table from uppercasing one */
+
+	for (i=128; i<256; i++) cp_table[i]=i;
+	for (i=128; i<256; i++) if (cp_table[i-128]!=i && cp_table[i-128]>=128)
+		cp_table[cp_table[i-128]] = i;
+	
+	return cp_table;
+}
+
+secno *hpfs_load_bitmap_directory(struct super_block *s, secno bmp)
+{
+	struct buffer_head *bh;
+	int n = (hpfs_sb(s)->sb_fs_size + 0x200000 - 1) >> 21;
+	int i;
+	secno *b;
+	if (!(b = kmalloc(n * 512, GFP_KERNEL))) {
+		printk("HPFS: can't allocate memory for bitmap directory\n");
+		return NULL;
+	}	
+	for (i=0;i<n;i++) {
+		secno *d = hpfs_map_sector(s, bmp+i, &bh, n - i - 1);
+		if (!d) {
+			kfree(b);
+			return NULL;
+		}
+		memcpy((char *)b + 512 * i, d, 512);
+		brelse(bh);
+	}
+	return b;
+}
+
+/*
+ * Load fnode to memory
+ */
+
+struct fnode *hpfs_map_fnode(struct super_block *s, ino_t ino, struct buffer_head **bhp)
+{
+	struct fnode *fnode;
+	if (hpfs_sb(s)->sb_chk) if (hpfs_chk_sectors(s, ino, 1, "fnode")) {
+		return NULL;
+	}
+	if ((fnode = hpfs_map_sector(s, ino, bhp, FNODE_RD_AHEAD))) {
+		if (hpfs_sb(s)->sb_chk) {
+			struct extended_attribute *ea;
+			struct extended_attribute *ea_end;
+			if (fnode->magic != FNODE_MAGIC) {
+				hpfs_error(s, "bad magic on fnode %08x", ino);
+				goto bail;
+			}
+			if (!fnode->dirflag) {
+				if ((unsigned)fnode->btree.n_used_nodes + (unsigned)fnode->btree.n_free_nodes !=
+				    (fnode->btree.internal ? 12 : 8)) {
+					hpfs_error(s, "bad number of nodes in fnode %08x", ino);
+					goto bail;
+				}
+				if (fnode->btree.first_free !=
+				    8 + fnode->btree.n_used_nodes * (fnode->btree.internal ? 8 : 12)) {
+					hpfs_error(s, "bad first_free pointer in fnode %08x", ino);
+					goto bail;
+				}
+			}
+			if (fnode->ea_size_s && ((signed int)fnode->ea_offs < 0xc4 ||
+			   (signed int)fnode->ea_offs + fnode->acl_size_s + fnode->ea_size_s > 0x200)) {
+				hpfs_error(s, "bad EA info in fnode %08x: ea_offs == %04x ea_size_s == %04x",
+					ino, fnode->ea_offs, fnode->ea_size_s);
+				goto bail;
+			}
+			ea = fnode_ea(fnode);
+			ea_end = fnode_end_ea(fnode);
+			while (ea != ea_end) {
+				if (ea > ea_end) {
+					hpfs_error(s, "bad EA in fnode %08x", ino);
+					goto bail;
+				}
+				ea = next_ea(ea);
+			}
+		}
+	}
+	return fnode;
+	bail:
+	brelse(*bhp);
+	return NULL;
+}
+
+struct anode *hpfs_map_anode(struct super_block *s, anode_secno ano, struct buffer_head **bhp)
+{
+	struct anode *anode;
+	if (hpfs_sb(s)->sb_chk) if (hpfs_chk_sectors(s, ano, 1, "anode")) return NULL;
+	if ((anode = hpfs_map_sector(s, ano, bhp, ANODE_RD_AHEAD)))
+		if (hpfs_sb(s)->sb_chk) {
+			if (anode->magic != ANODE_MAGIC || anode->self != ano) {
+				hpfs_error(s, "bad magic on anode %08x", ano);
+				goto bail;
+			}
+			if ((unsigned)anode->btree.n_used_nodes + (unsigned)anode->btree.n_free_nodes !=
+			    (anode->btree.internal ? 60 : 40)) {
+				hpfs_error(s, "bad number of nodes in anode %08x", ano);
+				goto bail;
+			}
+			if (anode->btree.first_free !=
+			    8 + anode->btree.n_used_nodes * (anode->btree.internal ? 8 : 12)) {
+				hpfs_error(s, "bad first_free pointer in anode %08x", ano);
+				goto bail;
+			}
+		}
+	return anode;
+	bail:
+	brelse(*bhp);
+	return NULL;
+}
+
+/*
+ * Load dnode to memory and do some checks
+ */
+
+struct dnode *hpfs_map_dnode(struct super_block *s, unsigned secno,
+			     struct quad_buffer_head *qbh)
+{
+	struct dnode *dnode;
+	if (hpfs_sb(s)->sb_chk) {
+		if (hpfs_chk_sectors(s, secno, 4, "dnode")) return NULL;
+		if (secno & 3) {
+			hpfs_error(s, "dnode %08x not byte-aligned", secno);
+			return NULL;
+		}	
+	}
+	if ((dnode = hpfs_map_4sectors(s, secno, qbh, DNODE_RD_AHEAD)))
+		if (hpfs_sb(s)->sb_chk) {
+			unsigned p, pp = 0;
+			unsigned char *d = (char *)dnode;
+			int b = 0;
+			if (dnode->magic != DNODE_MAGIC) {
+				hpfs_error(s, "bad magic on dnode %08x", secno);
+				goto bail;
+			}
+			if (dnode->self != secno)
+				hpfs_error(s, "bad self pointer on dnode %08x self = %08x", secno, dnode->self);
+			/* Check dirents - bad dirents would cause infinite
+			   loops or shooting to memory */
+			if (dnode->first_free > 2048/* || dnode->first_free < 84*/) {
+				hpfs_error(s, "dnode %08x has first_free == %08x", secno, dnode->first_free);
+				goto bail;
+			}
+			for (p = 20; p < dnode->first_free; p += d[p] + (d[p+1] << 8)) {
+				struct hpfs_dirent *de = (struct hpfs_dirent *)((char *)dnode + p);
+				if (de->length > 292 || (de->length < 32) || (de->length & 3) || p + de->length > 2048) {
+					hpfs_error(s, "bad dirent size in dnode %08x, dirent %03x, last %03x", secno, p, pp);
+					goto bail;
+				}
+				if (((31 + de->namelen + de->down*4 + 3) & ~3) != de->length) {
+					if (((31 + de->namelen + de->down*4 + 3) & ~3) < de->length && s->s_flags & MS_RDONLY) goto ok;
+					hpfs_error(s, "namelen does not match dirent size in dnode %08x, dirent %03x, last %03x", secno, p, pp);
+					goto bail;
+				}
+				ok:
+				if (hpfs_sb(s)->sb_chk >= 2) b |= 1 << de->down;
+				if (de->down) if (de_down_pointer(de) < 0x10) {
+					hpfs_error(s, "bad down pointer in dnode %08x, dirent %03x, last %03x", secno, p, pp);
+					goto bail;
+				}
+				pp = p;
+				
+			}
+			if (p != dnode->first_free) {
+				hpfs_error(s, "size on last dirent does not match first_free; dnode %08x", secno);
+				goto bail;
+			}
+			if (d[pp + 30] != 1 || d[pp + 31] != 255) {
+				hpfs_error(s, "dnode %08x does not end with \\377 entry", secno);
+				goto bail;
+			}
+			if (b == 3) printk("HPFS: warning: unbalanced dnode tree, dnode %08x; see hpfs.txt 4 more info\n", secno);
+		}
+	return dnode;
+	bail:
+	hpfs_brelse4(qbh);
+	return NULL;
+}
+
+dnode_secno hpfs_fnode_dno(struct super_block *s, ino_t ino)
+{
+	struct buffer_head *bh;
+	struct fnode *fnode;
+	dnode_secno dno;
+
+	fnode = hpfs_map_fnode(s, ino, &bh);
+	if (!fnode)
+		return 0;
+
+	dno = fnode->u.external[0].disk_secno;
+	brelse(bh);
+	return dno;
+}
diff --git a/fs/hpfs/name.c b/fs/hpfs/name.c
new file mode 100644
index 0000000..1f4a964
--- /dev/null
+++ b/fs/hpfs/name.c
@@ -0,0 +1,144 @@
+/*
+ *  linux/fs/hpfs/name.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  operations with filenames
+ */
+
+#include "hpfs_fn.h"
+
+static char *text_postfix[]={
+".ASM", ".BAS", ".BAT", ".C", ".CC", ".CFG", ".CMD", ".CON", ".CPP", ".DEF",
+".DOC", ".DPR", ".ERX", ".H", ".HPP", ".HTM", ".HTML", ".JAVA", ".LOG", ".PAS",
+".RC", ".TEX", ".TXT", ".Y", ""};
+
+static char *text_prefix[]={
+"AUTOEXEC.", "CHANGES", "COPYING", "CONFIG.", "CREDITS", "FAQ", "FILE_ID.DIZ",
+"MAKEFILE", "READ.ME", "README", "TERMCAP", ""};
+
+void hpfs_decide_conv(struct inode *inode, unsigned char *name, unsigned len)
+{
+	struct hpfs_inode_info *hpfs_inode = hpfs_i(inode);
+	int i;
+	if (hpfs_inode->i_conv != CONV_AUTO) return;
+	for (i = 0; *text_postfix[i]; i++) {
+		int l = strlen(text_postfix[i]);
+		if (l <= len)
+			if (!hpfs_compare_names(inode->i_sb, text_postfix[i], l, name + len - l, l, 0))
+				goto text;
+	}
+	for (i = 0; *text_prefix[i]; i++) {
+		int l = strlen(text_prefix[i]);
+		if (l <= len)
+			if (!hpfs_compare_names(inode->i_sb, text_prefix[i], l, name, l, 0))
+				goto text;
+	}
+	hpfs_inode->i_conv = CONV_BINARY;
+	return;
+	text:
+	hpfs_inode->i_conv = CONV_TEXT;
+	return;
+}
+
+static inline int not_allowed_char(unsigned char c)
+{
+	return c<' ' || c=='"' || c=='*' || c=='/' || c==':' || c=='<' ||
+	      c=='>' || c=='?' || c=='\\' || c=='|';
+}
+
+static inline int no_dos_char(unsigned char c)
+{	/* Characters that are allowed in HPFS but not in DOS */
+	return c=='+' || c==',' || c==';' || c=='=' || c=='[' || c==']';
+}
+
+static inline unsigned char upcase(unsigned char *dir, unsigned char a)
+{
+	if (a<128 || a==255) return a>='a' && a<='z' ? a - 0x20 : a;
+	if (!dir) return a;
+	return dir[a-128];
+}
+
+unsigned char hpfs_upcase(unsigned char *dir, unsigned char a)
+{
+	return upcase(dir, a);
+}
+
+static inline unsigned char locase(unsigned char *dir, unsigned char a)
+{
+	if (a<128 || a==255) return a>='A' && a<='Z' ? a + 0x20 : a;
+	if (!dir) return a;
+	return dir[a];
+}
+
+int hpfs_chk_name(unsigned char *name, unsigned *len)
+{
+	int i;
+	if (*len > 254) return -ENAMETOOLONG;
+	hpfs_adjust_length(name, len);
+	if (!*len) return -EINVAL;
+	for (i = 0; i < *len; i++) if (not_allowed_char(name[i])) return -EINVAL;
+	if (*len == 1) if (name[0] == '.') return -EINVAL;
+	if (*len == 2) if (name[0] == '.' && name[1] == '.') return -EINVAL;
+	return 0;
+}
+
+char *hpfs_translate_name(struct super_block *s, unsigned char *from,
+			  unsigned len, int lc, int lng)
+{
+	char *to;
+	int i;
+	if (hpfs_sb(s)->sb_chk >= 2) if (hpfs_is_name_long(from, len) != lng) {
+		printk("HPFS: Long name flag mismatch - name ");
+		for (i=0; i<len; i++) printk("%c", from[i]);
+		printk(" misidentified as %s.\n", lng ? "short" : "long");
+		printk("HPFS: It's nothing serious. It could happen because of bug in OS/2.\nHPFS: Set checks=normal to disable this message.\n");
+	}
+	if (!lc) return from;
+	if (!(to = kmalloc(len, GFP_KERNEL))) {
+		printk("HPFS: can't allocate memory for name conversion buffer\n");
+		return from;
+	}
+	for (i = 0; i < len; i++) to[i] = locase(hpfs_sb(s)->sb_cp_table,from[i]);
+	return to;
+}
+
+int hpfs_compare_names(struct super_block *s, unsigned char *n1, unsigned l1,
+		       unsigned char *n2, unsigned l2, int last)
+{
+	unsigned l = l1 < l2 ? l1 : l2;
+	unsigned i;
+	if (last) return -1;
+	for (i = 0; i < l; i++) {
+		unsigned char c1 = upcase(hpfs_sb(s)->sb_cp_table,n1[i]);
+		unsigned char c2 = upcase(hpfs_sb(s)->sb_cp_table,n2[i]);
+		if (c1 < c2) return -1;
+		if (c1 > c2) return 1;
+	}
+	if (l1 < l2) return -1;
+	if (l1 > l2) return 1;
+	return 0;
+}
+
+int hpfs_is_name_long(unsigned char *name, unsigned len)
+{
+	int i,j;
+	for (i = 0; i < len && name[i] != '.'; i++)
+		if (no_dos_char(name[i])) return 1;
+	if (!i || i > 8) return 1;
+	if (i == len) return 0;
+	for (j = i + 1; j < len; j++)
+		if (name[j] == '.' || no_dos_char(name[i])) return 1;
+	return j - i > 4;
+}
+
+/* OS/2 clears dots and spaces at the end of file name, so we have to */
+
+void hpfs_adjust_length(unsigned char *name, unsigned *len)
+{
+	if (!*len) return;
+	if (*len == 1 && name[0] == '.') return;
+	if (*len == 2 && name[0] == '.' && name[1] == '.') return;
+	while (*len && (name[*len - 1] == '.' || name[*len - 1] == ' '))
+		(*len)--;
+}
diff --git a/fs/hpfs/namei.c b/fs/hpfs/namei.c
new file mode 100644
index 0000000..8ff8fc4
--- /dev/null
+++ b/fs/hpfs/namei.c
@@ -0,0 +1,673 @@
+/*
+ *  linux/fs/hpfs/namei.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  adding & removing files & directories
+ */
+
+#include "hpfs_fn.h"
+
+static int hpfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
+{
+	const char *name = dentry->d_name.name;
+	unsigned len = dentry->d_name.len;
+	struct quad_buffer_head qbh0;
+	struct buffer_head *bh;
+	struct hpfs_dirent *de;
+	struct fnode *fnode;
+	struct dnode *dnode;
+	struct inode *result;
+	fnode_secno fno;
+	dnode_secno dno;
+	int r;
+	struct hpfs_dirent dee;
+	int err;
+	if ((err = hpfs_chk_name((char *)name, &len))) return err==-ENOENT ? -EINVAL : err;
+	lock_kernel();
+	err = -ENOSPC;
+	fnode = hpfs_alloc_fnode(dir->i_sb, hpfs_i(dir)->i_dno, &fno, &bh);
+	if (!fnode)
+		goto bail;
+	dnode = hpfs_alloc_dnode(dir->i_sb, fno, &dno, &qbh0, 1);
+	if (!dnode)
+		goto bail1;
+	memset(&dee, 0, sizeof dee);
+	dee.directory = 1;
+	if (!(mode & 0222)) dee.read_only = 1;
+	/*dee.archive = 0;*/
+	dee.hidden = name[0] == '.';
+	dee.fnode = fno;
+	dee.creation_date = dee.write_date = dee.read_date = gmt_to_local(dir->i_sb, get_seconds());
+	result = new_inode(dir->i_sb);
+	if (!result)
+		goto bail2;
+	hpfs_init_inode(result);
+	result->i_ino = fno;
+	hpfs_i(result)->i_parent_dir = dir->i_ino;
+	hpfs_i(result)->i_dno = dno;
+	result->i_ctime.tv_sec = result->i_mtime.tv_sec = result->i_atime.tv_sec = local_to_gmt(dir->i_sb, dee.creation_date);
+	result->i_ctime.tv_nsec = 0; 
+	result->i_mtime.tv_nsec = 0; 
+	result->i_atime.tv_nsec = 0; 
+	hpfs_i(result)->i_ea_size = 0;
+	result->i_mode |= S_IFDIR;
+	result->i_op = &hpfs_dir_iops;
+	result->i_fop = &hpfs_dir_ops;
+	result->i_blocks = 4;
+	result->i_size = 2048;
+	result->i_nlink = 2;
+	if (dee.read_only)
+		result->i_mode &= ~0222;
+
+	down(&hpfs_i(dir)->i_sem);
+	r = hpfs_add_dirent(dir, (char *)name, len, &dee, 0);
+	if (r == 1)
+		goto bail3;
+	if (r == -1) {
+		err = -EEXIST;
+		goto bail3;
+	}
+	fnode->len = len;
+	memcpy(fnode->name, name, len > 15 ? 15 : len);
+	fnode->up = dir->i_ino;
+	fnode->dirflag = 1;
+	fnode->btree.n_free_nodes = 7;
+	fnode->btree.n_used_nodes = 1;
+	fnode->btree.first_free = 0x14;
+	fnode->u.external[0].disk_secno = dno;
+	fnode->u.external[0].file_secno = -1;
+	dnode->root_dnode = 1;
+	dnode->up = fno;
+	de = hpfs_add_de(dir->i_sb, dnode, "\001\001", 2, 0);
+	de->creation_date = de->write_date = de->read_date = gmt_to_local(dir->i_sb, get_seconds());
+	if (!(mode & 0222)) de->read_only = 1;
+	de->first = de->directory = 1;
+	/*de->hidden = de->system = 0;*/
+	de->fnode = fno;
+	mark_buffer_dirty(bh);
+	brelse(bh);
+	hpfs_mark_4buffers_dirty(&qbh0);
+	hpfs_brelse4(&qbh0);
+	dir->i_nlink++;
+	insert_inode_hash(result);
+
+	if (result->i_uid != current->fsuid ||
+	    result->i_gid != current->fsgid ||
+	    result->i_mode != (mode | S_IFDIR)) {
+		result->i_uid = current->fsuid;
+		result->i_gid = current->fsgid;
+		result->i_mode = mode | S_IFDIR;
+		hpfs_write_inode_nolock(result);
+	}
+	d_instantiate(dentry, result);
+	up(&hpfs_i(dir)->i_sem);
+	unlock_kernel();
+	return 0;
+bail3:
+	up(&hpfs_i(dir)->i_sem);
+	iput(result);
+bail2:
+	hpfs_brelse4(&qbh0);
+	hpfs_free_dnode(dir->i_sb, dno);
+bail1:
+	brelse(bh);
+	hpfs_free_sectors(dir->i_sb, fno, 1);
+bail:
+	unlock_kernel();
+	return err;
+}
+
+static int hpfs_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd)
+{
+	const char *name = dentry->d_name.name;
+	unsigned len = dentry->d_name.len;
+	struct inode *result = NULL;
+	struct buffer_head *bh;
+	struct fnode *fnode;
+	fnode_secno fno;
+	int r;
+	struct hpfs_dirent dee;
+	int err;
+	if ((err = hpfs_chk_name((char *)name, &len)))
+		return err==-ENOENT ? -EINVAL : err;
+	lock_kernel();
+	err = -ENOSPC;
+	fnode = hpfs_alloc_fnode(dir->i_sb, hpfs_i(dir)->i_dno, &fno, &bh);
+	if (!fnode)
+		goto bail;
+	memset(&dee, 0, sizeof dee);
+	if (!(mode & 0222)) dee.read_only = 1;
+	dee.archive = 1;
+	dee.hidden = name[0] == '.';
+	dee.fnode = fno;
+	dee.creation_date = dee.write_date = dee.read_date = gmt_to_local(dir->i_sb, get_seconds());
+
+	result = new_inode(dir->i_sb);
+	if (!result)
+		goto bail1;
+	
+	hpfs_init_inode(result);
+	result->i_ino = fno;
+	result->i_mode |= S_IFREG;
+	result->i_mode &= ~0111;
+	result->i_op = &hpfs_file_iops;
+	result->i_fop = &hpfs_file_ops;
+	result->i_nlink = 1;
+	hpfs_decide_conv(result, (char *)name, len);
+	hpfs_i(result)->i_parent_dir = dir->i_ino;
+	result->i_ctime.tv_sec = result->i_mtime.tv_sec = result->i_atime.tv_sec = local_to_gmt(dir->i_sb, dee.creation_date);
+	result->i_ctime.tv_nsec = 0;
+	result->i_mtime.tv_nsec = 0;
+	result->i_atime.tv_nsec = 0;
+	hpfs_i(result)->i_ea_size = 0;
+	if (dee.read_only)
+		result->i_mode &= ~0222;
+	result->i_blocks = 1;
+	result->i_size = 0;
+	result->i_data.a_ops = &hpfs_aops;
+	hpfs_i(result)->mmu_private = 0;
+
+	down(&hpfs_i(dir)->i_sem);
+	r = hpfs_add_dirent(dir, (char *)name, len, &dee, 0);
+	if (r == 1)
+		goto bail2;
+	if (r == -1) {
+		err = -EEXIST;
+		goto bail2;
+	}
+	fnode->len = len;
+	memcpy(fnode->name, name, len > 15 ? 15 : len);
+	fnode->up = dir->i_ino;
+	mark_buffer_dirty(bh);
+	brelse(bh);
+
+	insert_inode_hash(result);
+
+	if (result->i_uid != current->fsuid ||
+	    result->i_gid != current->fsgid ||
+	    result->i_mode != (mode | S_IFREG)) {
+		result->i_uid = current->fsuid;
+		result->i_gid = current->fsgid;
+		result->i_mode = mode | S_IFREG;
+		hpfs_write_inode_nolock(result);
+	}
+	d_instantiate(dentry, result);
+	up(&hpfs_i(dir)->i_sem);
+	unlock_kernel();
+	return 0;
+
+bail2:
+	up(&hpfs_i(dir)->i_sem);
+	iput(result);
+bail1:
+	brelse(bh);
+	hpfs_free_sectors(dir->i_sb, fno, 1);
+bail:
+	unlock_kernel();
+	return err;
+}
+
+static int hpfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t rdev)
+{
+	const char *name = dentry->d_name.name;
+	unsigned len = dentry->d_name.len;
+	struct buffer_head *bh;
+	struct fnode *fnode;
+	fnode_secno fno;
+	int r;
+	struct hpfs_dirent dee;
+	struct inode *result = NULL;
+	int err;
+	if ((err = hpfs_chk_name((char *)name, &len))) return err==-ENOENT ? -EINVAL : err;
+	if (hpfs_sb(dir->i_sb)->sb_eas < 2) return -EPERM;
+	if (!new_valid_dev(rdev))
+		return -EINVAL;
+	lock_kernel();
+	err = -ENOSPC;
+	fnode = hpfs_alloc_fnode(dir->i_sb, hpfs_i(dir)->i_dno, &fno, &bh);
+	if (!fnode)
+		goto bail;
+	memset(&dee, 0, sizeof dee);
+	if (!(mode & 0222)) dee.read_only = 1;
+	dee.archive = 1;
+	dee.hidden = name[0] == '.';
+	dee.fnode = fno;
+	dee.creation_date = dee.write_date = dee.read_date = gmt_to_local(dir->i_sb, get_seconds());
+
+	result = new_inode(dir->i_sb);
+	if (!result)
+		goto bail1;
+
+	hpfs_init_inode(result);
+	result->i_ino = fno;
+	hpfs_i(result)->i_parent_dir = dir->i_ino;
+	result->i_ctime.tv_sec = result->i_mtime.tv_sec = result->i_atime.tv_sec = local_to_gmt(dir->i_sb, dee.creation_date);
+	result->i_ctime.tv_nsec = 0;
+	result->i_mtime.tv_nsec = 0;
+	result->i_atime.tv_nsec = 0;
+	hpfs_i(result)->i_ea_size = 0;
+	result->i_uid = current->fsuid;
+	result->i_gid = current->fsgid;
+	result->i_nlink = 1;
+	result->i_size = 0;
+	result->i_blocks = 1;
+	init_special_inode(result, mode, rdev);
+
+	down(&hpfs_i(dir)->i_sem);
+	r = hpfs_add_dirent(dir, (char *)name, len, &dee, 0);
+	if (r == 1)
+		goto bail2;
+	if (r == -1) {
+		err = -EEXIST;
+		goto bail2;
+	}
+	fnode->len = len;
+	memcpy(fnode->name, name, len > 15 ? 15 : len);
+	fnode->up = dir->i_ino;
+	mark_buffer_dirty(bh);
+
+	insert_inode_hash(result);
+
+	hpfs_write_inode_nolock(result);
+	d_instantiate(dentry, result);
+	up(&hpfs_i(dir)->i_sem);
+	brelse(bh);
+	unlock_kernel();
+	return 0;
+bail2:
+	up(&hpfs_i(dir)->i_sem);
+	iput(result);
+bail1:
+	brelse(bh);
+	hpfs_free_sectors(dir->i_sb, fno, 1);
+bail:
+	unlock_kernel();
+	return err;
+}
+
+static int hpfs_symlink(struct inode *dir, struct dentry *dentry, const char *symlink)
+{
+	const char *name = dentry->d_name.name;
+	unsigned len = dentry->d_name.len;
+	struct buffer_head *bh;
+	struct fnode *fnode;
+	fnode_secno fno;
+	int r;
+	struct hpfs_dirent dee;
+	struct inode *result;
+	int err;
+	if ((err = hpfs_chk_name((char *)name, &len))) return err==-ENOENT ? -EINVAL : err;
+	lock_kernel();
+	if (hpfs_sb(dir->i_sb)->sb_eas < 2) {
+		unlock_kernel();
+		return -EPERM;
+	}
+	err = -ENOSPC;
+	fnode = hpfs_alloc_fnode(dir->i_sb, hpfs_i(dir)->i_dno, &fno, &bh);
+	if (!fnode)
+		goto bail;
+	memset(&dee, 0, sizeof dee);
+	dee.archive = 1;
+	dee.hidden = name[0] == '.';
+	dee.fnode = fno;
+	dee.creation_date = dee.write_date = dee.read_date = gmt_to_local(dir->i_sb, get_seconds());
+
+	result = new_inode(dir->i_sb);
+	if (!result)
+		goto bail1;
+	result->i_ino = fno;
+	hpfs_init_inode(result);
+	hpfs_i(result)->i_parent_dir = dir->i_ino;
+	result->i_ctime.tv_sec = result->i_mtime.tv_sec = result->i_atime.tv_sec = local_to_gmt(dir->i_sb, dee.creation_date);
+	result->i_ctime.tv_nsec = 0;
+	result->i_mtime.tv_nsec = 0;
+	result->i_atime.tv_nsec = 0;
+	hpfs_i(result)->i_ea_size = 0;
+	result->i_mode = S_IFLNK | 0777;
+	result->i_uid = current->fsuid;
+	result->i_gid = current->fsgid;
+	result->i_blocks = 1;
+	result->i_nlink = 1;
+	result->i_size = strlen(symlink);
+	result->i_op = &page_symlink_inode_operations;
+	result->i_data.a_ops = &hpfs_symlink_aops;
+
+	down(&hpfs_i(dir)->i_sem);
+	r = hpfs_add_dirent(dir, (char *)name, len, &dee, 0);
+	if (r == 1)
+		goto bail2;
+	if (r == -1) {
+		err = -EEXIST;
+		goto bail2;
+	}
+	fnode->len = len;
+	memcpy(fnode->name, name, len > 15 ? 15 : len);
+	fnode->up = dir->i_ino;
+	hpfs_set_ea(result, fnode, "SYMLINK", (char *)symlink, strlen(symlink));
+	mark_buffer_dirty(bh);
+	brelse(bh);
+
+	insert_inode_hash(result);
+
+	hpfs_write_inode_nolock(result);
+	d_instantiate(dentry, result);
+	up(&hpfs_i(dir)->i_sem);
+	unlock_kernel();
+	return 0;
+bail2:
+	up(&hpfs_i(dir)->i_sem);
+	iput(result);
+bail1:
+	brelse(bh);
+	hpfs_free_sectors(dir->i_sb, fno, 1);
+bail:
+	unlock_kernel();
+	return err;
+}
+
+static int hpfs_unlink(struct inode *dir, struct dentry *dentry)
+{
+	const char *name = dentry->d_name.name;
+	unsigned len = dentry->d_name.len;
+	struct quad_buffer_head qbh;
+	struct hpfs_dirent *de;
+	struct inode *inode = dentry->d_inode;
+	dnode_secno dno;
+	fnode_secno fno;
+	int r;
+	int rep = 0;
+	int err;
+
+	lock_kernel();
+	hpfs_adjust_length((char *)name, &len);
+again:
+	down(&hpfs_i(inode)->i_parent);
+	down(&hpfs_i(dir)->i_sem);
+	err = -ENOENT;
+	de = map_dirent(dir, hpfs_i(dir)->i_dno, (char *)name, len, &dno, &qbh);
+	if (!de)
+		goto out;
+
+	err = -EPERM;
+	if (de->first)
+		goto out1;
+
+	err = -EISDIR;
+	if (de->directory)
+		goto out1;
+
+	fno = de->fnode;
+	r = hpfs_remove_dirent(dir, dno, de, &qbh, 1);
+	switch (r) {
+	case 1:
+		hpfs_error(dir->i_sb, "there was error when removing dirent");
+		err = -EFSERROR;
+		break;
+	case 2:		/* no space for deleting, try to truncate file */
+
+		err = -ENOSPC;
+		if (rep++)
+			break;
+
+		up(&hpfs_i(dir)->i_sem);
+		up(&hpfs_i(inode)->i_parent);
+		d_drop(dentry);
+		spin_lock(&dentry->d_lock);
+		if (atomic_read(&dentry->d_count) > 1 ||
+		    permission(inode, MAY_WRITE, NULL) ||
+		    !S_ISREG(inode->i_mode) ||
+		    get_write_access(inode)) {
+			spin_unlock(&dentry->d_lock);
+			d_rehash(dentry);
+		} else {
+			struct iattr newattrs;
+			spin_unlock(&dentry->d_lock);
+			/*printk("HPFS: truncating file before delete.\n");*/
+			newattrs.ia_size = 0;
+			newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME;
+			err = notify_change(dentry, &newattrs);
+			put_write_access(inode);
+			if (!err)
+				goto again;
+		}
+		unlock_kernel();
+		return -ENOSPC;
+	default:
+		inode->i_nlink--;
+		err = 0;
+	}
+	goto out;
+
+out1:
+	hpfs_brelse4(&qbh);
+out:
+	up(&hpfs_i(dir)->i_sem);
+	up(&hpfs_i(inode)->i_parent);
+	unlock_kernel();
+	return err;
+}
+
+static int hpfs_rmdir(struct inode *dir, struct dentry *dentry)
+{
+	const char *name = dentry->d_name.name;
+	unsigned len = dentry->d_name.len;
+	struct quad_buffer_head qbh;
+	struct hpfs_dirent *de;
+	struct inode *inode = dentry->d_inode;
+	dnode_secno dno;
+	fnode_secno fno;
+	int n_items = 0;
+	int err;
+	int r;
+
+	hpfs_adjust_length((char *)name, &len);
+	lock_kernel();
+	down(&hpfs_i(inode)->i_parent);
+	down(&hpfs_i(dir)->i_sem);
+	err = -ENOENT;
+	de = map_dirent(dir, hpfs_i(dir)->i_dno, (char *)name, len, &dno, &qbh);
+	if (!de)
+		goto out;
+
+	err = -EPERM;
+	if (de->first)
+		goto out1;
+
+	err = -ENOTDIR;
+	if (!de->directory)
+		goto out1;
+
+	hpfs_count_dnodes(dir->i_sb, hpfs_i(inode)->i_dno, NULL, NULL, &n_items);
+	err = -ENOTEMPTY;
+	if (n_items)
+		goto out1;
+
+	fno = de->fnode;
+	r = hpfs_remove_dirent(dir, dno, de, &qbh, 1);
+	switch (r) {
+	case 1:
+		hpfs_error(dir->i_sb, "there was error when removing dirent");
+		err = -EFSERROR;
+		break;
+	case 2:
+		err = -ENOSPC;
+		break;
+	default:
+		dir->i_nlink--;
+		inode->i_nlink = 0;
+		err = 0;
+	}
+	goto out;
+out1:
+	hpfs_brelse4(&qbh);
+out:
+	up(&hpfs_i(dir)->i_sem);
+	up(&hpfs_i(inode)->i_parent);
+	unlock_kernel();
+	return err;
+}
+
+static int hpfs_symlink_readpage(struct file *file, struct page *page)
+{
+	char *link = kmap(page);
+	struct inode *i = page->mapping->host;
+	struct fnode *fnode;
+	struct buffer_head *bh;
+	int err;
+
+	err = -EIO;
+	lock_kernel();
+	if (!(fnode = hpfs_map_fnode(i->i_sb, i->i_ino, &bh)))
+		goto fail;
+	err = hpfs_read_ea(i->i_sb, fnode, "SYMLINK", link, PAGE_SIZE);
+	brelse(bh);
+	if (err)
+		goto fail;
+	unlock_kernel();
+	SetPageUptodate(page);
+	kunmap(page);
+	unlock_page(page);
+	return 0;
+
+fail:
+	unlock_kernel();
+	SetPageError(page);
+	kunmap(page);
+	unlock_page(page);
+	return err;
+}
+
+struct address_space_operations hpfs_symlink_aops = {
+	.readpage	= hpfs_symlink_readpage
+};
+	
+static int hpfs_rename(struct inode *old_dir, struct dentry *old_dentry,
+		struct inode *new_dir, struct dentry *new_dentry)
+{
+	char *old_name = (char *)old_dentry->d_name.name;
+	int old_len = old_dentry->d_name.len;
+	char *new_name = (char *)new_dentry->d_name.name;
+	int new_len = new_dentry->d_name.len;
+	struct inode *i = old_dentry->d_inode;
+	struct inode *new_inode = new_dentry->d_inode;
+	struct quad_buffer_head qbh, qbh1;
+	struct hpfs_dirent *dep, *nde;
+	struct hpfs_dirent de;
+	dnode_secno dno;
+	int r;
+	struct buffer_head *bh;
+	struct fnode *fnode;
+	int err;
+	if ((err = hpfs_chk_name((char *)new_name, &new_len))) return err;
+	err = 0;
+	hpfs_adjust_length((char *)old_name, &old_len);
+
+	lock_kernel();
+	/* order doesn't matter, due to VFS exclusion */
+	down(&hpfs_i(i)->i_parent);
+	if (new_inode)
+		down(&hpfs_i(new_inode)->i_parent);
+	down(&hpfs_i(old_dir)->i_sem);
+	if (new_dir != old_dir)
+		down(&hpfs_i(new_dir)->i_sem);
+	
+	/* Erm? Moving over the empty non-busy directory is perfectly legal */
+	if (new_inode && S_ISDIR(new_inode->i_mode)) {
+		err = -EINVAL;
+		goto end1;
+	}
+
+	if (!(dep = map_dirent(old_dir, hpfs_i(old_dir)->i_dno, (char *)old_name, old_len, &dno, &qbh))) {
+		hpfs_error(i->i_sb, "lookup succeeded but map dirent failed");
+		err = -ENOENT;
+		goto end1;
+	}
+	copy_de(&de, dep);
+	de.hidden = new_name[0] == '.';
+
+	if (new_inode) {
+		int r;
+		if ((r = hpfs_remove_dirent(old_dir, dno, dep, &qbh, 1)) != 2) {
+			if ((nde = map_dirent(new_dir, hpfs_i(new_dir)->i_dno, (char *)new_name, new_len, NULL, &qbh1))) {
+				new_inode->i_nlink = 0;
+				copy_de(nde, &de);
+				memcpy(nde->name, new_name, new_len);
+				hpfs_mark_4buffers_dirty(&qbh1);
+				hpfs_brelse4(&qbh1);
+				goto end;
+			}
+			hpfs_error(new_dir->i_sb, "hpfs_rename: could not find dirent");
+			err = -EFSERROR;
+			goto end1;
+		}
+		err = r == 2 ? -ENOSPC : r == 1 ? -EFSERROR : 0;
+		goto end1;
+	}
+
+	if (new_dir == old_dir) hpfs_brelse4(&qbh);
+
+	hpfs_lock_creation(i->i_sb);
+	if ((r = hpfs_add_dirent(new_dir, new_name, new_len, &de, 1))) {
+		hpfs_unlock_creation(i->i_sb);
+		if (r == -1) hpfs_error(new_dir->i_sb, "hpfs_rename: dirent already exists!");
+		err = r == 1 ? -ENOSPC : -EFSERROR;
+		if (new_dir != old_dir) hpfs_brelse4(&qbh);
+		goto end1;
+	}
+	
+	if (new_dir == old_dir)
+		if (!(dep = map_dirent(old_dir, hpfs_i(old_dir)->i_dno, (char *)old_name, old_len, &dno, &qbh))) {
+			hpfs_unlock_creation(i->i_sb);
+			hpfs_error(i->i_sb, "lookup succeeded but map dirent failed at #2");
+			err = -ENOENT;
+			goto end1;
+		}
+
+	if ((r = hpfs_remove_dirent(old_dir, dno, dep, &qbh, 0))) {
+		hpfs_unlock_creation(i->i_sb);
+		hpfs_error(i->i_sb, "hpfs_rename: could not remove dirent");
+		err = r == 2 ? -ENOSPC : -EFSERROR;
+		goto end1;
+	}
+	hpfs_unlock_creation(i->i_sb);
+	
+	end:
+	hpfs_i(i)->i_parent_dir = new_dir->i_ino;
+	if (S_ISDIR(i->i_mode)) {
+		new_dir->i_nlink++;
+		old_dir->i_nlink--;
+	}
+	if ((fnode = hpfs_map_fnode(i->i_sb, i->i_ino, &bh))) {
+		fnode->up = new_dir->i_ino;
+		fnode->len = new_len;
+		memcpy(fnode->name, new_name, new_len>15?15:new_len);
+		if (new_len < 15) memset(&fnode->name[new_len], 0, 15 - new_len);
+		mark_buffer_dirty(bh);
+		brelse(bh);
+	}
+	hpfs_i(i)->i_conv = hpfs_sb(i->i_sb)->sb_conv;
+	hpfs_decide_conv(i, (char *)new_name, new_len);
+end1:
+	if (old_dir != new_dir)
+		up(&hpfs_i(new_dir)->i_sem);
+	up(&hpfs_i(old_dir)->i_sem);
+	up(&hpfs_i(i)->i_parent);
+	if (new_inode)
+		up(&hpfs_i(new_inode)->i_parent);
+	unlock_kernel();
+	return err;
+}
+
+struct inode_operations hpfs_dir_iops =
+{
+	.create		= hpfs_create,
+	.lookup		= hpfs_lookup,
+	.unlink		= hpfs_unlink,
+	.symlink	= hpfs_symlink,
+	.mkdir		= hpfs_mkdir,
+	.rmdir		= hpfs_rmdir,
+	.mknod		= hpfs_mknod,
+	.rename		= hpfs_rename,
+	.setattr	= hpfs_notify_change,
+};
diff --git a/fs/hpfs/super.c b/fs/hpfs/super.c
new file mode 100644
index 0000000..8eefa63
--- /dev/null
+++ b/fs/hpfs/super.c
@@ -0,0 +1,701 @@
+/*
+ *  linux/fs/hpfs/super.c
+ *
+ *  Mikulas Patocka (mikulas@artax.karlin.mff.cuni.cz), 1998-1999
+ *
+ *  mounting, unmounting, error handling
+ */
+
+#include "hpfs_fn.h"
+#include <linux/module.h>
+#include <linux/parser.h>
+#include <linux/init.h>
+#include <linux/statfs.h>
+
+/* Mark the filesystem dirty, so that chkdsk checks it when os/2 booted */
+
+static void mark_dirty(struct super_block *s)
+{
+	if (hpfs_sb(s)->sb_chkdsk && !(s->s_flags & MS_RDONLY)) {
+		struct buffer_head *bh;
+		struct hpfs_spare_block *sb;
+		if ((sb = hpfs_map_sector(s, 17, &bh, 0))) {
+			sb->dirty = 1;
+			sb->old_wrote = 0;
+			mark_buffer_dirty(bh);
+			brelse(bh);
+		}
+	}
+}
+
+/* Mark the filesystem clean (mark it dirty for chkdsk if chkdsk==2 or if there
+   were errors) */
+
+static void unmark_dirty(struct super_block *s)
+{
+	struct buffer_head *bh;
+	struct hpfs_spare_block *sb;
+	if (s->s_flags & MS_RDONLY) return;
+	if ((sb = hpfs_map_sector(s, 17, &bh, 0))) {
+		sb->dirty = hpfs_sb(s)->sb_chkdsk > 1 - hpfs_sb(s)->sb_was_error;
+		sb->old_wrote = hpfs_sb(s)->sb_chkdsk >= 2 && !hpfs_sb(s)->sb_was_error;
+		mark_buffer_dirty(bh);
+		brelse(bh);
+	}
+}
+
+/* Filesystem error... */
+
+#define ERR_BUF_SIZE 1024
+
+void hpfs_error(struct super_block *s, char *m,...)
+{
+	char *buf;
+	va_list l;
+	va_start(l, m);
+	if (!(buf = kmalloc(ERR_BUF_SIZE, GFP_KERNEL)))
+		printk("HPFS: No memory for error message '%s'\n",m);
+	else if (vsprintf(buf, m, l) >= ERR_BUF_SIZE)
+		printk("HPFS: Grrrr... Kernel memory corrupted ... going on, but it'll crash very soon :-(\n");
+	printk("HPFS: filesystem error: ");
+	if (buf) printk("%s", buf);
+	else printk("%s\n",m);
+	if (!hpfs_sb(s)->sb_was_error) {
+		if (hpfs_sb(s)->sb_err == 2) {
+			printk("; crashing the system because you wanted it\n");
+			mark_dirty(s);
+			panic("HPFS panic");
+		} else if (hpfs_sb(s)->sb_err == 1) {
+			if (s->s_flags & MS_RDONLY) printk("; already mounted read-only\n");
+			else {
+				printk("; remounting read-only\n");
+				mark_dirty(s);
+				s->s_flags |= MS_RDONLY;
+			}
+		} else if (s->s_flags & MS_RDONLY) printk("; going on - but anything won't be destroyed because it's read-only\n");
+		else printk("; corrupted filesystem mounted read/write - your computer will explode within 20 seconds ... but you wanted it so!\n");
+	} else printk("\n");
+	if (buf) kfree(buf);
+	hpfs_sb(s)->sb_was_error = 1;
+}
+
+/* 
+ * A little trick to detect cycles in many hpfs structures and don't let the
+ * kernel crash on corrupted filesystem. When first called, set c2 to 0.
+ *
+ * BTW. chkdsk doesn't detect cycles correctly. When I had 2 lost directories
+ * nested each in other, chkdsk locked up happilly.
+ */
+
+int hpfs_stop_cycles(struct super_block *s, int key, int *c1, int *c2,
+		char *msg)
+{
+	if (*c2 && *c1 == key) {
+		hpfs_error(s, "cycle detected on key %08x in %s", key, msg);
+		return 1;
+	}
+	(*c2)++;
+	if (!((*c2 - 1) & *c2)) *c1 = key;
+	return 0;
+}
+
+static void hpfs_put_super(struct super_block *s)
+{
+	struct hpfs_sb_info *sbi = hpfs_sb(s);
+	if (sbi->sb_cp_table) kfree(sbi->sb_cp_table);
+	if (sbi->sb_bmp_dir) kfree(sbi->sb_bmp_dir);
+	unmark_dirty(s);
+	s->s_fs_info = NULL;
+	kfree(sbi);
+}
+
+unsigned hpfs_count_one_bitmap(struct super_block *s, secno secno)
+{
+	struct quad_buffer_head qbh;
+	unsigned *bits;
+	unsigned i, count;
+	if (!(bits = hpfs_map_4sectors(s, secno, &qbh, 4))) return 0;
+	count = 0;
+	for (i = 0; i < 2048 / sizeof(unsigned); i++) {
+		unsigned b; 
+		if (!bits[i]) continue;
+		for (b = bits[i]; b; b>>=1) count += b & 1;
+	}
+	hpfs_brelse4(&qbh);
+	return count;
+}
+
+static unsigned count_bitmaps(struct super_block *s)
+{
+	unsigned n, count, n_bands;
+	n_bands = (hpfs_sb(s)->sb_fs_size + 0x3fff) >> 14;
+	count = 0;
+	for (n = 0; n < n_bands; n++)
+		count += hpfs_count_one_bitmap(s, hpfs_sb(s)->sb_bmp_dir[n]);
+	return count;
+}
+
+static int hpfs_statfs(struct super_block *s, struct kstatfs *buf)
+{
+	struct hpfs_sb_info *sbi = hpfs_sb(s);
+	lock_kernel();
+
+	/*if (sbi->sb_n_free == -1) {*/
+		sbi->sb_n_free = count_bitmaps(s);
+		sbi->sb_n_free_dnodes = hpfs_count_one_bitmap(s, sbi->sb_dmap);
+	/*}*/
+	buf->f_type = s->s_magic;
+	buf->f_bsize = 512;
+	buf->f_blocks = sbi->sb_fs_size;
+	buf->f_bfree = sbi->sb_n_free;
+	buf->f_bavail = sbi->sb_n_free;
+	buf->f_files = sbi->sb_dirband_size / 4;
+	buf->f_ffree = sbi->sb_n_free_dnodes;
+	buf->f_namelen = 254;
+
+	unlock_kernel();
+
+	return 0;
+}
+
+static kmem_cache_t * hpfs_inode_cachep;
+
+static struct inode *hpfs_alloc_inode(struct super_block *sb)
+{
+	struct hpfs_inode_info *ei;
+	ei = (struct hpfs_inode_info *)kmem_cache_alloc(hpfs_inode_cachep, SLAB_NOFS);
+	if (!ei)
+		return NULL;
+	ei->vfs_inode.i_version = 1;
+	return &ei->vfs_inode;
+}
+
+static void hpfs_destroy_inode(struct inode *inode)
+{
+	kmem_cache_free(hpfs_inode_cachep, hpfs_i(inode));
+}
+
+static void init_once(void * foo, kmem_cache_t * cachep, unsigned long flags)
+{
+	struct hpfs_inode_info *ei = (struct hpfs_inode_info *) foo;
+
+	if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
+	    SLAB_CTOR_CONSTRUCTOR) {
+		init_MUTEX(&ei->i_sem);
+		init_MUTEX(&ei->i_parent);
+		inode_init_once(&ei->vfs_inode);
+	}
+}
+ 
+static int init_inodecache(void)
+{
+	hpfs_inode_cachep = kmem_cache_create("hpfs_inode_cache",
+					     sizeof(struct hpfs_inode_info),
+					     0, SLAB_RECLAIM_ACCOUNT,
+					     init_once, NULL);
+	if (hpfs_inode_cachep == NULL)
+		return -ENOMEM;
+	return 0;
+}
+
+static void destroy_inodecache(void)
+{
+	if (kmem_cache_destroy(hpfs_inode_cachep))
+		printk(KERN_INFO "hpfs_inode_cache: not all structures were freed\n");
+}
+
+/*
+ * A tiny parser for option strings, stolen from dosfs.
+ * Stolen again from read-only hpfs.
+ * And updated for table-driven option parsing.
+ */
+
+enum {
+	Opt_help, Opt_uid, Opt_gid, Opt_umask, Opt_case_lower, Opt_case_asis,
+	Opt_conv_binary, Opt_conv_text, Opt_conv_auto,
+	Opt_check_none, Opt_check_normal, Opt_check_strict,
+	Opt_err_cont, Opt_err_ro, Opt_err_panic,
+	Opt_eas_no, Opt_eas_ro, Opt_eas_rw,
+	Opt_chkdsk_no, Opt_chkdsk_errors, Opt_chkdsk_always,
+	Opt_timeshift, Opt_err,
+};
+
+static match_table_t tokens = {
+	{Opt_help, "help"},
+	{Opt_uid, "uid=%u"},
+	{Opt_gid, "gid=%u"},
+	{Opt_umask, "umask=%o"},
+	{Opt_case_lower, "case=lower"},
+	{Opt_case_asis, "case=asis"},
+	{Opt_conv_binary, "conv=binary"},
+	{Opt_conv_text, "conv=text"},
+	{Opt_conv_auto, "conv=auto"},
+	{Opt_check_none, "check=none"},
+	{Opt_check_normal, "check=normal"},
+	{Opt_check_strict, "check=strict"},
+	{Opt_err_cont, "errors=continue"},
+	{Opt_err_ro, "errors=remount-ro"},
+	{Opt_err_panic, "errors=panic"},
+	{Opt_eas_no, "eas=no"},
+	{Opt_eas_ro, "eas=ro"},
+	{Opt_eas_rw, "eas=rw"},
+	{Opt_chkdsk_no, "chkdsk=no"},
+	{Opt_chkdsk_errors, "chkdsk=errors"},
+	{Opt_chkdsk_always, "chkdsk=always"},
+	{Opt_timeshift, "timeshift=%d"},
+	{Opt_err, NULL},
+};
+
+static int parse_opts(char *opts, uid_t *uid, gid_t *gid, umode_t *umask,
+		      int *lowercase, int *conv, int *eas, int *chk, int *errs,
+		      int *chkdsk, int *timeshift)
+{
+	char *p;
+	int option;
+
+	if (!opts)
+		return 1;
+
+	/*printk("Parsing opts: '%s'\n",opts);*/
+
+	while ((p = strsep(&opts, ",")) != NULL) {
+		substring_t args[MAX_OPT_ARGS];
+		int token;
+		if (!*p)
+			continue;
+
+		token = match_token(p, tokens, args);
+		switch (token) {
+		case Opt_help:
+			return 2;
+		case Opt_uid:
+			if (match_int(args, &option))
+				return 0;
+			*uid = option;
+			break;
+		case Opt_gid:
+			if (match_int(args, &option))
+				return 0;
+			*gid = option;
+			break;
+		case Opt_umask:
+			if (match_octal(args, &option))
+				return 0;
+			*umask = option;
+			break;
+		case Opt_case_lower:
+			*lowercase = 1;
+			break;
+		case Opt_case_asis:
+			*lowercase = 0;
+			break;
+		case Opt_conv_binary:
+			*conv = CONV_BINARY;
+			break;
+		case Opt_conv_text:
+			*conv = CONV_TEXT;
+			break;
+		case Opt_conv_auto:
+			*conv = CONV_AUTO;
+			break;
+		case Opt_check_none:
+			*chk = 0;
+			break;
+		case Opt_check_normal:
+			*chk = 1;
+			break;
+		case Opt_check_strict:
+			*chk = 2;
+			break;
+		case Opt_err_cont:
+			*errs = 0;
+			break;
+		case Opt_err_ro:
+			*errs = 1;
+			break;
+		case Opt_err_panic:
+			*errs = 2;
+			break;
+		case Opt_eas_no:
+			*eas = 0;
+			break;
+		case Opt_eas_ro:
+			*eas = 1;
+			break;
+		case Opt_eas_rw:
+			*eas = 2;
+			break;
+		case Opt_chkdsk_no:
+			*chkdsk = 0;
+			break;
+		case Opt_chkdsk_errors:
+			*chkdsk = 1;
+			break;
+		case Opt_chkdsk_always:
+			*chkdsk = 2;
+			break;
+		case Opt_timeshift:
+		{
+			int m = 1;
+			char *rhs = args[0].from;
+			if (!rhs || !*rhs)
+				return 0;
+			if (*rhs == '-') m = -1;
+			if (*rhs == '+' || *rhs == '-') rhs++;
+			*timeshift = simple_strtoul(rhs, &rhs, 0) * m;
+			if (*rhs)
+				return 0;
+			break;
+		}
+		default:
+			return 0;
+		}
+	}
+	return 1;
+}
+
+static inline void hpfs_help(void)
+{
+	printk("\n\
+HPFS filesystem options:\n\
+      help              do not mount and display this text\n\
+      uid=xxx           set uid of files that don't have uid specified in eas\n\
+      gid=xxx           set gid of files that don't have gid specified in eas\n\
+      umask=xxx         set mode of files that don't have mode specified in eas\n\
+      case=lower        lowercase all files\n\
+      case=asis         do not lowercase files (default)\n\
+      conv=binary       do not convert CR/LF -> LF (default)\n\
+      conv=auto         convert only files with known text extensions\n\
+      conv=text         convert all files\n\
+      check=none        no fs checks - kernel may crash on corrupted filesystem\n\
+      check=normal      do some checks - it should not crash (default)\n\
+      check=strict      do extra time-consuming checks, used for debugging\n\
+      errors=continue   continue on errors\n\
+      errors=remount-ro remount read-only if errors found (default)\n\
+      errors=panic      panic on errors\n\
+      chkdsk=no         do not mark fs for chkdsking even if there were errors\n\
+      chkdsk=errors     mark fs dirty if errors found (default)\n\
+      chkdsk=always     always mark fs dirty - used for debugging\n\
+      eas=no            ignore extended attributes\n\
+      eas=ro            read but do not write extended attributes\n\
+      eas=rw            r/w eas => enables chmod, chown, mknod, ln -s (default)\n\
+      timeshift=nnn	add nnn seconds to file times\n\
+\n");
+}
+
+static int hpfs_remount_fs(struct super_block *s, int *flags, char *data)
+{
+	uid_t uid;
+	gid_t gid;
+	umode_t umask;
+	int lowercase, conv, eas, chk, errs, chkdsk, timeshift;
+	int o;
+	struct hpfs_sb_info *sbi = hpfs_sb(s);
+	
+	*flags |= MS_NOATIME;
+	
+	uid = sbi->sb_uid; gid = sbi->sb_gid;
+	umask = 0777 & ~sbi->sb_mode;
+	lowercase = sbi->sb_lowercase; conv = sbi->sb_conv;
+	eas = sbi->sb_eas; chk = sbi->sb_chk; chkdsk = sbi->sb_chkdsk;
+	errs = sbi->sb_err; timeshift = sbi->sb_timeshift;
+
+	if (!(o = parse_opts(data, &uid, &gid, &umask, &lowercase, &conv,
+	    &eas, &chk, &errs, &chkdsk, &timeshift))) {
+		printk("HPFS: bad mount options.\n");
+	    	return 1;
+	}
+	if (o == 2) {
+		hpfs_help();
+		return 1;
+	}
+	if (timeshift != sbi->sb_timeshift) {
+		printk("HPFS: timeshift can't be changed using remount.\n");
+		return 1;
+	}
+
+	unmark_dirty(s);
+
+	sbi->sb_uid = uid; sbi->sb_gid = gid;
+	sbi->sb_mode = 0777 & ~umask;
+	sbi->sb_lowercase = lowercase; sbi->sb_conv = conv;
+	sbi->sb_eas = eas; sbi->sb_chk = chk; sbi->sb_chkdsk = chkdsk;
+	sbi->sb_err = errs; sbi->sb_timeshift = timeshift;
+
+	if (!(*flags & MS_RDONLY)) mark_dirty(s);
+
+	return 0;
+}
+
+/* Super operations */
+
+static struct super_operations hpfs_sops =
+{
+	.alloc_inode	= hpfs_alloc_inode,
+	.destroy_inode	= hpfs_destroy_inode,
+	.delete_inode	= hpfs_delete_inode,
+	.put_super	= hpfs_put_super,
+	.statfs		= hpfs_statfs,
+	.remount_fs	= hpfs_remount_fs,
+};
+
+static int hpfs_fill_super(struct super_block *s, void *options, int silent)
+{
+	struct buffer_head *bh0, *bh1, *bh2;
+	struct hpfs_boot_block *bootblock;
+	struct hpfs_super_block *superblock;
+	struct hpfs_spare_block *spareblock;
+	struct hpfs_sb_info *sbi;
+	struct inode *root;
+
+	uid_t uid;
+	gid_t gid;
+	umode_t umask;
+	int lowercase, conv, eas, chk, errs, chkdsk, timeshift;
+
+	dnode_secno root_dno;
+	struct hpfs_dirent *de = NULL;
+	struct quad_buffer_head qbh;
+
+	int o;
+
+	sbi = kmalloc(sizeof(*sbi), GFP_KERNEL);
+	if (!sbi)
+		return -ENOMEM;
+	s->s_fs_info = sbi;
+	memset(sbi, 0, sizeof(*sbi));
+
+	sbi->sb_bmp_dir = NULL;
+	sbi->sb_cp_table = NULL;
+
+	init_MUTEX(&sbi->hpfs_creation_de);
+
+	uid = current->uid;
+	gid = current->gid;
+	umask = current->fs->umask;
+	lowercase = 0;
+	conv = CONV_BINARY;
+	eas = 2;
+	chk = 1;
+	errs = 1;
+	chkdsk = 1;
+	timeshift = 0;
+
+	if (!(o = parse_opts(options, &uid, &gid, &umask, &lowercase, &conv,
+	    &eas, &chk, &errs, &chkdsk, &timeshift))) {
+		printk("HPFS: bad mount options.\n");
+		goto bail0;
+	}
+	if (o==2) {
+		hpfs_help();
+		goto bail0;
+	}
+
+	/*sbi->sb_mounting = 1;*/
+	sb_set_blocksize(s, 512);
+	sbi->sb_fs_size = -1;
+	if (!(bootblock = hpfs_map_sector(s, 0, &bh0, 0))) goto bail1;
+	if (!(superblock = hpfs_map_sector(s, 16, &bh1, 1))) goto bail2;
+	if (!(spareblock = hpfs_map_sector(s, 17, &bh2, 0))) goto bail3;
+
+	/* Check magics */
+	if (/*bootblock->magic != BB_MAGIC
+	    ||*/ superblock->magic != SB_MAGIC
+	    || spareblock->magic != SP_MAGIC) {
+		if (!silent) printk("HPFS: Bad magic ... probably not HPFS\n");
+		goto bail4;
+	}
+
+	/* Check version */
+	if (!(s->s_flags & MS_RDONLY) &&
+	      superblock->funcversion != 2 && superblock->funcversion != 3) {
+		printk("HPFS: Bad version %d,%d. Mount readonly to go around\n",
+			(int)superblock->version, (int)superblock->funcversion);
+		printk("HPFS: please try recent version of HPFS driver at http://artax.karlin.mff.cuni.cz/~mikulas/vyplody/hpfs/index-e.cgi and if it still can't understand this format, contact author - mikulas@artax.karlin.mff.cuni.cz\n");
+		goto bail4;
+	}
+
+	s->s_flags |= MS_NOATIME;
+
+	/* Fill superblock stuff */
+	s->s_magic = HPFS_SUPER_MAGIC;
+	s->s_op = &hpfs_sops;
+
+	sbi->sb_root = superblock->root;
+	sbi->sb_fs_size = superblock->n_sectors;
+	sbi->sb_bitmaps = superblock->bitmaps;
+	sbi->sb_dirband_start = superblock->dir_band_start;
+	sbi->sb_dirband_size = superblock->n_dir_band;
+	sbi->sb_dmap = superblock->dir_band_bitmap;
+	sbi->sb_uid = uid;
+	sbi->sb_gid = gid;
+	sbi->sb_mode = 0777 & ~umask;
+	sbi->sb_n_free = -1;
+	sbi->sb_n_free_dnodes = -1;
+	sbi->sb_lowercase = lowercase;
+	sbi->sb_conv = conv;
+	sbi->sb_eas = eas;
+	sbi->sb_chk = chk;
+	sbi->sb_chkdsk = chkdsk;
+	sbi->sb_err = errs;
+	sbi->sb_timeshift = timeshift;
+	sbi->sb_was_error = 0;
+	sbi->sb_cp_table = NULL;
+	sbi->sb_c_bitmap = -1;
+	sbi->sb_max_fwd_alloc = 0xffffff;
+	
+	/* Load bitmap directory */
+	if (!(sbi->sb_bmp_dir = hpfs_load_bitmap_directory(s, superblock->bitmaps)))
+		goto bail4;
+	
+	/* Check for general fs errors*/
+	if (spareblock->dirty && !spareblock->old_wrote) {
+		if (errs == 2) {
+			printk("HPFS: Improperly stopped, not mounted\n");
+			goto bail4;
+		}
+		hpfs_error(s, "improperly stopped");
+	}
+
+	if (!(s->s_flags & MS_RDONLY)) {
+		spareblock->dirty = 1;
+		spareblock->old_wrote = 0;
+		mark_buffer_dirty(bh2);
+	}
+
+	if (spareblock->hotfixes_used || spareblock->n_spares_used) {
+		if (errs >= 2) {
+			printk("HPFS: Hotfixes not supported here, try chkdsk\n");
+			mark_dirty(s);
+			goto bail4;
+		}
+		hpfs_error(s, "hotfixes not supported here, try chkdsk");
+		if (errs == 0) printk("HPFS: Proceeding, but your filesystem will be probably corrupted by this driver...\n");
+		else printk("HPFS: This driver may read bad files or crash when operating on disk with hotfixes.\n");
+	}
+	if (spareblock->n_dnode_spares != spareblock->n_dnode_spares_free) {
+		if (errs >= 2) {
+			printk("HPFS: Spare dnodes used, try chkdsk\n");
+			mark_dirty(s);
+			goto bail4;
+		}
+		hpfs_error(s, "warning: spare dnodes used, try chkdsk");
+		if (errs == 0) printk("HPFS: Proceeding, but your filesystem could be corrupted if you delete files or directories\n");
+	}
+	if (chk) {
+		unsigned a;
+		if (superblock->dir_band_end - superblock->dir_band_start + 1 != superblock->n_dir_band ||
+		    superblock->dir_band_end < superblock->dir_band_start || superblock->n_dir_band > 0x4000) {
+			hpfs_error(s, "dir band size mismatch: dir_band_start==%08x, dir_band_end==%08x, n_dir_band==%08x",
+				superblock->dir_band_start, superblock->dir_band_end, superblock->n_dir_band);
+			goto bail4;
+		}
+		a = sbi->sb_dirband_size;
+		sbi->sb_dirband_size = 0;
+		if (hpfs_chk_sectors(s, superblock->dir_band_start, superblock->n_dir_band, "dir_band") ||
+		    hpfs_chk_sectors(s, superblock->dir_band_bitmap, 4, "dir_band_bitmap") ||
+		    hpfs_chk_sectors(s, superblock->bitmaps, 4, "bitmaps")) {
+			mark_dirty(s);
+			goto bail4;
+		}
+		sbi->sb_dirband_size = a;
+	} else printk("HPFS: You really don't want any checks? You are crazy...\n");
+
+	/* Load code page table */
+	if (spareblock->n_code_pages)
+		if (!(sbi->sb_cp_table = hpfs_load_code_page(s, spareblock->code_page_dir)))
+			printk("HPFS: Warning: code page support is disabled\n");
+
+	brelse(bh2);
+	brelse(bh1);
+	brelse(bh0);
+
+	root = iget_locked(s, sbi->sb_root);
+	if (!root)
+		goto bail0;
+	hpfs_init_inode(root);
+	hpfs_read_inode(root);
+	unlock_new_inode(root);
+	s->s_root = d_alloc_root(root);
+	if (!s->s_root) {
+		iput(root);
+		goto bail0;
+	}
+	hpfs_set_dentry_operations(s->s_root);
+
+	/*
+	 * find the root directory's . pointer & finish filling in the inode
+	 */
+
+	root_dno = hpfs_fnode_dno(s, sbi->sb_root);
+	if (root_dno)
+		de = map_dirent(root, root_dno, "\001\001", 2, NULL, &qbh);
+	if (!de)
+		hpfs_error(s, "unable to find root dir");
+	else {
+		root->i_atime.tv_sec = local_to_gmt(s, de->read_date);
+		root->i_atime.tv_nsec = 0;
+		root->i_mtime.tv_sec = local_to_gmt(s, de->write_date);
+		root->i_mtime.tv_nsec = 0;
+		root->i_ctime.tv_sec = local_to_gmt(s, de->creation_date);
+		root->i_ctime.tv_nsec = 0;
+		hpfs_i(root)->i_ea_size = de->ea_size;
+		hpfs_i(root)->i_parent_dir = root->i_ino;
+		if (root->i_size == -1)
+			root->i_size = 2048;
+		if (root->i_blocks == -1)
+			root->i_blocks = 5;
+		hpfs_brelse4(&qbh);
+	}
+	return 0;
+
+bail4:	brelse(bh2);
+bail3:	brelse(bh1);
+bail2:	brelse(bh0);
+bail1:
+bail0:
+	if (sbi->sb_bmp_dir) kfree(sbi->sb_bmp_dir);
+	if (sbi->sb_cp_table) kfree(sbi->sb_cp_table);
+	s->s_fs_info = NULL;
+	kfree(sbi);
+	return -EINVAL;
+}
+
+static struct super_block *hpfs_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, hpfs_fill_super);
+}
+
+static struct file_system_type hpfs_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "hpfs",
+	.get_sb		= hpfs_get_sb,
+	.kill_sb	= kill_block_super,
+	.fs_flags	= FS_REQUIRES_DEV,
+};
+
+static int __init init_hpfs_fs(void)
+{
+	int err = init_inodecache();
+	if (err)
+		goto out1;
+	err = register_filesystem(&hpfs_fs_type);
+	if (err)
+		goto out;
+	return 0;
+out:
+	destroy_inodecache();
+out1:
+	return err;
+}
+
+static void __exit exit_hpfs_fs(void)
+{
+	unregister_filesystem(&hpfs_fs_type);
+	destroy_inodecache();
+}
+
+module_init(init_hpfs_fs)
+module_exit(exit_hpfs_fs)
+MODULE_LICENSE("GPL");