Skip to contentSkip to conversation

Directory index for Ext2 - the pagecache version

RFC In review Daniel Phillips 2 participants 3 messages Mar 9, 2001 – Mar 10, 2001

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

RFC 1/1 Directory index for Ext2 - the pagecache version

Though the hash races aren't finished yet, I put them on hold for a while to get the directory indexing converted from buffers to page cache. Al Viro had a patch to get me started at:

ftp://ftp.math.psu.edu/pub/viro/ext2-dir-patch-S2.gz

Having last been worked on sometime last fall it had only a small amount of bitrot. After we cleaned that up it functioned pretty well for normal ext2 directories, showing a doubling in performance but of course continuing to suffer from n**2 behaviour on large directories.

Gory details of the page cache conversion
=========================================
Converting from buffers to pages took about five times as long as I expected, and there is still work to do. However, something workable is now starting to emerge. The main difficulty is fundamental to the way the page cache works: when data divides nicely into pages everything is fine but when it doesn't things can get complicated.

This *is* a case where the data doesn't always divide into 4K pages very well. To illustrate, consider a directory on a 1K filesystem. Suppose that some random leaf block has to be split. The new block may or may not end up on the same page. If we lock the new block's page and it happens to be the same as the old block's page we will deadlock instantly. We get the same problem with the index root and intermediate index blocks. Up to 4 blocks can be 'in play' at once over different regions of the code and the combinatorics of this are pretty bad.

Al and I discussed what to do about this at some length, then we both went away and independently came up with the idea that the real problem is the page locking, and we might not actually need it in this context. The page->count should take care of keeping the pages in cache while we work on them, and i_sem takes care of the filesystem locking. If we don't have to worry about page-level locking it's a lot less messy to operate on different parts of the same page - less bookkeeping is required and the logic flow doesn't get obfuscated.

This patch tests that idea. There is an #ifdef which, if true, disables all the page lock/unlocking in the patch and otherwise does normal page locking. With the locking off the way is clear to handling 1K blocks reasonably elegantly. Though I have not really stressed it much, it does seem to work the same whichever way you set the #ifdef, at least for 4K blocks. This is good. The next step is to add the 1K block support. It should now be just a few lines instead of the horrible mess I originally imagined.

I've diverged pretty far from Al's original patch and the ext2/dir.c part of it won't look very much like the original when done. When everything stabilizes I should probably sync up with Al and divide it back up into two patchs - one to change the normal directory ops to the page cache and a second to add the index. For now it's all one patch, because the factoring still has a long way to go before it's acceptably orthogonal. Whichever way it ends up, both Al and my patch replace almost all of ext2/dir.c and ext2/namei.c.

Performance
===========
Performance is roughly doubled over the buffer cache version, needing only 75 uSec to create each file in a directory of 90,000 files. But I'm not finished optimizing. Binary-searching the index should speed things up further. An (optional) 'incompatible' directory block format I'm working on will likely give another doubling in speed. These and other incremental improvements mean we'll eventually be seeing Ext2 directory operations in the low 10's of microseconds. That should be fast enough. ;-)

Dumping the index
=================
There is a little hack in this patch for dumping the directory index structure. If you cat a directory you normally get an error, but I piggyback an index dump via printk onto this before returning the error code. This gives a nice little interactive tool for checking the index integrity and general niceness of the leaf block layout.

Hash races on hold
==================
I received a lot of email from hash function experts as a result of the earlier thread. I have to catch up with that email and I have about half a dozen very interesting functions to test. My dx_hack_hash now gets within 6% of the ideal 75% per-leaf 'fullness', but it is most definitely not perfect. Larry Auton asked me how I came up with the constants in my function and I told him 'by moving my fingers randomly on the keyboard' until it looked about right. He wasn't much surprised by that and suggested some better constants that he'd come up with by running an automatic hash function analyzer. I'll see what happens.

Several other people sent me code and a lot of it looks good. I'll make time to test it all over the next couple of weeks.

A funny thing about the page cache
==================================
Why does read_cache_page unlock the page just before returning? I'd prefer that both return the page locked and let me drop the lock myself, or have a read_page_locked and read_cache_page can be a wrapper for it. This would be consistent with grab_cache_page and in certain contexts it might turn out to be essential to do things that way.

Benchmarking code
=================

Here is some c/bash for benchmarks and testing. I'd very much appreciate it if some brave people would try applying the patch and exercising it now. There are no doubt a few points I've forgotten, which follows from the forest:trees principle.

------------
makefiles.c:
------------

int main (int argc, char *argv[])

{
	int n = (argc > 2)? strtol(argv[2], 0, 10): 0;
	int i, size = 50, show = argc > 3 && !strncmp(argv[3], "y", 1);
	char name[size];
	for (i = 0; i < n; i++)
	{
	        snprintf(name, size, "%s%i", argv[1], i);
	        if (show) printf("create %s\n", name);
        	close(open(name, 0100));
	}
	return 0;
}

-------------------
bash script 'test':
-------------------
umount $1
mount /dev/hda999 $1 -t ext2 -o index
cd $1 && time rm -rf $2
mkdir $2;
cd $2 && time makefiles $2 $3 $4
---------------------------------

Once you patch a 2.4.2 kernel you will essentially get Al's version of pagecache directories, with some spelling changes by me. If it oopses or loses files, direct you complaints to Al. :-) To enable the indexed code path you have to mount an ext2 filesystem with -o index. Please use the option only on test partitions, and be prepared for oopses. I.E, please use a test machine, not your workstation.

This version is closer to the final directory file format but it isn't quite there yet. Using a suggestion from Adreas Dilger I got rid of the giant-looking directory sizes, and ls now shows the real size of the directory file. I haven't got the full forward-compatibility support in yet either (from 2.1.something on) but it is coming soon.

To run the 90,000 file create test (create files /x/foo0 through /x/foo89999):

	sh test /x foo 90000 n

The 'n' supresses the file name listing.

To-do list
==========

- Finish 1K page cache support
- Stress test the page cache
- Finalize the file format
- Settle on a good hash function
- Hash function upgrade support
- Second tree level
- Audit directory locking
- COMPAT/INCOMPAT flags
- Backport 2.2 version
- Binary search index optmization
- Leaf block 'incompat' optimization
- Refactor page cache primitives
- Clean up the source
- Divide into two patches

The patch
=========
I cleaned up the obscure logic in the block splitting code by observing that some of the cases I was trying to optmize are so rare that the code complexity just isn't worth it. On the other hand, there is a 'fancy dance' section that got more complicated. The intent is to reduce stack usage, and contain it so it doesn't end up with recursive memory allocation stack frames sitting on top of it. The new code makes the stack utilization much more predictable and should sit comfortably within a normal kernel stack.

This patch *does not* support 1K blocksize. It doesn't check for that either - if you run it on a 1K blocksize filesystem it will fail noisily and probably oops. Otherwise things seem to be working pretty well. Benchmarks run smoothly up to the current limit around 90,000 files.

fs/ext2/dir.c renamed from uml.2.4.2.clean/fs/ext2/dir.c +1028 -139
@@ -16,167 +16,1056 @@
  *
  *  Big-endian to little-endian byte-swapping/bitmaps by
  *        David S. Miller ([email protected]), 1995
