]> nv-tegra.nvidia Code Review - linux-2.6.git/blobdiff - fs/ufs/dir.c
ufs: convert to new aops
[linux-2.6.git] / fs / ufs / dir.c
index 7c10c68902ae2f22ba2e620c197e57d7233b4b5d..2410ec6002db55807945b126fa880a096036dc36 100644 (file)
  * 4.4BSD (FreeBSD) support added on February 1st 1998 by
  * Niels Kristian Bech Jensen <nkbj@image.dk> partially based
  * on code by Martin von Loewis <martin@mira.isdn.cs.tu-berlin.de>.
+ *
+ * Migration to usage of "page cache" on May 2006 by
+ * Evgeniy Dushistov <dushistov@mail.ru> based on ext2 code base.
  */
 
 #include <linux/time.h>
 #include <linux/fs.h>
 #include <linux/ufs_fs.h>
-#include <linux/smp_lock.h>
-#include <linux/buffer_head.h>
-#include <linux/sched.h>
+#include <linux/swap.h>
 
 #include "swab.h"
 #include "util.h"
 
-#undef UFS_DIR_DEBUG
-
-#ifdef UFS_DIR_DEBUG
-#define UFSD(x) printk("(%s, %d), %s: ", __FILE__, __LINE__, __FUNCTION__); printk x;
-#else
-#define UFSD(x)
-#endif
-
-static int
-ufs_check_dir_entry (const char *, struct inode *, struct ufs_dir_entry *,
-                    struct buffer_head *, unsigned long);
-
-
 /*
  * NOTE! unlike strncmp, ufs_match returns 1 for success, 0 for failure.
  *
@@ -51,495 +39,556 @@ static inline int ufs_match(struct super_block *sb, int len,
        return !memcmp(name, de->d_name, len);
 }
 
-/*
- * This is blatantly stolen from ext2fs
- */
-static int
-ufs_readdir (struct file * filp, void * dirent, filldir_t filldir)
+static int ufs_commit_chunk(struct page *page, loff_t pos, unsigned len)
 {
-       struct inode *inode = filp->f_dentry->d_inode;
-       int error = 0;
-       unsigned long offset, lblk;
-       int i, stored;
-       struct buffer_head * bh;
-       struct ufs_dir_entry * de;
-       struct super_block * sb;
-       int de_reclen;
-       unsigned flags;
-       u64     blk= 0L;
-
-       lock_kernel();
-
-       sb = inode->i_sb;
-       flags = UFS_SB(sb)->s_flags;
-
-       UFSD(("ENTER, ino %lu  f_pos %lu\n", inode->i_ino, (unsigned long) filp->f_pos))
-
-       stored = 0;
-       bh = NULL;
-       offset = filp->f_pos & (sb->s_blocksize - 1);
-
-       while (!error && !stored && filp->f_pos < inode->i_size) {
-               lblk = (filp->f_pos) >> sb->s_blocksize_bits;
-               blk = ufs_frag_map(inode, lblk);
-               if (!blk || !(bh = sb_bread(sb, blk))) {
-                       /* XXX - error - skip to the next block */
-                       printk("ufs_readdir: "
-                              "dir inode %lu has a hole at offset %lu\n",
-                              inode->i_ino, (unsigned long int)filp->f_pos);
-                       filp->f_pos += sb->s_blocksize - offset;
-                       continue;
-               }
+       struct address_space *mapping = page->mapping;
+       struct inode *dir = mapping->host;
+       int err = 0;
 
-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 ufs_dir_entry *)(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. */
-                               de_reclen = fs16_to_cpu(sb, de->d_reclen);
-                               if (de_reclen < 1)
-                                       break;
-                               i += de_reclen;
-                       }
-                       offset = i;
-                       filp->f_pos = (filp->f_pos & ~(sb->s_blocksize - 1))
-                               | offset;
-                       filp->f_version = inode->i_version;
-               }
+       dir->i_version++;
+       block_write_end(NULL, mapping, pos, len, len, page, NULL);
+       if (pos+len > dir->i_size) {
+               i_size_write(dir, pos+len);
+               mark_inode_dirty(dir);
+       }
+       if (IS_DIRSYNC(dir))
+               err = write_one_page(page, 1);
+       else
+               unlock_page(page);
+       return err;
+}
 