+ *
+ * All code that works with directory layout had been switched to pagecache
+ * and moved here. WARNING: it's early beta.	 -- AV
  */
 
 #include <linux/fs.h>
 #include <linux/ext2_fs.h>
+#include <linux/pagemap.h>
 
-static unsigned char ext2_filetype_table[] = {
-	DT_UNKNOWN, DT_REG, DT_DIR, DT_CHR, DT_BLK, DT_FIFO, DT_SOCK, DT_LNK
+int waitfor_one_page(struct page *page);
+
+static inline unsigned get_blocksize(struct inode *inode)
+{
+	return inode->i_sb->s_blocksize;
+}
+
+static inline unsigned get_blockshift(struct inode *inode)
+{
+	return inode->i_sb->s_blocksize_bits;
+}
+
+static inline unsigned long dir_pages(struct inode *inode)
+{
+	return (inode->i_size+PAGE_CACHE_SIZE-1)>>PAGE_CACHE_SHIFT;
+}
+
+#define unlock_page(page) UnlockPage(page)
+
+#if 1
+#define maybe_lock_page(page)
+#define maybe_unlock_page(page)
+#else
+#define maybe_lock_page(page) lock_page(page)
+#define maybe_unlock_page(page) UnlockPage(page)
+#endif
+
+static inline void release_page(struct page *page)
+{
+//	printk("release %p count=%i locked=%i\n", page, atomic_read(&page->count), PageLocked(page));
+	maybe_unlock_page(page);
+	kunmap(page);
+	page_cache_release(page);
+}
+
+
+static int fixme_commit_chunk(struct page *page, unsigned from, unsigned to)
+{
+	struct inode *dir = page->mapping->host;
+	int err = 0;
+	dir->i_version = ++event;
+	page->mapping->a_ops->commit_write(NULL, page, from, to);
+	if (IS_SYNC(dir))
+		err = waitfor_one_page(page);
+	return err;
+}
+
+static void fixme_check_page(struct page *page)
+{
+	struct inode *dir = page->mapping->host;
+	struct super_block *sb = dir->i_sb;
+	unsigned chunk_size = get_blocksize(dir);
+	char *kaddr = (char*)page_address(page);
+	u32 max_inumber = le32_to_cpu(sb->u.ext2_sb.s_es->s_inodes_count);
+	unsigned offs, rec_len;
+	unsigned limit = PAGE_CACHE_SIZE;
+	ext2_dirent *p;
+	char *error;
+
+	if ((dir->i_size >> PAGE_CACHE_SHIFT) == page->index) {
+		limit = dir->i_size & ~PAGE_CACHE_MASK;
+		if (limit & (chunk_size - 1))
+			goto Ebadsize;
+		for (offs = limit; offs<PAGE_CACHE_SIZE; offs += chunk_size) {
+			ext2_dirent *p = (ext2_dirent*)(kaddr + offs);
+			p->rec_len = cpu_to_le16(chunk_size);
+		}
+		if (!limit)
+			goto out;
+	}
+	for (offs = 0; offs <= limit - EXT2_DIR_REC_LEN(1); offs += rec_len) {
+		p = (ext2_dirent *) (kaddr + offs);
+		rec_len = le16_to_cpu(p->rec_len);
+
+		if (rec_len < EXT2_DIR_REC_LEN(1))
+			goto Eshort;
+		if (rec_len & 3)
+			goto Ealign;
+		if (rec_len < EXT2_DIR_REC_LEN(p->name_len))
+			goto Enamelen;
+		if (((offs + rec_len - 1) ^ offs) & ~(chunk_size-1))
+			goto Espan;
+		if (le32_to_cpu(p->inode) > max_inumber)
+			goto Einumber;
+	}
+	if (offs != limit)
+		goto Eend;
+out:
+	SetPageChecked(page);
+	return;
+
+	/* Too bad, we had an error */
+
+Ebadsize:
+	ext2_error(sb, "fixme_check_page",
+		"size of directory #%lu is not a multiple of chunk size",
+		dir->i_ino
+	);
+	goto fail;
+Eshort:
+	error = "rec_len is smaller than minimal";
+	goto bad_entry;
+Ealign:
+	error = "unaligned directory entry";
+	goto bad_entry;
+Enamelen:
+	error = "rec_len is too small for name_len";
+	goto bad_entry;
+Espan:
+	error = "directory entry across blocks";
+	goto bad_entry;
+Einumber:
+	error = "inode out of bounds";
+bad_entry:
+	ext2_error (sb, "fixme_check_page", "bad entry in directory #%lu: %s - "
+		"offset=%lu, inode=%lu, rec_len=%d, name_len=%d",
+		dir->i_ino, error, (page->index<<PAGE_CACHE_SHIFT)+offs,
+		(unsigned long) le32_to_cpu(p->inode),
+		rec_len, p->name_len);
+	goto fail;
+Eend:
+	p = (ext2_dirent *) (kaddr + offs);
+	ext2_error (sb, "fixme_check_page",
+		"entry in directory #%lu spans the page boundary"
+		"offset=%lu, inode=%lu",
+		dir->i_ino, (page->index<<PAGE_CACHE_SHIFT)+offs,
+		(unsigned long) le32_to_cpu(p->inode));
+fail:
+	SetPageChecked(page);
+	SetPageError(page);
+}
+
+static struct page * fixme_acquire_page(struct inode *dir, unsigned long n)
+{
+	struct address_space *mapping = dir->i_mapping;
+	struct page *page = read_cache_page (mapping, n, (filler_t*)mapping->a_ops->readpage, NULL);
+	// if read_cache_page didn't drop the lock we wouldn't have to reacquire it here...
+	if (!IS_ERR(page)) {
+		maybe_lock_page(page);
+		kmap(page);
+		if (!Page_Uptodate(page))
+			goto fail;
+		if (!PageChecked(page))
+			fixme_check_page(page);
+		if (PageError(page))
+			goto fail;
+	}
+	return page;
+
+fail:
+	release_page(page);
+	return ERR_PTR(-EIO);
+}
+
+/*
+ * p is at least 6 bytes before the end of page
+ */
+static inline ext2_dirent *ext2_next_entry(ext2_dirent *p)
+{
+	return (ext2_dirent *) ((char*)p + le16_to_cpu(p->rec_len));
+}
+
+/*
+ * Hash Tree Directory indexing
+ * (c) 2001, Daniel Phillips
+ */
+#ifdef CONFIG_EXT2_INDEX
+#define dxtrace_on(command) command
+#define dxtrace_off(command)
+#define dxtrace dxtrace_off
+
+/*
+ * Order n log(n) sort utility with n log(n) worst case
+ */
+
+#ifndef COMBSORT
+#define COMBSORT(size, i, j, COMPARE, EXCHANGE) { \
+	unsigned gap = size, more, i; \
+	do { \
+		if (gap > 1) gap = gap*10/13; \
+		if (gap - 9 < 2) gap = 11; \
+		for (i = size - 1, more = gap > 1; i >= gap; i--) { \
+			int j = i - gap; \
+			if (COMPARE) { EXCHANGE; more = 1; } } \
+	} while (more); }
+#endif
+
+#ifndef exchange
+#define exchange(x, y) do { typeof(x) z = x; x = y; y = z; } while (0)
+#endif
+
+static int prepare_chunk (struct page *page, char *chunk)
+{
+	unsigned offset = chunk - (char *) page_address(page);
+	unsigned size = page->mapping->host->i_sb->s_blocksize;
+	if (offset > PAGE_CACHE_SIZE || offset + size > PAGE_CACHE_SIZE) BUG();
+	return page->mapping->a_ops->prepare_write(NULL, page, offset, offset + size);
+}
+
+static int commit_chunk (struct page *page, char *chunk)
+{
+	unsigned offset = chunk - (char *) page_address(page);
+	unsigned size = page->mapping->host->i_sb->s_blocksize;
+	if (offset > PAGE_CACHE_SIZE || offset + size > PAGE_CACHE_SIZE) BUG();
+	return page->mapping->a_ops->commit_write(NULL, page, offset, offset + size);
+}
+
+char *acquire_block (struct inode *inode, u32 block, struct page **ppage)
+{
+	unsigned blockshift = get_blockshift(inode);
+	unsigned page2block = PAGE_CACHE_SHIFT - blockshift;
+	unsigned index = (block >> page2block) & ~PAGE_CACHE_MASK;
+	struct address_space *mapping = inode->i_mapping;
+	struct page *page = read_cache_page (mapping, index, (filler_t *) mapping->a_ops->readpage, NULL);
+	// if read_cache_page didn't drop the lock we wouldn't need to reacquire it here
+	if (!IS_ERR(page)) // can we please have just one way of passing back the error?
+	{
+		maybe_lock_page(page);
+		kmap(page);
+		if (!Page_Uptodate(page))
+			goto fail;
+		if (PageError(page))
+			goto fail;
+	}
+	return page_address(*ppage = page) + ((block & ~(-1<<page2block)) << blockshift);
+fail:
+	release_page(page);
+	return ERR_PTR(-EIO);
+}
+
+/*
+ * Structure of the directory root block
+ */
+
+struct dx_root
+{
+	ext2_dirent fake;
+	struct dx_root_info
+	{
+		u8 info_length;
+		u8 hash_version;
+		u8 indirect_levels;
+		u8 unused_flags;
+		u32 zero; // parent inode
+	}
+	info;
+	struct dx_entry
+	{
+		u32 hash;
+		u32 block;
+	} 
+	entries[0];
 };
 