-               while (!error && filp->f_pos < inode->i_size
-                      && offset < sb->s_blocksize) {
-                       de = (struct ufs_dir_entry *) (bh->b_data + offset);
-                       /* XXX - put in a real ufs_check_dir_entry() */
-                       if ((de->d_reclen == 0) || (ufs_get_de_namlen(sb, de) == 0)) {
-                               filp->f_pos = (filp->f_pos &
-                                             (sb->s_blocksize - 1)) +
-                                              sb->s_blocksize;
-                               brelse(bh);
-                               unlock_kernel();
-                               return stored;
-                       }
-                       if (!ufs_check_dir_entry ("ufs_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);
-                               unlock_kernel();
-                               return stored;
-                       }
-                       offset += fs16_to_cpu(sb, de->d_reclen);
-                       if (de->d_ino) {
-                               /* 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;
+static inline void ufs_put_page(struct page *page)
+{
+       kunmap(page);
+       page_cache_release(page);
+}
 
-                               UFSD(("filldir(%s,%u)\n", de->d_name,
-                                                       fs32_to_cpu(sb, de->d_ino)))
-                               UFSD(("namlen %u\n", ufs_get_de_namlen(sb, de)))
+static inline unsigned long ufs_dir_pages(struct inode *inode)
+{
+       return (inode->i_size+PAGE_CACHE_SIZE-1)>>PAGE_CACHE_SHIFT;
+}
 
-                               if ((flags & UFS_DE_MASK) == UFS_DE_44BSD)
-                                       d_type = de->d_u.d_44.d_type;
-                               error = filldir(dirent, de->d_name,
-                                               ufs_get_de_namlen(sb, de), filp->f_pos,
-                                               fs32_to_cpu(sb, de->d_ino), d_type);
-                               if (error)
-                                       break;
-                               if (version != filp->f_version)
-                                       goto revalidate;
-                               stored ++;
-                       }
-                       filp->f_pos += fs16_to_cpu(sb, de->d_reclen);
-               }
-               offset = 0;
-               brelse (bh);
+ino_t ufs_inode_by_name(struct inode *dir, struct dentry *dentry)
+{
+       ino_t res = 0;
+       struct ufs_dir_entry *de;
+       struct page *page;
+       
+       de = ufs_find_entry(dir, dentry, &page);
+       if (de) {
+               res = fs32_to_cpu(dir->i_sb, de->d_ino);
+               ufs_put_page(page);
        }
-       unlock_kernel();
-       return 0;
+       return res;
 }
 
-/*
- * define how far ahead to read directories while searching them.
- */
-#define NAMEI_RA_CHUNKS  2
-#define NAMEI_RA_BLOCKS  4
-#define NAMEI_RA_SIZE        (NAMEI_RA_CHUNKS * NAMEI_RA_BLOCKS)
-#define NAMEI_RA_INDEX(c,b)  (((c) * NAMEI_RA_BLOCKS) + (b))
 
-/*
- *     ufs_find_entry()
- *
- * finds an entry in the specified directory with the wanted name. It
- * returns the cache buffer in which the entry was found, and the entry
- * itself (as a parameter - res_bh). It does NOT read the inode of the
- * entry - you'll have to do that yourself if you want to.
- */
-struct ufs_dir_entry * ufs_find_entry (struct dentry *dentry,
-       struct buffer_head ** res_bh)
+/* Releases the page */
+void ufs_set_link(struct inode *dir, struct ufs_dir_entry *de,
+                 struct page *page, struct inode *inode)
 {
-       struct super_block * sb;
-       struct buffer_head * bh_use[NAMEI_RA_SIZE];
-       struct buffer_head * bh_read[NAMEI_RA_SIZE];
-       unsigned long offset;
-       int block, toread, i, err;
-       struct inode *dir = dentry->d_parent->d_inode;
-       const char *name = dentry->d_name.name;
-       int namelen = dentry->d_name.len;
+       loff_t pos = page_offset(page) +
+                       (char *) de - (char *) page_address(page);
+       unsigned len = fs16_to_cpu(dir->i_sb, de->d_reclen);
+       int err;
 
-       UFSD(("ENTER, dir_ino %lu, name %s, namlen %u\n", dir->i_ino, name, namelen))
-       
-       *res_bh = NULL;
-       
-       sb = dir->i_sb;
-       
-       if (namelen > UFS_MAXNAMLEN)
-               return NULL;
+       lock_page(page);
+       err = __ufs_write_begin(NULL, page->mapping, pos, len,
+                               AOP_FLAG_UNINTERRUPTIBLE, &page, NULL);
+       BUG_ON(err);
 
-       memset (bh_use, 0, sizeof (bh_use));
-       toread = 0;
-       for (block = 0; block < NAMEI_RA_SIZE; ++block) {
-               struct buffer_head * bh;
+       de->d_ino = cpu_to_fs32(dir->i_sb, inode->i_ino);
+       ufs_set_de_type(dir->i_sb, de, inode->i_mode);
 
-               if ((block << sb->s_blocksize_bits) >= dir->i_size)
-                       break;
-               bh = ufs_getfrag (dir, block, 0, &err);
-               bh_use[block] = bh;
-               if (bh && !buffer_uptodate(bh))
-                       bh_read[toread++] = bh;
-       }
+       err = ufs_commit_chunk(page, pos, len);
+       ufs_put_page(page);
+       dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;
+       mark_inode_dirty(dir);
+}
 
-       for (block = 0, offset = 0; offset < dir->i_size; block++) {
-               struct buffer_head * bh;
-               struct ufs_dir_entry * de;
-               char * dlimit;
 
-               if ((block % NAMEI_RA_BLOCKS) == 0 && toread) {
-                       ll_rw_block (READ, toread, bh_read);
-                       toread = 0;
-               }
-               bh = bh_use[block % NAMEI_RA_SIZE];
-               if (!bh) {
-                       ufs_error (sb, "ufs_find_entry", 
-                               "directory #%lu contains a hole at offset %lu",
-                               dir->i_ino, offset);
-                       offset += sb->s_blocksize;
-                       continue;
-               }
-               wait_on_buffer (bh);
-               if (!buffer_uptodate(bh)) {
-                       /*
-                        * read error: all bets are off
-                        */
-                       break;
-               }
-
-               de = (struct ufs_dir_entry *) bh->b_data;
-               dlimit = bh->b_data + sb->s_blocksize;
-               while ((char *) de < dlimit && offset < dir->i_size) {
-                       /* this code is executed quadratically often */
-                       /* do minimal checking by hand */
-                       int de_len;
-
-                       if ((char *) de + namelen <= dlimit &&
-                           ufs_match(sb, namelen, name, de)) {
-                               /* found a match -
-                               just to be sure, do a full check */
-                               if (!ufs_check_dir_entry("ufs_find_entry",
-                                   dir, de, bh, offset))
-                                       goto failed;
-                               for (i = 0; i < NAMEI_RA_SIZE; ++i) {
-                                       if (bh_use[i] != bh)
-                                               brelse (bh_use[i]);
-                               }
-                               *res_bh = bh;
-                               return de;
-                       }
-                        /* prevent looping on a bad block */
-                       de_len = fs16_to_cpu(sb, de->d_reclen);
-                       if (de_len <= 0)
-                               goto failed;
-                       offset += de_len;
-                       de = (struct ufs_dir_entry *) ((char *) de + de_len);
-               }
+static void ufs_check_page(struct page *page)
+{
+       struct inode *dir = page->mapping->host;
+       struct super_block *sb = dir->i_sb;
+       char *kaddr = page_address(page);
+       unsigned offs, rec_len;
+       unsigned limit = PAGE_CACHE_SIZE;
+       const unsigned chunk_mask = UFS_SB(sb)->s_uspi->s_dirblksize - 1;
+       struct ufs_dir_entry *p;
+       char *error;
+
+       if ((dir->i_size >> PAGE_CACHE_SHIFT) == page->index) {
+               limit = dir->i_size & ~PAGE_CACHE_MASK;
+               if (limit & chunk_mask)
+                       goto Ebadsize;
+               if (!limit)
+                       goto out;
+       }
+       for (offs = 0; offs <= limit - UFS_DIR_REC_LEN(1); offs += rec_len) {
+               p = (struct ufs_dir_entry *)(kaddr + offs);
+               rec_len = fs16_to_cpu(sb, p->d_reclen);
+
+               if (rec_len < UFS_DIR_REC_LEN(1))
+                       goto Eshort;
+               if (rec_len & 3)
+                       goto Ealign;
+               if (rec_len < UFS_DIR_REC_LEN(ufs_get_de_namlen(sb, p)))
+                       goto Enamelen;
+               if (((offs + rec_len - 1) ^ offs) & ~chunk_mask)
+                       goto Espan;
+               if (fs32_to_cpu(sb, p->d_ino) > (UFS_SB(sb)->s_uspi->s_ipg *
+                                                 UFS_SB(sb)->s_uspi->s_ncg))
+                       goto Einumber;
+       }
+       if (offs != limit)
+               goto Eend;
+out:
+       SetPageChecked(page);
+       return;
+
+       /* Too bad, we had an error */
+
+Ebadsize:
+       ufs_error(sb, "ufs_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:
+       ufs_error (sb, "ufs_check_page", "bad entry in directory #%lu: %s - "
+                  "offset=%lu, rec_len=%d, name_len=%d",
+                  dir->i_ino, error, (page->index<<PAGE_CACHE_SHIFT)+offs,
+                  rec_len, ufs_get_de_namlen(sb, p));
+       goto fail;
+Eend:
+       p = (struct ufs_dir_entry *)(kaddr + offs);
+       ufs_error (sb, "ext2_check_page",
+                  "entry in directory #%lu spans the page boundary"
+                  "offset=%lu",
+                  dir->i_ino, (page->index<<PAGE_CACHE_SHIFT)+offs);
+fail:
+       SetPageChecked(page);
+       SetPageError(page);
+}
 
-               brelse (bh);
-               if (((block + NAMEI_RA_SIZE) << sb->s_blocksize_bits ) >=
-                   dir->i_size)
-                       bh = NULL;
-               else
-                       bh = ufs_getfrag (dir, block + NAMEI_RA_SIZE, 0, &err);
-               bh_use[block % NAMEI_RA_SIZE] = bh;
-               if (bh && !buffer_uptodate(bh))
-                       bh_read[toread++] = bh;
+static struct page *ufs_get_page(struct inode *dir, unsigned long n)
+{
+       struct address_space *mapping = dir->i_mapping;
+       struct page *page = read_mapping_page(mapping, n, NULL);
+       if (!IS_ERR(page)) {
+               kmap(page);
+               if (!PageChecked(page))
+                       ufs_check_page(page);
+               if (PageError(page))
+                       goto fail;
        }
+       return page;
 
-failed:
-       for (i = 0; i < NAMEI_RA_SIZE; ++i) brelse (bh_use[i]);
-       UFSD(("EXIT\n"))
-       return NULL;
+fail:
+       ufs_put_page(page);
+       return ERR_PTR(-EIO);
 }
 
-static int
-ufs_check_dir_entry (const char *function, struct inode *dir,
-                    struct ufs_dir_entry *de, struct buffer_head *bh,
-                    unsigned long offset)
+/*
+ * Return the offset into page `page_nr' of the last valid
+ * byte in that page, plus one.
+ */
+static unsigned
+ufs_last_byte(struct inode *inode, unsigned long page_nr)
 {
-       struct super_block *sb = dir->i_sb;
-       const char *error_msg = NULL;
-       int rlen = fs16_to_cpu(sb, de->d_reclen);
-
-       if (rlen < UFS_DIR_REC_LEN(1))
-               error_msg = "reclen is smaller than minimal";
-       else if (rlen % 4 != 0)
-               error_msg = "reclen % 4 != 0";
-       else if (rlen < UFS_DIR_REC_LEN(ufs_get_de_namlen(sb, de)))
-               error_msg = "reclen is too small for namlen";
-       else if (((char *) de - bh->b_data) + rlen > dir->i_sb->s_blocksize)
-               error_msg = "directory entry across blocks";
-       else if (fs32_to_cpu(sb, de->d_ino) > (UFS_SB(sb)->s_uspi->s_ipg *
-                                     UFS_SB(sb)->s_uspi->s_ncg))
-               error_msg = "inode out of bounds";
-
-       if (error_msg != NULL)
-               ufs_error (sb, function, "bad entry in directory #%lu, size %Lu: %s - "
-                           "offset=%lu, inode=%lu, reclen=%d, namlen=%d",
-                           dir->i_ino, dir->i_size, error_msg, offset,
-                           (unsigned long)fs32_to_cpu(sb, de->d_ino),
-                           rlen, ufs_get_de_namlen(sb, de));
-       
-       return (error_msg == NULL ? 1 : 0);
+       unsigned last_byte = inode->i_size;
+
+       last_byte -= page_nr << PAGE_CACHE_SHIFT;
+       if (last_byte > PAGE_CACHE_SIZE)
+               last_byte = PAGE_CACHE_SIZE;
+       return last_byte;
 }
 
-struct ufs_dir_entry *ufs_dotdot(struct inode *dir, struct buffer_head **p)
+static inline struct ufs_dir_entry *
+ufs_next_entry(struct super_block *sb, struct ufs_dir_entry *p)
 {
-       int err;
-       struct buffer_head *bh = ufs_bread (dir, 0, 0, &err);
-       struct ufs_dir_entry *res = NULL;
-
-       if (bh) {
-               res = (struct ufs_dir_entry *) bh->b_data;
-               res = (struct ufs_dir_entry *)((char *)res +
-                       fs16_to_cpu(dir->i_sb, res->d_reclen));
-       }
-       *p = bh;
-       return res;
+       return (struct ufs_dir_entry *)((char *)p +
+                                       fs16_to_cpu(sb, p->d_reclen));
 }
-ino_t ufs_inode_by_name(struct inode * dir, struct dentry *dentry)
+
+struct ufs_dir_entry *ufs_dotdot(struct inode *dir, struct page **p)
 {
-       ino_t res = 0;
-       struct ufs_dir_entry * de;
-       struct buffer_head *bh;
+       struct page *page = ufs_get_page(dir, 0);
+       struct ufs_dir_entry *de = NULL;
 
-       de = ufs_find_entry (dentry, &bh);
-       if (de) {
-               res = fs32_to_cpu(dir->i_sb, de->d_ino);
-               brelse(bh);
+       if (!IS_ERR(page)) {
+               de = ufs_next_entry(dir->i_sb,
+                                   (struct ufs_dir_entry *)page_address(page));
+               *p = page;
        }
-       return res;
+       return de;
 }
 
-void ufs_set_link(struct inode *dir, struct ufs_dir_entry *de,
-               struct buffer_head *bh, struct inode *inode)
+/*
+ *     ufs_find_entry()
+ *
+ * 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.
+ */
+struct ufs_dir_entry *ufs_find_entry(struct inode *dir, struct dentry *dentry,
+                                    struct page **res_page)
 {
-       dir->i_version++;
-       de->d_ino = cpu_to_fs32(dir->i_sb, inode->i_ino);
-       mark_buffer_dirty(bh);
-       if (IS_DIRSYNC(dir))
-               sync_dirty_buffer(bh);
-       brelse (bh);
+       struct super_block *sb = dir->i_sb;
+       const char *name = dentry->d_name.name;
+       int namelen = dentry->d_name.len;
+       unsigned reclen = UFS_DIR_REC_LEN(namelen);
+       unsigned long start, n;
+       unsigned long npages = ufs_dir_pages(dir);
+       struct page *page = NULL;
+       struct ufs_inode_info *ui = UFS_I(dir);
+       struct ufs_dir_entry *de;
+
+       UFSD("ENTER, dir_ino %lu, name %s, namlen %u\n", dir->i_ino, name, namelen);
+
+       if (npages == 0 || namelen > UFS_MAXNAMLEN)
+               goto out;
+
+       /* OFFSET_CACHE */
+       *res_page = NULL;
+
+       start = ui->i_dir_start_lookup;
+
+       if (start >= npages)
+               start = 0;
+       n = start;
+       do {
+               char *kaddr;
+               page = ufs_get_page(dir, n);
+               if (!IS_ERR(page)) {
+                       kaddr = page_address(page);
+                       de = (struct ufs_dir_entry *) kaddr;
+                       kaddr += ufs_last_byte(dir, n) - reclen;
+                       while ((char *) de <= kaddr) {
+                               if (de->d_reclen == 0) {
+                                       ufs_error(dir->i_sb, __FUNCTION__,
+                                                 "zero-length directory entry");
+                                       ufs_put_page(page);
+                                       goto out;
+                               }
+                               if (ufs_match(sb, namelen, name, de))
+                                       goto found;
+                               de = ufs_next_entry(sb, de);
+                       }
+                       ufs_put_page(page);
+               }
+               if (++n >= npages)
+                       n = 0;
+       } while (n != start);
+out:
+       return NULL;
+
+found:
+       *res_page = page;
+       ui->i_dir_start_lookup = n;
+       return de;
 }
 
 /*
- *     ufs_add_entry()
- *
- * adds a file entry to the specified directory, using the same
- * semantics as ufs_find_entry(). It returns NULL if it failed.
+ *     Parent is locked.
  */
 int ufs_add_link(struct dentry *dentry, struct inode *inode)
 {
-       struct super_block * sb;
-       struct ufs_sb_private_info * uspi;
-       unsigned long offset;
-       unsigned fragoff;
-       unsigned short rec_len;
-       struct buffer_head * bh;
-       struct ufs_dir_entry * de, * de1;
        struct inode *dir = dentry->d_parent->d_inode;
        const char *name = dentry->d_name.name;
        int namelen = dentry->d_name.len;
+       struct super_block *sb = dir->i_sb;
+       unsigned reclen = UFS_DIR_REC_LEN(namelen);
+       const unsigned int chunk_size = UFS_SB(sb)->s_uspi->s_dirblksize;
+       unsigned short rec_len, name_len;
+       struct page *page = NULL;
+       struct ufs_dir_entry *de;
+       unsigned long npages = ufs_dir_pages(dir);
+       unsigned long n;
+       char *kaddr;
+       loff_t pos;
        int err;
 
-       UFSD(("ENTER, name %s, namelen %u\n", name, namelen))
-       
-       sb = dir->i_sb;
-       uspi = UFS_SB(sb)->s_uspi;
-
-       if (!namelen)
-               return -EINVAL;
-       bh = ufs_bread (dir, 0, 0, &err);
-       if (!bh)
-               return err;
-       rec_len = UFS_DIR_REC_LEN(namelen);
-       offset = 0;
-       de = (struct ufs_dir_entry *) bh->b_data;
-       while (1) {
-               if ((char *)de >= UFS_SECTOR_SIZE + bh->b_data) {
-                       fragoff = offset & ~uspi->s_fmask;
-                       if (fragoff != 0 && fragoff != UFS_SECTOR_SIZE)
-                               ufs_error (sb, "ufs_add_entry", "internal error"
-                                       " fragoff %u", fragoff);
-                       if (!fragoff) {
-                               brelse (bh);
-                               bh = ufs_bread (dir, offset >> sb->s_blocksize_bits, 1, &err);
-                               if (!bh)
-                                       return err;
-                       }
-                       if (dir->i_size <= offset) {
-                               if (dir->i_size == 0) {
-                                       brelse(bh);
-                                       return -ENOENT;
-                               }
-                               de = (struct ufs_dir_entry *) (bh->b_data + fragoff);
+       UFSD("ENTER, name %s, namelen %u\n", name, namelen);
+
+       /*
+        * We take care of directory expansion in the same loop.
+        * This code plays outside i_size, so it locks the page
+        * to protect that region.
+        */
+       for (n = 0; n <= npages; n++) {
+               char *dir_end;
+
+               page = ufs_get_page(dir, n);
+               err = PTR_ERR(page);
+               if (IS_ERR(page))
+                       goto out;
+               lock_page(page);
+               kaddr = page_address(page);
+               dir_end = kaddr + ufs_last_byte(dir, n);
+               de = (struct ufs_dir_entry *)kaddr;
+               kaddr += PAGE_CACHE_SIZE - reclen;
+               while ((char *)de <= kaddr) {
+                       if ((char *)de == dir_end) {
+                               /* We hit i_size */
+                               name_len = 0;
+                               rec_len = chunk_size;
+                               de->d_reclen = cpu_to_fs16(sb, chunk_size);
                                de->d_ino = 0;
-                               de->d_reclen = cpu_to_fs16(sb, UFS_SECTOR_SIZE);
-                               ufs_set_de_namlen(sb, de, 0);
-                               dir->i_size = offset + UFS_SECTOR_SIZE;
-                               mark_inode_dirty(dir);
-                       } else {
-                               de = (struct ufs_dir_entry *) bh->b_data;
+                               goto got_it;
                        }
+                       if (de->d_reclen == 0) {
+                               ufs_error(dir->i_sb, __FUNCTION__,
+                                         "zero-length directory entry");
+                               err = -EIO;
+                               goto out_unlock;
+                       }
+                       err = -EEXIST;
+                       if (ufs_match(sb, namelen, name, de))
+                               goto out_unlock;
+                       name_len = UFS_DIR_REC_LEN(ufs_get_de_namlen(sb, de));
+                       rec_len = fs16_to_cpu(sb, de->d_reclen);
+                       if (!de->d_ino && rec_len >= reclen)
+                               goto got_it;
+                       if (rec_len >= name_len + reclen)
+                               goto got_it;
+                       de = (struct ufs_dir_entry *) ((char *) de + rec_len);
                }
-               if (!ufs_check_dir_entry ("ufs_add_entry", dir, de, bh, offset)) {
-                       brelse (bh);
-                       return -ENOENT;
-               }
-               if (ufs_match(sb, namelen, name, de)) {
-                       brelse (bh);
-                       return -EEXIST;
-               }
-               if (de->d_ino == 0 && fs16_to_cpu(sb, de->d_reclen) >= rec_len)
-                       break;
-                       
-               if (fs16_to_cpu(sb, de->d_reclen) >=
-                    UFS_DIR_REC_LEN(ufs_get_de_namlen(sb, de)) + rec_len)
-                       break;
-               offset += fs16_to_cpu(sb, de->d_reclen);
-               de = (struct ufs_dir_entry *) ((char *) de + fs16_to_cpu(sb, de->d_reclen));
+               unlock_page(page);
+               ufs_put_page(page);
        }
-
+       BUG();
+       return -EINVAL;
+
+got_it:
+       pos = page_offset(page) +
+                       (char*)de - (char*)page_address(page);
+       err = __ufs_write_begin(NULL, page->mapping, pos, rec_len,
+                               AOP_FLAG_UNINTERRUPTIBLE, &page, NULL);
+       if (err)
+               goto out_unlock;
        if (de->d_ino) {
-               de1 = (struct ufs_dir_entry *) ((char *) de +
-                       UFS_DIR_REC_LEN(ufs_get_de_namlen(sb, de)));
-               de1->d_reclen =
-                       cpu_to_fs16(sb, fs16_to_cpu(sb, de->d_reclen) -
-                               UFS_DIR_REC_LEN(ufs_get_de_namlen(sb, de)));
-               de->d_reclen =
-                       cpu_to_fs16(sb, UFS_DIR_REC_LEN(ufs_get_de_namlen(sb, de)));
+               struct ufs_dir_entry *de1 =
+                       (struct ufs_dir_entry *) ((char *) de + name_len);
+               de1->d_reclen = cpu_to_fs16(sb, rec_len - name_len);
+               de->d_reclen = cpu_to_fs16(sb, name_len);
+
                de = de1;
        }
-       de->d_ino = 0;
+
        ufs_set_de_namlen(sb, de, namelen);
-       memcpy (de->d_name, name, namelen + 1);
+       memcpy(de->d_name, name, namelen + 1);
        de->d_ino = cpu_to_fs32(sb, inode->i_ino);
        ufs_set_de_type(sb, de, inode->i_mode);
-       mark_buffer_dirty(bh);
-       if (IS_DIRSYNC(dir))
-               sync_dirty_buffer(bh);
-       brelse (bh);
+
+       err = ufs_commit_chunk(page, pos, rec_len);
        dir->i_mtime = dir->i_ctime = CURRENT_TIME_SEC;
-       dir->i_version++;
+
        mark_inode_dirty(dir);
+       /* OFFSET_CACHE */
+out_put:
+       ufs_put_page(page);
+out:
+       return err;
+out_unlock:
+       unlock_page(page);
+       goto out_put;
+}
+
+static inline unsigned
+ufs_validate_entry(struct super_block *sb, char *base,
+                  unsigned offset, unsigned mask)
+{
+       struct ufs_dir_entry *de = (struct ufs_dir_entry*)(base + offset);
+       struct ufs_dir_entry *p = (struct ufs_dir_entry*)(base + (offset&mask));
+       while ((char*)p < (char*)de) {
+               if (p->d_reclen == 0)
+                       break;
+               p = ufs_next_entry(sb, p);
+       }
+       return (char *)p - base;
+}
+
+
+/*
+ * This is blatantly stolen from ext2fs
+ */
+static int
+ufs_readdir(struct file *filp, void *dirent, filldir_t filldir)
+{
+       loff_t pos = filp->f_pos;
+       struct inode *inode = filp->f_path.dentry->d_inode;
+       struct super_block *sb = inode->i_sb;
+       unsigned int offset = pos & ~PAGE_CACHE_MASK;
+       unsigned long n = pos >> PAGE_CACHE_SHIFT;
+       unsigned long npages = ufs_dir_pages(inode);
+       unsigned chunk_mask = ~(UFS_SB(sb)->s_uspi->s_dirblksize - 1);
+       int need_revalidate = filp->f_version != inode->i_version;
+       unsigned flags = UFS_SB(sb)->s_flags;
+
+       UFSD("BEGIN\n");
+
+       if (pos > inode->i_size - UFS_DIR_REC_LEN(1))
+               return 0;
+
+       for ( ; n < npages; n++, offset = 0) {
+               char *kaddr, *limit;
+               struct ufs_dir_entry *de;
+
+               struct page *page = ufs_get_page(inode, n);
+
+               if (IS_ERR(page)) {
+                       ufs_error(sb, __FUNCTION__,
+                                 "bad page in #%lu",
+                                 inode->i_ino);
+                       filp->f_pos += PAGE_CACHE_SIZE - offset;
+                       return -EIO;
+               }
+               kaddr = page_address(page);
+               if (unlikely(need_revalidate)) {
+                       if (offset) {
+                               offset = ufs_validate_entry(sb, kaddr, offset, chunk_mask);
+                               filp->f_pos = (n<<PAGE_CACHE_SHIFT) + offset;
+                       }
+                       filp->f_version = inode->i_version;
+                       need_revalidate = 0;
+               }
+               de = (struct ufs_dir_entry *)(kaddr+offset);
+               limit = kaddr + ufs_last_byte(inode, n) - UFS_DIR_REC_LEN(1);
+               for ( ;(char*)de <= limit; de = ufs_next_entry(sb, de)) {
+                       if (de->d_reclen == 0) {
+                               ufs_error(sb, __FUNCTION__,
+                                       "zero-length directory entry");
+                               ufs_put_page(page);
+                               return -EIO;
+                       }
+                       if (de->d_ino) {
+                               int over;
+                               unsigned char d_type = DT_UNKNOWN;
 
-       UFSD(("EXIT\n"))
+                               offset = (char *)de - kaddr;
+
+                               UFSD("filldir(%s,%u)\n", de->d_name,
+                                     fs32_to_cpu(sb, de->d_ino));
+                               UFSD("namlen %u\n", ufs_get_de_namlen(sb, de));
+
+                               if ((flags & UFS_DE_MASK) == UFS_DE_44BSD)
+                                       d_type = de->d_u.d_44.d_type;
+
+                               over = filldir(dirent, de->d_name,
+                                              ufs_get_de_namlen(sb, de),
+                                               (n<<PAGE_CACHE_SHIFT) | offset,
+                                              fs32_to_cpu(sb, de->d_ino), d_type);
+                               if (over) {
+                                       ufs_put_page(page);
+                                       return 0;
+                               }
+                       }
+                       filp->f_pos += fs16_to_cpu(sb, de->d_reclen);
+               }
+               ufs_put_page(page);
+       }
        return 0;
 }
 
+
 /*
  * ufs_delete_entry deletes a directory entry by merging it with the
  * previous entry.
  */
-int ufs_delete_entry (struct inode * inode, struct ufs_dir_entry * dir,
-       struct buffer_head * bh )
-       
+int ufs_delete_entry(struct inode *inode, struct ufs_dir_entry *dir,
+                    struct page * page)
 {
-       struct super_block * sb;
-       struct ufs_dir_entry * de, * pde;
-       unsigned i;
-       
-       UFSD(("ENTER\n"))
+       struct super_block *sb = inode->i_sb;
+       struct address_space *mapping = page->mapping;
+       char *kaddr = page_address(page);
+       unsigned from = ((char*)dir - kaddr) & ~(UFS_SB(sb)->s_uspi->s_dirblksize - 1);
+       unsigned to = ((char*)dir - kaddr) + fs16_to_cpu(sb, dir->d_reclen);
+       loff_t pos;
+       struct ufs_dir_entry *pde = NULL;
+       struct ufs_dir_entry *de = (struct ufs_dir_entry *) (kaddr + from);
+       int err;
 
-       sb = inode->i_sb;
-       i = 0;
-       pde = NULL;
-       de = (struct ufs_dir_entry *) bh->b_data;
-       
-       UFSD(("ino %u, reclen %u, namlen %u, name %s\n",
-               fs32_to_cpu(sb, de->d_ino),
-               fs16_to_cpu(sb, de->d_reclen),
-               ufs_get_de_namlen(sb, de), de->d_name))
-
-       while (i < bh->b_size) {
-               if (!ufs_check_dir_entry ("ufs_delete_entry", inode, de, bh, i)) {
-                       brelse(bh);
-                       return -EIO;
-               }
-               if (de == dir)  {
-                       if (pde)
-                               fs16_add(sb, &pde->d_reclen,
-                                       fs16_to_cpu(sb, dir->d_reclen));
-                       dir->d_ino = 0;
-                       inode->i_version++;
-                       inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;
-                       mark_inode_dirty(inode);
-                       mark_buffer_dirty(bh);
-                       if (IS_DIRSYNC(inode))
-                               sync_dirty_buffer(bh);
-                       brelse(bh);
-                       UFSD(("EXIT\n"))
-                       return 0;
+       UFSD("ENTER\n");
+
+       UFSD("ino %u, reclen %u, namlen %u, name %s\n",
+             fs32_to_cpu(sb, de->d_ino),
+             fs16_to_cpu(sb, de->d_reclen),
+             ufs_get_de_namlen(sb, de), de->d_name);
+
+       while ((char*)de < (char*)dir) {
+               if (de->d_reclen == 0) {
+                       ufs_error(inode->i_sb, __FUNCTION__,
+                                 "zero-length directory entry");
+                       err = -EIO;
+                       goto out;
                }
-               i += fs16_to_cpu(sb, de->d_reclen);
-               if (i == UFS_SECTOR_SIZE) pde = NULL;
-               else pde = de;
-               de = (struct ufs_dir_entry *)
-                   ((char *) de + fs16_to_cpu(sb, de->d_reclen));
-               if (i == UFS_SECTOR_SIZE && de->d_reclen == 0)
-                       break;
+               pde = de;
+               de = ufs_next_entry(sb, de);
        }
-       UFSD(("EXIT\n"))
-       brelse(bh);
-       return -ENOENT;
+       if (pde)
+               from = (char*)pde - (char*)page_address(page);
+
+       pos = page_offset(page) + from;
+       lock_page(page);
+       err = __ufs_write_begin(NULL, mapping, pos, to - from,
+                               AOP_FLAG_UNINTERRUPTIBLE, &page, NULL);
+       BUG_ON(err);
+       if (pde)
+               pde->d_reclen = cpu_to_fs16(sb, to - from);
+       dir->d_ino = 0;
+       err = ufs_commit_chunk(page, pos, to - from);
+       inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC;
+       mark_inode_dirty(inode);
+out:
+       ufs_put_page(page);
+       UFSD("EXIT\n");
+       return err;
 }
 
 int ufs_make_empty(struct inode * inode, struct inode *dir)
 {
        struct super_block * sb = dir->i_sb;
-       struct buffer_head * dir_block;
+       struct address_space *mapping = inode->i_mapping;
+       struct page *page = grab_cache_page(mapping, 0);
+       const unsigned int chunk_size = UFS_SB(sb)->s_uspi->s_dirblksize;
        struct ufs_dir_entry * de;
+       char *base;
        int err;
 
-       dir_block = ufs_bread (inode, 0, 1, &err);
-       if (!dir_block)
-               return err;
+       if (!page)
+               return -ENOMEM;
+
+       err = __ufs_write_begin(NULL, mapping, 0, chunk_size,
+                               AOP_FLAG_UNINTERRUPTIBLE, &page, NULL);
+       if (err) {
+               unlock_page(page);
+               goto fail;
+       }
+
+       kmap(page);
+       base = (char*)page_address(page);
+       memset(base, 0, PAGE_CACHE_SIZE);
+
+       de = (struct ufs_dir_entry *) base;
 
-       inode->i_blocks = sb->s_blocksize / UFS_SECTOR_SIZE;
-       de = (struct ufs_dir_entry *) dir_block->b_data;
        de->d_ino = cpu_to_fs32(sb, inode->i_ino);
        ufs_set_de_type(sb, de, inode->i_mode);
        ufs_set_de_namlen(sb, de, 1);
@@ -549,78 +598,71 @@ int ufs_make_empty(struct inode * inode, struct inode *dir)
                ((char *)de + fs16_to_cpu(sb, de->d_reclen));
        de->d_ino = cpu_to_fs32(sb, dir->i_ino);
        ufs_set_de_type(sb, de, dir->i_mode);
-       de->d_reclen = cpu_to_fs16(sb, UFS_SECTOR_SIZE - UFS_DIR_REC_LEN(1));
+       de->d_reclen = cpu_to_fs16(sb, chunk_size - UFS_DIR_REC_LEN(1));
        ufs_set_de_namlen(sb, de, 2);
        strcpy (de->d_name, "..");
-       mark_buffer_dirty(dir_block);
-       brelse (dir_block);
-       mark_inode_dirty(inode);
-       return 0;
+       kunmap(page);
+
+       err = ufs_commit_chunk(page, 0, chunk_size);
+fail:
+       page_cache_release(page);
+       return err;
 }
 
 /*
  * routine to check that the specified directory is empty (for rmdir)
  */
-int ufs_empty_dir (struct inode * inode)
+int ufs_empty_dir(struct inode * inode)
 {
-       struct super_block * sb;
-       unsigned long offset;
-       struct buffer_head * bh;
-       struct ufs_dir_entry * de, * de1;
-       int err;
-       
-       sb = inode->i_sb;
-
-       if (inode->i_size < UFS_DIR_REC_LEN(1) + UFS_DIR_REC_LEN(2) ||
-           !(bh = ufs_bread (inode, 0, 0, &err))) {
-               ufs_warning (inode->i_sb, "empty_dir",
-                             "bad directory (dir #%lu) - no data block",
-                             inode->i_ino);
-               return 1;
-       }
-       de = (struct ufs_dir_entry *) bh->b_data;
-       de1 = (struct ufs_dir_entry *)
-               ((char *)de + fs16_to_cpu(sb, de->d_reclen));
-       if (fs32_to_cpu(sb, de->d_ino) != inode->i_ino || de1->d_ino == 0 ||
-            strcmp (".", de->d_name) || strcmp ("..", de1->d_name)) {
-               ufs_warning (inode->i_sb, "empty_dir",
-                             "bad directory (dir #%lu) - no `.' or `..'",
-                             inode->i_ino);
-               return 1;
-       }
-       offset = fs16_to_cpu(sb, de->d_reclen) + fs16_to_cpu(sb, de1->d_reclen);
-       de = (struct ufs_dir_entry *)
-               ((char *)de1 + fs16_to_cpu(sb, de1->d_reclen));
-       while (offset < inode->i_size ) {
-               if (!bh || (void *) de >= (void *) (bh->b_data + sb->s_blocksize)) {
-                       brelse (bh);
-                       bh = ufs_bread (inode, offset >> sb->s_blocksize_bits, 1, &err);
-                       if (!bh) {
-                               ufs_error (sb, "empty_dir",
-                                           "directory #%lu contains a hole at offset %lu",
-                                           inode->i_ino, offset);
-                               offset += sb->s_blocksize;
-                               continue;
+       struct super_block *sb = inode->i_sb;
+       struct page *page = NULL;
+       unsigned long i, npages = ufs_dir_pages(inode);
+
+       for (i = 0; i < npages; i++) {
+               char *kaddr;
+               struct ufs_dir_entry *de;
+               page = ufs_get_page(inode, i);
+
+               if (IS_ERR(page))
+                       continue;
+
+               kaddr = page_address(page);
+               de = (struct ufs_dir_entry *)kaddr;
+               kaddr += ufs_last_byte(inode, i) - UFS_DIR_REC_LEN(1);
+
+               while ((char *)de <= kaddr) {
+                       if (de->d_reclen == 0) {
+                               ufs_error(inode->i_sb, __FUNCTION__,
+                                       "zero-length directory entry: "
+                                       "kaddr=%p, de=%p\n", kaddr, de);
+                               goto not_empty;
                        }
-                       de = (struct ufs_dir_entry *) bh->b_data;
-               }
-               if (!ufs_check_dir_entry ("empty_dir", inode, de, bh, offset)) {
-                       brelse (bh);
-                       return 1;
-               }
-               if (de->d_ino) {
-                       brelse (bh);
-                       return 0;
+                       if (de->d_ino) {
+                               u16 namelen=ufs_get_de_namlen(sb, de);
+                               /* check for . and .. */
+                               if (de->d_name[0] != '.')
+                                       goto not_empty;
+                               if (namelen > 2)
+                                       goto not_empty;
+                               if (namelen < 2) {
+                                       if (inode->i_ino !=
+                                           fs32_to_cpu(sb, de->d_ino))
+                                               goto not_empty;
+                               } else if (de->d_name[1] != '.')
+                                       goto not_empty;
+                       }
+                       de = ufs_next_entry(sb, de);
                }
-               offset += fs16_to_cpu(sb, de->d_reclen);
-               de = (struct ufs_dir_entry *)
-                       ((char *)de + fs16_to_cpu(sb, de->d_reclen));
+               ufs_put_page(page);
        }
-       brelse (bh);
        return 1;
+
+not_empty:
+       ufs_put_page(page);
+       return 0;
 }
 
-struct file_operations ufs_dir_operations = {
+const struct file_operations ufs_dir_operations = {
        .read           = generic_read_dir,
        .readdir        = ufs_readdir,
        .fsync          = file_fsync,