-static int ext2_readdir(struct file *, void *, filldir_t);
+/*
+ * Bookkeeping for index traversal
+ */
 
-struct file_operations ext2_dir_operations = {
-	read:		generic_read_dir,
-	readdir:	ext2_readdir,
-	ioctl:		ext2_ioctl,
-	fsync:		ext2_sync_file,
+struct dx_frame
+{
+	char *data;
+	struct page *page;
+	struct dx_entry *entries;
+	struct dx_entry *at;
+	struct dx_root_info *info;
+	unsigned count;
+	unsigned limit;
 };
 
-int ext2_check_dir_entry (const char * function, struct inode * dir,
-			  struct ext2_dir_entry_2 * de,
-			  struct buffer_head * bh,
-			  unsigned long offset)
-{
-	const char * error_msg = NULL;
-
-	if (le16_to_cpu(de->rec_len) < EXT2_DIR_REC_LEN(1))
-		error_msg = "rec_len is smaller than minimal";
-	else if (le16_to_cpu(de->rec_len) % 4 != 0)
-		error_msg = "rec_len % 4 != 0";
-	else if (le16_to_cpu(de->rec_len) < EXT2_DIR_REC_LEN(de->name_len))
-		error_msg = "rec_len is too small for name_len";
-	else if (dir && ((char *) de - bh->b_data) + le16_to_cpu(de->rec_len) >
-		 dir->i_sb->s_blocksize)
-		error_msg = "directory entry across blocks";
-	else if (dir && le32_to_cpu(de->inode) > le32_to_cpu(dir->i_sb->u.ext2_sb.s_es->s_inodes_count))
-		error_msg = "inode out of bounds";
-
-	if (error_msg != NULL)
-		ext2_error (dir->i_sb, function, "bad entry in directory #%lu: %s - "
-			    "offset=%lu, inode=%lu, rec_len=%d, name_len=%d",
-			    dir->i_ino, error_msg, offset,
-			    (unsigned long) le32_to_cpu(de->inode),
-			    le16_to_cpu(de->rec_len), de->name_len);
-	return error_msg == NULL ? 1 : 0;
-}
-
-static int ext2_readdir(struct file * filp,
-			 void * dirent, filldir_t filldir)
-{
-	int error = 0;
-	unsigned long offset, blk;
-	int i, num, stored;
-	struct buffer_head * bh, * tmp, * bha[16];
-	struct ext2_dir_entry_2 * de;
-	struct super_block * sb;
-	int err;
+unsigned dx_hack_hash (const char *name, int len)
+{
+	u32 hash0 = 0x12a3fe2d, hash1 = 0x37abe8f9;
+	while (len--)
+	{
+		u32 hash = hash1 + (hash0 ^ (*name++ * 71523));
+		if (hash & 0x80000000) hash -= 0x7fffffff;
+		hash1 = hash0;
+		hash0 = hash;
+	}
+	return hash0;
+}
+
+#define dx_hash(s,n) (dx_hack_hash(s,n) << 1)
+
+/*
+ * Probe for a directory leaf block to search
+ */
+
+int dx_probe (struct inode *dir, u32 hash, struct dx_frame *dxframe)
+{
+	int count, search;
+	struct dx_entry *at, *at0;
+	struct page *page;
+	struct dx_root *root = (struct dx_root *) acquire_block (dir, 0, &page);
+	dxtrace(printk("Look up %u", hash));
+	if (!root) return -EINVAL;
+
+	/* First hash field holds count of entries */
+	at = at0 = root->entries;
+	if (!(count = le32_to_cpu(at[0].hash))) BUG();
+	search = count - 1; // should use binary search
+
+	while (search--)
+	{
+		dxtrace(printk("."));
+		if (le32_to_cpu((++at)->hash) > hash)
+		{
+			at--;
+			break;
+		}
+	}
+	dxtrace(printk(" in %u:%u\n", at - at0, le32_to_cpu(at->block)));
+	dxframe->info = &(root->info);
+	dxframe->page = page;
+	dxframe->data = (char *) root;
+	dxframe->entries = at0;
+	dxframe->at = at;
+	dxframe->count = count;
+	dxframe->limit = (get_blocksize(dir) - sizeof(struct dx_root_info)) / sizeof(struct dx_entry);
+	return 0;
+}
+
+/*
+ * Prior to split, finds record offset, computes hash of each dir block record
+ */
+
+struct dx_map_entry
+{
+	u32 hash;
+	u32 offs;
+};
+
+#define MAX_DX_MAP (PAGE_SIZE/EXT2_DIR_REC_LEN(1) + 1)
+/* Assumes file blocksize <= PAGE_SIZE */                                                                                                     static int dx_make_map (ext2_dirent *de, int size, struct dx_map_entry map[])
+
+{
+	int count = 0;
+	char *base = (char *) de;
+	while ((char *) de < base + size)
+	{
+		map[count].hash = dx_hash (de->name, de->name_len);
+		map[count].offs = (u32) ((char *) de - base);
+		de = (ext2_dirent *) ((char *) de + le16_to_cpu(de->rec_len));
+		count++;
+	}
+	return count;
+}
+
+/*
+ * For dir block splitting and compacting
+ */
+
+ext2_dirent *dx_copy (
+	char *from, char *to, unsigned size, // should pass from, to as de's (uli)
+	struct dx_map_entry map[], int start, int count)
+{
+	ext2_dirent *de = NULL;
+	char *top = to + size;
+	unsigned rec_len = 0;
+	if (!count) BUG();
+	while (count--)
+	{
+		de = (ext2_dirent *) (from + map[start++].offs);
+		rec_len = EXT2_DIR_REC_LEN(de->name_len);
+		if (to + rec_len > top) BUG();
+		memcpy (to, de, rec_len);
+		((ext2_dirent *) to)->rec_len = cpu_to_le16(rec_len);
+		to += rec_len;
+	}
+	return (ext2_dirent *) (to - rec_len);
+}
+
+static inline void set_rec_len (ext2_dirent *de, unsigned len)
+{
+	de->rec_len = cpu_to_le16(len);
+}
+
+static inline void set_rec_full (ext2_dirent *de, char *limit)
+{
+	set_rec_len (de, limit - (char *) de); // need to clear top?
+}
+
+/*
+ * Debug
+ */
+
+#ifndef printks
+void kstring (char *name, unsigned len)
+{
+	while (len--) printk("%c", *name++);
+}
+#endif
+
+struct stats { unsigned names; unsigned used; }
+dx_show_leaf (ext2_dirent *de, int size, int names)
+{
+	unsigned count = 0, used = 0;
+	char *base = (char *) de;
+	printk("names: ");
+	while ((char *) de < base + size)
+	{
+		if (names)
+		{
+			kstring(de->name, de->name_len);
+			printk(":%u.%u ", dx_hash (de->name, de->name_len), (u32) ((char *) de - base));
+		}
+		used += EXT2_DIR_REC_LEN(de->name_len);
+		de = (ext2_dirent *) ((char *) de + le16_to_cpu(de->rec_len));
+ 		count++;
+	}
+	printk("(%i)\n", count);
+	return (struct stats) { count, used };
+}
+
+void dx_show_buckets (struct inode *dir)
+{
+	struct super_block *sb = dir->i_sb;
+	int blockshift = EXT2_BLOCK_SIZE_BITS (sb), blocksize = 1 << blockshift;
+	unsigned count, i, used = 0, names = 0;
+	struct dx_entry *at;
+	struct page *page, *page2;
+	struct dx_root *root = (struct dx_root *) acquire_block (dir, 0, &page);
+	if (!root) return;
+	at = root->entries;
+	count = *(u32 *) at;
+	printk("%i indexed blocks...\n", count);
+	for (i = 0; i < count; i++, at++)
+	{
+		u32 block = le32_to_cpu(at->block), h = le32_to_cpu(at->hash), hash = i? h: 0;
+		u32 range = i == count - 1? ~h: (le32_to_cpu((at + 1)->hash) - hash);
+		struct stats stats;
+		ext2_dirent *leaf = (ext2_dirent *) acquire_block (dir, block, &page2);
+		printk("%u:%u hash %u/%u ", i, block, hash, range);
+		if (!leaf) continue;
+		stats = dx_show_leaf (leaf, blocksize, 0);
+		names += stats.names;
+		used += stats.used;
+		release_page (page2);
+	}
+	release_page (page);
+	printk("names %u, fullness %u (%u%%)\n", names, used/count, (used/count)*100/blocksize);
+}
+
+static void dx_show_index (struct dx_frame *dxframe)
+{
+	struct dx_entry *entries = dxframe->entries;
+	int i = 0;
+	printk("Index: ");
+	for (; i < *(u32 *) entries; i++)
+	{
+		printk("%u@%u ", le32_to_cpu(entries[i].hash), le32_to_cpu(entries[i].block));
+	}
+	printk("\n");
+}
+#endif
+
+/*
+ * NOTE! unlike strncmp, ext2_match returns 1 for success, 0 for failure.
+ *
+ * len <= EXT2_NAME_LEN and de != NULL are guaranteed by caller.
+ */
+static inline int ext2_match (int len, const char *name, ext2_dirent *de)
+{
+	if (len != de->name_len)
+		return 0;
+	if (!de->inode)
+		return 0;
+	return !memcmp(name, de->name, len);
+}
+
+static inline unsigned 
+ext2_validate_entry(char *base, unsigned offset, unsigned mask)
+{
+	ext2_dirent *de = (ext2_dirent*)(base + offset);
+	ext2_dirent *p = (ext2_dirent*)(base + (offset&mask));
+	while ((char*)p < (char*)de)
+		p = ext2_next_entry(p);
+	return (char *)p - base;
+}
+
+static unsigned char ext2_filetype_table[EXT2_FT_MAX] = {
+	[EXT2_FT_UNKNOWN]	DT_UNKNOWN,
+	[EXT2_FT_REG_FILE]	DT_REG,
+	[EXT2_FT_DIR]		DT_DIR,
+	[EXT2_FT_CHRDEV]	DT_CHR,
+	[EXT2_FT_BLKDEV]	DT_BLK,
+	[EXT2_FT_FIFO]		DT_FIFO,
+	[EXT2_FT_SOCK]		DT_SOCK,
+	[EXT2_FT_SYMLINK]	DT_LNK,
+};
+
+#define S_SHIFT 12
+static unsigned char ext2_type_by_mode[S_IFMT >> S_SHIFT] = {
+	[S_IFREG >> S_SHIFT]	EXT2_FT_REG_FILE,
+	[S_IFDIR >> S_SHIFT]	EXT2_FT_DIR,
+	[S_IFCHR >> S_SHIFT]	EXT2_FT_CHRDEV,
+	[S_IFBLK >> S_SHIFT]	EXT2_FT_BLKDEV,
+	[S_IFIFO >> S_SHIFT]	EXT2_FT_FIFO,
+	[S_IFSOCK >> S_SHIFT]	EXT2_FT_SOCK,
+	[S_IFLNK >> S_SHIFT]	EXT2_FT_SYMLINK,
+};
+
+static inline void ext2_set_de_type (ext2_dirent *de, struct inode *inode)
+{
+	mode_t mode = inode->i_mode;
+	if (EXT2_HAS_INCOMPAT_FEATURE(inode->i_sb, EXT2_FEATURE_INCOMPAT_FILETYPE))
+		de->file_type = ext2_type_by_mode[(mode & S_IFMT)>>S_SHIFT];
+}
+
+static int ext2_readdir (struct file *filp, void *dirent, filldir_t filldir)
+{
+	loff_t pos = filp->f_pos;
 	struct inode *inode = filp->f_dentry->d_inode;
+	struct super_block *sb = inode->i_sb;
+	unsigned offset = pos & ~PAGE_CACHE_MASK;
+	unsigned long n = pos >> PAGE_CACHE_SHIFT;
+#ifdef CONFIG_EXT2_INDEX
+	int start = is_dx(inode)? 1: 0;
+#else
+	int start = 0;
+#endif
+	unsigned long npages = dir_pages(inode) - start;
+	unsigned chunk_mask = ~(get_blocksize(inode)-1);
+	unsigned char *types = NULL;
+	int need_revalidate = (filp->f_version != inode->i_version);
 
-	sb = inode->i_sb;
+	if (pos > inode->i_size - EXT2_DIR_REC_LEN(1))
+		goto done;
 
-	stored = 0;
-	bh = NULL;
-	offset = filp->f_pos & (sb->s_blocksize - 1);
-
-	while (!error && !stored && filp->f_pos < inode->i_size) {
-		blk = (filp->f_pos) >> EXT2_BLOCK_SIZE_BITS(sb);
-		bh = ext2_bread (inode, blk, 0, &err);
-		if (!bh) {
-			ext2_error (sb, "ext2_readdir",
-				    "directory #%lu contains a hole at offset %lu",
-				    inode->i_ino, (unsigned long)filp->f_pos);
-			filp->f_pos += sb->s_blocksize - offset;
+	if (EXT2_HAS_INCOMPAT_FEATURE(sb, EXT2_FEATURE_INCOMPAT_FILETYPE))
+		types = ext2_filetype_table;
+
+	for (; n < npages; n++, offset = 0) {
+		char *kaddr, *limit;
+		ext2_dirent *de;
+		struct page *page = fixme_acquire_page(inode, n + start);
+
+		if (IS_ERR(page))
 			continue;
+		kaddr = (char *) page_address(page);
+		if (need_revalidate) {
+			offset = ext2_validate_entry(kaddr, offset, chunk_mask);
+			need_revalidate = 0;
 		}
+		de = (ext2_dirent *) (kaddr+offset);
+		limit = kaddr + PAGE_CACHE_SIZE - EXT2_DIR_REC_LEN(1);
+		for (; (char*) de <= limit; de = ext2_next_entry(de))
+			if (de->inode) {
+				int over;
+				unsigned char d_type = DT_UNKNOWN;
 
-		/*
-		 * Do the readahead
-		 */
-		if (!offset) {
-			for (i = 16 >> (EXT2_BLOCK_SIZE_BITS(sb) - 9), num = 0;
-			     i > 0; i--) {
-				tmp = ext2_getblk (inode, ++blk, 0, &err);
-				if (tmp && !buffer_uptodate(tmp) && !buffer_locked(tmp))
-					bha[num++] = tmp;
-				else
-					brelse (tmp);
-			}
-			if (num) {
-				ll_rw_block (READA, num, bha);
-				for (i = 0; i < num; i++)
-					brelse (bha[i]);
+				if (types && de->file_type < EXT2_FT_MAX)
+					d_type = types[de->file_type];
+
+				offset = (char *)de - kaddr;
+				over = filldir(dirent, de->name, de->name_len,
+						(n<<PAGE_CACHE_SHIFT) | offset,
+						le32_to_cpu(de->inode), d_type);
+				if (over) {
+					release_page(page);
+					goto done;
+				}
 			}
+		release_page(page);
+	}
+
+done:
+	filp->f_pos = (n << PAGE_CACHE_SHIFT) | offset;
+	filp->f_version = inode->i_version;
+	UPDATE_ATIME(inode);
+	return 0;
+}
+
+/*
+ * finds an entry in the specified directory with the wanted name. It
+ * returns the page in which the entry was found, and the entry itself
+ * (as a parameter - res_dir). Page is returned mapped and unlocked.
+ * Entry is guaranteed to be valid.
+ */
+ext2_dirent *ext2_find_entry (struct inode *dir, struct dentry *dentry, struct page **res_page)
+{
+	const char *name = dentry->d_name.name;
+	int namelen = dentry->d_name.len;
+	unsigned reclen = EXT2_DIR_REC_LEN(namelen);
+	unsigned long n;
+	unsigned long npages = dir_pages(dir);
+	struct page *page = NULL;
+	ext2_dirent *de;
+
+	/* OFFSET_CACHE */
+	*res_page = NULL;
+
+#ifdef CONFIG_EXT2_INDEX
+	if (is_dx(dir))
+	{
+		u32 hash = dx_hash (name, namelen);
+		struct dx_frame dxframe;
+		char *data;
+		unsigned blocksize = get_blocksize (dir);
+		int err = dx_probe (dir, hash, &dxframe); // don't ignore the error!!
+
+		while (1)
+		{
+			if (!(data = acquire_block (dir, le32_to_cpu(dxframe.at->block), &page))) goto dxfail;
+			de = (ext2_dirent *) data;
+			for (; (char *) de <= data + blocksize - reclen; de = ext2_next_entry(de))
+				if (ext2_match (namelen, name, de))
+				{
+					dxtrace(printk("Found %s in %i:%i page %p\n", name, 
+						dxframe.at - dxframe.entries, le32_to_cpu(dxframe.at->block),
+						page));
+					release_page(dxframe.page);
+					goto found;
+				}
+			release_page(page);
+			/* Same hash continues in next block?  Search further. */
+			if (++(dxframe.at) - dxframe.entries == dxframe.count) break;
+			if ((le32_to_cpu(dxframe.at->hash) & -2) != hash) break;
+			dxtrace(printk("Try next, block %i\n", le32_to_cpu(dxframe.at->block)));
 		}
-		
-revalidate:
-		/* If the dir block has changed since the last call to
-		 * readdir(2), then we might be pointing to an invalid
-		 * dirent right now.  Scan from the start of the block
-		 * to make sure. */
-		if (filp->f_version != inode->i_version) {
-			for (i = 0; i < sb->s_blocksize && i < offset; ) {
-				de = (struct ext2_dir_entry_2 *) 
-					(bh->b_data + i);
-				/* It's too expensive to do a full
-				 * dirent test each time round this
-				 * loop, but we do have to test at
-				 * least that it is non-zero.  A
-				 * failure will be detected in the
-				 * dirent test below. */
-				if (le16_to_cpu(de->rec_len) < EXT2_DIR_REC_LEN(1))
-					break;
-				i += le16_to_cpu(de->rec_len);
-			}
-			offset = i;
-			filp->f_pos = (filp->f_pos & ~(sb->s_blocksize - 1))
-				| offset;
-			filp->f_version = inode->i_version;
-		}
-		
-		while (!error && filp->f_pos < inode->i_size 
-		       && offset < sb->s_blocksize) {
-			de = (struct ext2_dir_entry_2 *) (bh->b_data + offset);
-			if (!ext2_check_dir_entry ("ext2_readdir", inode, de,
-						   bh, offset)) {
-				/* On error, skip the f_pos to the
-                                   next block. */
-				filp->f_pos = (filp->f_pos | (sb->s_blocksize - 1))
-					      + 1;
-				brelse (bh);
-				return stored;
+dxfail:
+		release_page(dxframe.page);
+		return NULL;
+	}
+#endif
+
+	for (n = 0; n < npages; n++) {
+		char *kaddr;
+		page = fixme_acquire_page(dir, n);
+		if (IS_ERR(page))
+			continue;
+
+		kaddr = (char*)page_address(page);
+		de = (ext2_dirent *) kaddr;
+		kaddr += PAGE_CACHE_SIZE - reclen;
+		for (; (char *) de <= kaddr; de = ext2_next_entry(de))
+			if (ext2_match (namelen, name, de))
+				goto found;
+		release_page(page);
+	}
+	return NULL;
+found:
+	maybe_unlock_page(page);
+	*res_page = page;
+	return de;
+}
+
+ext2_dirent *ext2_parent (struct inode *dir, struct page **p)
+{
+	struct page *page = fixme_acquire_page(dir, 0);
+	ext2_dirent *de = NULL;
+
+	if (!IS_ERR(page)) {
+		de = ext2_next_entry((ext2_dirent *) page_address(page));
+		*p = page;
+		maybe_unlock_page(page);
+	}
+	return de;
+}
+
+ino_t ext2_find_inode (struct inode * dir, struct dentry *dentry)
+{
+	ino_t res = 0;
+	ext2_dirent *de;
+	struct page *page;
+	
+	de = ext2_find_entry (dir, dentry, &page);
+	if (de) {
+		res = le32_to_cpu(de->inode);
+		kunmap(page);
+		page_cache_release(page);
+	}
+	return res;
+}
+
+/* Releases the page */
+void ext2_set_entry (struct inode *dir, ext2_dirent *de, struct page *page, struct inode *inode)
+{
+	unsigned from = (char *)de-(char*)page_address(page);
+	unsigned to = from + le16_to_cpu(de->rec_len);
+	int err;
+
+	maybe_lock_page(page);
+	err = page->mapping->a_ops->prepare_write(NULL, page, from, to);
+	if (err)
+		BUG();
+	de->inode = cpu_to_le32(inode->i_ino);
+	ext2_set_de_type (de, inode);
+	err = fixme_commit_chunk(page, from, to);
+	release_page(page);
+	dir->i_mtime = dir->i_ctime = CURRENT_TIME;
+	mark_inode_dirty(dir);
+}
+
+/*
+ *	Parent is locked.
+ */
+int ext2_add_entry (struct dentry *dentry, struct inode *inode)
+{
+	struct inode *dir = dentry->d_parent->d_inode;
+	const char *name = dentry->d_name.name;
+	int namelen = dentry->d_name.len;
+	unsigned reclen = EXT2_DIR_REC_LEN(namelen);
+	unsigned short rec_len, name_len;
+	struct page *page = NULL;
+	ext2_dirent *de;
+	unsigned long npages = dir_pages(dir);
+	unsigned long n;
+	char *kaddr;
+	unsigned from, to;
+	int err;
+
+#ifdef CONFIG_EXT2_INDEX
+	if (is_dx(dir))
+	{
+		struct dx_frame dxframe;
+		struct dx_entry *entries, *at;
+		u32 hash = dx_hash (name, namelen), hash2, block2;
+		unsigned blockshift = get_blockshift (dir), blocksize = 1 << blockshift;
+		unsigned count, split, continued;
+		struct page *page2;
+		ext2_dirent *de2;
+		char *b1, *b2;
+
+		if ((err = dx_probe (dir, hash, &dxframe))) return err;
+		entries = dxframe.entries;
+		at = dxframe.at;
+
+		if (!(b1 = acquire_block (dir, le32_to_cpu(dxframe.at->block), &page))) return err;
+		prepare_chunk (page, b1);
+		de = (ext2_dirent *) b1;
+
+		while ((char *) de < b1 + blocksize)
+		{
+			name_len = EXT2_DIR_REC_LEN(de->name_len);
+			rec_len = le16_to_cpu(de->rec_len);
+			if (!de->inode && rec_len >= reclen) goto dx_got_it;
+			if (rec_len >= name_len + reclen) goto dx_got_it;
+			de = (ext2_dirent *) ((char *) de + rec_len);
+		}
+
+		/* Could compress, but for now just split it */
+		dxtrace(printk("entry count %i, limit %i\n", dxframe.count, dxframe.limit));
+
+		if (dxframe.count == dxframe.limit)
+		{
+			release_page(page);
+			release_page(dxframe.page);
+			return -ENOENT;
+		}
+
+		if (!(b2 = acquire_block (dir, block2 = dir->i_size >> blockshift, &page2)))
+		{
+			BUG();
+			release_page(page);
+			release_page(dxframe.page);
+			return err;
+		}
+
+		prepare_chunk (page2, b2);
+		if (!page2->buffers) BUG();
+		if (!page->buffers) BUG();
+		{
+			char *b3;
+			struct dx_map_entry map[MAX_DX_MAP];
+			count = dx_make_map ((ext2_dirent *) b1, blocksize, map);
+			split = count/2; // need to adjust to actual middle
+			COMBSORT(count, i, j, map[i].hash < map[j].hash, exchange(map[i], map[j]));
+
+			hash2 = map[split].hash;
+			continued = hash2 == map[split - 1].hash;
+			dxtrace(printk("Split block %i at %u, %i/%i\n", dxframe.at->block, hash2, split, count-split));
+
+			/* Fancy dance to stay within two buffers */
+			de2 = dx_copy (b1, b2, blocksize, map, split, count - split);
+			b3 = (char *) de2 + de2->rec_len;
+			de = dx_copy (b1, b3, blocksize - (b3 - b2), map, 0, split);
+			memcpy(b1, b3, (char *) de + de->rec_len - b3);
+			de = (ext2_dirent *) ((char *) de - b3 + b1);
+			set_rec_full (de, b1 + blocksize);
+			set_rec_full (de2, b2 + blocksize);
+
+			dxtrace(dx_show_leaf ((ext2_dirent *) b1, blocksize, 1));
+			dxtrace(dx_show_leaf ((ext2_dirent *) b2, blocksize, 1));
+
+			/* Which block gets the new entry? */
+			dxtrace(printk("Insert %s/%u ", name, hash));
+			if (hash >= hash2)
+			{
+				dxtrace(printk("above"));
+				exchange(page, page2);
+				exchange(b1, b2);
+				exchange(de, de2);
 			}
-			offset += le16_to_cpu(de->rec_len);
-			if (le32_to_cpu(de->inode)) {
-				/* We might block in the next section
-				 * if the data destination is
-				 * currently swapped out.  So, use a
-				 * version stamp to detect whether or
-				 * not the directory has been modified
-				 * during the copy operation.
-				 */
-				unsigned long version = filp->f_version;
-				unsigned char d_type = DT_UNKNOWN;
+			dxtrace(printk("\n"));
+		}
+		commit_chunk (page2, b2);
+		release_page (page2);
+
+		prepare_chunk (dxframe.page, dxframe.data);
+		if (!dxframe.page->buffers) BUG();
+		memmove (at + 1, at, (char *) (entries + dxframe.count) - (char *) (at));
+		at++;
+		at->block = cpu_to_le32(block2);
+		at->hash = cpu_to_le32(hash2 + continued);
+		entries[0].hash = cpu_to_le32(++dxframe.count); /* first hash field is entry count */
+		dxtrace(dx_show_index (&dxframe));
+		commit_chunk (dxframe.page, dxframe.data);
+
+dx_got_it:
+		release_page (dxframe.page);
+		/* New dirent will be added at de in page */
+		// put this down there?
+		name_len = EXT2_DIR_REC_LEN(de->name_len);
+		rec_len = le16_to_cpu(de->rec_len);
+		from = (char *) de - (char *) page_address(page);
+		to = from + rec_len;
+		goto add_it;
+	}
+#endif
+
+	/* We take care of directory expansion in the same loop */
+	for (n = 0; n <= npages; n++) {
+		page = fixme_acquire_page(dir, n);
+		err = PTR_ERR(page);
+		if (IS_ERR(page))
+			goto out;
+		kaddr = (char*)page_address(page);
+		de = (ext2_dirent *)kaddr;
+		kaddr += PAGE_CACHE_SIZE - reclen;
+		while ((char *)de <= kaddr) {
+			err = -EEXIST;
+			if (ext2_match (namelen, name, de))
+				goto out_page;
+			name_len = EXT2_DIR_REC_LEN(de->name_len);
+			rec_len = le16_to_cpu(de->rec_len);
+			if (!de->inode && rec_len >= reclen)
+				goto got_it;
+			if (rec_len >= name_len + reclen)
+				goto got_it;
+			de = (ext2_dirent *) ((char *) de + rec_len);
+		}
+		release_page(page);
+	}
+	BUG();
+	return -EINVAL;
+
+got_it:
+	from = (char *) de - (char *) page_address(page);
+	to = from + rec_len;
+	err = page->mapping->a_ops->prepare_write(NULL, page, from, to);
+	if (err)
+		goto out_page;
+add_it:
+	if (de->inode) {
+		ext2_dirent *de1 = (ext2_dirent *) ((char *) de + name_len);
+		de1->rec_len = cpu_to_le16(rec_len - name_len);
+		de->rec_len = cpu_to_le16(name_len);
+		de = de1;
+	}
+	de->name_len = namelen;
+	memcpy (de->name, name, namelen);
+	de->inode = cpu_to_le32(inode->i_ino);
+	ext2_set_de_type (de, inode);
+	err = fixme_commit_chunk(page, from, to);
+
+	dir->i_mtime = dir->i_ctime = CURRENT_TIME;
+	mark_inode_dirty(dir);
+out_page:
+	release_page(page);
+out:
+	return err;
+}
 
-				if (EXT2_HAS_INCOMPAT_FEATURE(sb, EXT2_FEATURE_INCOMPAT_FILETYPE)
-				    && de->file_type < EXT2_FT_MAX)
-					d_type = ext2_filetype_table[de->file_type];
-				error = filldir(dirent, de->name,
-						de->name_len,
-						filp->f_pos, le32_to_cpu(de->inode),
-						d_type);
-				if (error)
-					break;
-				if (version != filp->f_version)
-					goto revalidate;
-				stored ++;
+/*
+ * ext2_delete_entry deletes a directory entry by merging it with the
+ * previous entry. Page is up-to-date. Releases the page.
+ */
+int ext2_del_entry (ext2_dirent *dir, struct page * page )
+{
+	struct address_space *mapping = page->mapping;
+	struct inode *inode = (struct inode*) mapping->host;
+	char *kaddr = (char*) page_address(page);
+	unsigned from = ((char*) dir - kaddr) & ~(get_blocksize(inode)-1);
+	unsigned to = ((char*) dir - kaddr) + le16_to_cpu(dir->rec_len);
+	ext2_dirent *pde = NULL;
+	ext2_dirent *de = (ext2_dirent *) (kaddr + from);
+	int err;
+
+	maybe_lock_page(page);
+
+	while ((char*)de < (char*)dir) {
+		pde = de;
+		de = ext2_next_entry(de);
+	}
+	if (pde)
+		from = (char*)pde - (char*)page_address(page);
+	err = mapping->a_ops->prepare_write(NULL, page, from, to);
+	if (err)
+		BUG();
+	if (pde)
+		pde->rec_len = cpu_to_le16(to-from);
+	dir->inode = 0;
+	err = fixme_commit_chunk(page, from, to);
+	release_page(page);
+	inode->i_ctime = inode->i_mtime = CURRENT_TIME;
+	mark_inode_dirty(inode);
+	return err;
+}
+
+/*
+ * Set the first fragment of directory.
+ */
+int ext2_make_empty (struct inode *dir, struct inode *parent)
+{
+	struct super_block *sb = dir->i_sb;
+	struct address_space *mapping = dir->i_mapping;
+	unsigned blocksize = get_blocksize(dir);
+	ext2_dirent *de;
+	char *base;
+	int err;
+#ifdef CONFIG_EXT2_INDEX
+	int make_dx = test_opt (sb, DXTREE);
+	int start = make_dx? 1: 0;
+#else
+	int start = 0;
+#endif
+	struct page *page = grab_cache_page(mapping, start);
+	unlock_page(page); maybe_lock_page(page);
+
+	if (!page)
+		return -ENOMEM;
+	err = mapping->a_ops->prepare_write(NULL, page, 0, blocksize);
+	if (err)
+		goto fail;
+
+	base = (char*)page_address(page);
+
+	de = (ext2_dirent *) base;
+	de->name_len = 1;
+	de->rec_len = cpu_to_le16(EXT2_DIR_REC_LEN(1));
+	memcpy (de->name, ".\0\0", 4);
+	de->inode = cpu_to_le32(dir->i_ino);
+	ext2_set_de_type (de, dir);
+
+	de = (ext2_dirent *) (base + EXT2_DIR_REC_LEN(1));
+	de->name_len = 2;
+	de->inode = cpu_to_le32(parent->i_ino);
+	set_rec_len (de, blocksize - EXT2_DIR_REC_LEN(1));
+	memcpy (de->name, "..\0", 4);
+	ext2_set_de_type (de, dir);
+
+#ifdef CONFIG_EXT2_INDEX
+	if (make_dx)
+	{
+		struct page *rootpage = grab_cache_page (mapping, 0);
+		unlock_page(rootpage); maybe_lock_page(rootpage);
+		err = mapping->a_ops->prepare_write(NULL, rootpage, 0, blocksize); // must generalize this
+		if (rootpage)
+		{
+			struct dx_root *root = (struct dx_root *) page_address(rootpage);
+			struct dx_entry *entries = root->entries;
+			dxtrace_on(printk("Making indexed directory\n"));
+//			entries[0].block = le32_to_cpu(dx_dir_base(sb));
+			entries[0].block = le32_to_cpu(1);
+			entries[0].hash = le32_to_cpu(1); /* first hash field is entry count */
+			root->info.info_length = 8;
+			set_rec_len (&root->fake, blocksize);
+			err = fixme_commit_chunk (rootpage, 0, blocksize);
+			maybe_unlock_page(rootpage);
+			page_cache_release(rootpage);
+			dir->u.ext2_i.i_flags |= EXT2_INDEX_FL;
+			mark_inode_dirty(dir);
+		}
+	}
+#endif
+	err = fixme_commit_chunk(page, 0, blocksize);
+fail:
+	maybe_unlock_page(page);
+	page_cache_release(page);
+	return err;
+}
+
+/*
+ * routine to check that the specified directory is empty (for rmdir)
+ */
+int ext2_is_empty (struct inode *dir)
+{
+	struct page *page = NULL;
+	unsigned long i, npages = dir_pages(dir);
+#ifdef CONFIG_EXT2_INDEX
+	int start = is_dx(dir)? 1: 0;
+#else
+	int start = 0;
+#endif
+	
+	for (i = 0; i < npages - start; i++) {
+		char *kaddr;
+		ext2_dirent *de;
+		page = fixme_acquire_page(dir, i + start);
+
+		if (IS_ERR(page))
+			continue;
+
+		kaddr = (char *) page_address(page);
+		de = (ext2_dirent *) kaddr;
+		kaddr += PAGE_CACHE_SIZE-EXT2_DIR_REC_LEN(1);
+
+		while ((char *)de <= kaddr) {
+			if (de->inode != 0) {
+				/* check for . and .. */
+				if (de->name[0] != '.')
+					goto not_empty;
+				if (de->name_len > 2)
+					goto not_empty;
+				if (de->name_len < 2) {
+					if (de->inode !=
+					    cpu_to_le32(dir->i_ino))
+						goto not_empty;
+				} else if (de->name[1] != '.')
+					goto not_empty;
 			}
-			filp->f_pos += le16_to_cpu(de->rec_len);
+			de = ext2_next_entry(de);
 		}
-		offset = 0;
-		brelse (bh);
+		release_page(page);
 	}
-	UPDATE_ATIME(inode);
+	return 1;
+
+not_empty:
+	release_page(page);
 	return 0;
 }
+
+ssize_t hack_show_dir (struct file *filp, char *buf, size_t siz, loff_t *ppos)
+{
+	dx_show_buckets (filp->f_dentry->d_inode);
+	return -EISDIR;
+}
+
+struct file_operations ext2_dir_operations = {
+	read:		hack_show_dir,
+	readdir:	ext2_readdir,
+	fsync:		ext2_sync_file,
+};
fs/ext2/inode.c renamed from uml.2.4.2.clean/fs/ext2/inode.c +6 -81

105 lines not on this page · raw message

fs/ext2/namei.c renamed from uml.2.4.2.clean/fs/ext2/namei.c +218 -691

1,021 lines not on this page · raw message

fs/ext2/super.c renamed from uml.2.4.2.clean/fs/ext2/super.c +6 -0
@@ -188,6 +188,12 @@
 				printk("EXT2 Check option not supported\n");
 #endif
 		}
+		else if (!strcmp (this_char, "index"))
+#ifdef CONFIG_EXT2_INDEX
+			set_opt (*mount_options, DXTREE);
+#else
+			printk("EXT2 Index option not supported\n");
+#endif
 		else if (!strcmp (this_char, "debug"))
 			set_opt (*mount_options, DEBUG);
 		else if (!strcmp (this_char, "errors")) {
include/linux/ext2_fs.h renamed from uml.2.4.2.clean/include/linux/ext2_fs.h +39 -15

103 lines not on this page · raw message

include/linux/mm.h renamed from uml.2.4.2.clean/include/linux/mm.h +3 -0
@@ -167,6 +167,7 @@
 #define PG_skip			10
 #define PG_inactive_clean	11
 #define PG_highmem		12
+#define PG_checked		13	/* kill me in 2.5.<early>. */
 				/* bits 21-29 unused */
 #define PG_arch_1		30
 #define PG_reserved		31
@@ -181,6 +182,8 @@
 #define PageLocked(page)	test_bit(PG_locked, &(page)->flags)
 #define LockPage(page)		set_bit(PG_locked, &(page)->flags)
 #define TryLockPage(page)	test_and_set_bit(PG_locked, &(page)->flags)
+#define PageChecked(page)	test_bit(PG_checked, &(page)->flags)
+#define SetPageChecked(page)	set_bit(PG_checked, &(page)->flags)
 
 extern void __set_page_dirty(struct page *);
 
mm/filemap.c renamed from uml.2.4.2.clean/mm/filemap.c +2 -2
@@ -332,7 +332,7 @@
 	return 0;
 }
 
-static int waitfor_one_page(struct page *page)
+int waitfor_one_page(struct page *page)
 {
 	int error = 0;
 	struct buffer_head *bh, *head = page->buffers;
@@ -506,7 +506,7 @@
 	if (PageLocked(page))
 		BUG();
 
-	flags = page->flags & ~((1 << PG_uptodate) | (1 << PG_error) | (1 << PG_dirty) | (1 << PG_referenced) | (1 << PG_arch_1));
+	flags = page->flags & ~((1 << PG_uptodate) | (1 << PG_error) | (1 << PG_dirty) | (1 << PG_referenced) | (1 << PG_arch_1)| (1 << PG_checked));
 	page->flags = flags | (1 << PG_locked);
 	page_cache_get(page);
 	page->index = offset;
Signature · 1 line
Daniel
#1 lore raw
Quoted 1 line · Daniel Phillips

Why does read_cache_page unlock the page just before returning? I'd

Because it may find the thing in cache. Uptodate and unlocked. Since you want the same result on all ways out of the function, you get to unlock the thing if you had locked it - just to match the fast path.

I have one serious problem with the patch: maybe_lock_page().
I can understand why you might want to wait on page, I can understand why you might want to lock page2 iff page2 != page1, but...

Aside of that - several bad problems with the style.

fs/ext2/dir.c line 213

Quoted 11 lines
fs/ext2/dir.c renamed from uml.2.4.2.clean/fs/ext2/dir.c +11 -0
+#ifndef COMBSORT
+#define COMBSORT(size, i, j, COMPARE, EXCHANGE) { \
+	unsigned gap = size, more, i; \
+	do { \
+		if (gap > 1) gap = gap*10/13; \
+		if (gap - 9 < 2) gap = 11; \
+		for (i = size - 1, more = gap > 1; i >= gap; i--) { \
+			int j = i - gap; \
+			if (COMPARE) { EXCHANGE; more = 1; } } \
+	} while (more); }
+#endif

Don't. Do. That. C is not Algol 60. Please, cut down on preprocessor abuse. I'm quite serious - you are using the thing once, so there is no possible reason to write it that way. It's not cute, it's really, really ugly. Make it an inline function if you think that it's critical to keep it in-line, but for $DEITY sake, make it a C function.

fs/ext2/dir.c line 217

Quoted 3 lines
fs/ext2/dir.c renamed from uml.2.4.2.clean/fs/ext2/dir.c +3 -0
+#ifndef exchange
+#define exchange(x, y) do { typeof(x) z = x; x = y; y = z; } while (0)
+#endif

Ditto. I would simply kill this one off.

Quoted 1 line

+#define dx_hash(s,n) (dx_hack_hash(s,n) << 1)

Ugh. And the point of that would be? I.e. why not shift in the function itself?

OK, I'll go and do incremental over my variant and do some
tweaking on my side. Then I'll put both on ftp. I _really_ don't like the maybe_{un,}lock_page() logics. I'll try to come up with cleaner variant, but I think that you are trying to use the same function in situations of really different nature.
Off to diffing the thing...
Cheers,
Al

PS: Call me a style fascist, but _please_ get rid of preprocessor abuse. Code should be readable. Especially in the core kernel and frequently used filesystems.

#2 lore raw

Variant with saner locking is in the same place, called
ext2-dir-patch-b-S2.gz (no hash stuff, just an update of my part) Comments?
Cheers,
Al