[readdir] convert ext3
authorAl Viro <viro@zeniv.linux.org.uk>
Thu, 16 May 2013 01:02:48 +0000 (21:02 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Sat, 29 Jun 2013 08:46:49 +0000 (12:46 +0400)
new helper: dir_relax(inode).  Call when you are in location that will
_not_ be invalidated by directory modifications (block boundary, in case
of ext*).  Returns whether the directory has survived (dropping i_mutex
allows rmdir to kill the sucker; if it returns false to us, ->iterate()
is obviously done)

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/ext3/dir.c
include/linux/fs.h

index 87eccbbca25541a45f8e4b008728376cf7b59f5b..f522425aaa24ff56f6e01f02c2f3313819f62ca1 100644 (file)
@@ -28,8 +28,7 @@ static unsigned char ext3_filetype_table[] = {
        DT_UNKNOWN, DT_REG, DT_DIR, DT_CHR, DT_BLK, DT_FIFO, DT_SOCK, DT_LNK
 };
 
-static int ext3_dx_readdir(struct file * filp,
-                          void * dirent, filldir_t filldir);
+static int ext3_dx_readdir(struct file *, struct dir_context *);
 
 static unsigned char get_dtype(struct super_block *sb, int filetype)
 {
@@ -91,36 +90,30 @@ int ext3_check_dir_entry (const char * function, struct inode * dir,
        return error_msg == NULL ? 1 : 0;
 }
 
-static int ext3_readdir(struct file * filp,
-                        void * dirent, filldir_t filldir)
+static int ext3_readdir(struct file *file, struct dir_context *ctx)
 {
-       int error = 0;
        unsigned long offset;
-       int i, stored;
+       int i;
        struct ext3_dir_entry_2 *de;
        int err;
-       struct inode *inode = file_inode(filp);
+       struct inode *inode = file_inode(file);
        struct super_block *sb = inode->i_sb;
-       int ret = 0;
        int dir_has_error = 0;
 
        if (is_dx_dir(inode)) {
-               err = ext3_dx_readdir(filp, dirent, filldir);
-               if (err != ERR_BAD_DX_DIR) {
-                       ret = err;
-                       goto out;
-               }
+               err = ext3_dx_readdir(file, ctx);
+               if (err != ERR_BAD_DX_DIR)
+                       return err;
                /*
                 * We don't set the inode dirty flag since it's not
                 * critical that it get flushed back to the disk.
                 */
-               EXT3_I(file_inode(filp))->i_flags &= ~EXT3_INDEX_FL;
+               EXT3_I(inode)->i_flags &= ~EXT3_INDEX_FL;
        }
-       stored = 0;
-       offset = filp->f_pos & (sb->s_blocksize - 1);
+       offset = ctx->pos & (sb->s_blocksize - 1);
 
-       while (!error && !stored && filp->f_pos < inode->i_size) {
-               unsigned long blk = filp->f_pos >> EXT3_BLOCK_SIZE_BITS(sb);
+       while (ctx->pos < inode->i_size) {
+               unsigned long blk = ctx->pos >> EXT3_BLOCK_SIZE_BITS(sb);
                struct buffer_head map_bh;
                struct buffer_head *bh = NULL;
 
@@ -129,12 +122,12 @@ static int ext3_readdir(struct file * filp,
                if (err > 0) {
                        pgoff_t index = map_bh.b_blocknr >>
                                        (PAGE_CACHE_SHIFT - inode->i_blkbits);
-                       if (!ra_has_index(&filp->f_ra, index))
+                       if (!ra_has_index(&file->f_ra, index))
                                page_cache_sync_readahead(
                                        sb->s_bdev->bd_inode->i_mapping,
-                                       &filp->f_ra, filp,
+                                       &file->f_ra, file,
                                        index, 1);
-                       filp->f_ra.prev_pos = (loff_t)index << PAGE_CACHE_SHIFT;
+                       file->f_ra.prev_pos = (loff_t)index << PAGE_CACHE_SHIFT;
                        bh = ext3_bread(NULL, inode, blk, 0, &err);
                }
 
@@ -146,22 +139,21 @@ static int ext3_readdir(struct file * filp,
                        if (!dir_has_error) {
                                ext3_error(sb, __func__, "directory #%lu "
                                        "contains a hole at offset %lld",
-                                       inode->i_ino, filp->f_pos);
+                                       inode->i_ino, ctx->pos);
                                dir_has_error = 1;
                        }
                        /* corrupt size?  Maybe no more blocks to read */
-                       if (filp->f_pos > inode->i_blocks << 9)
+                       if (ctx->pos > inode->i_blocks << 9)
                                break;
-                       filp->f_pos += sb->s_blocksize - offset;
+                       ctx->pos += sb->s_blocksize - offset;
                        continue;
                }
 
-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) {
+               if (offset && file->f_version != inode->i_version) {
                        for (i = 0; i < sb->s_blocksize && i < offset; ) {
                                de = (struct ext3_dir_entry_2 *)
                                        (bh->b_data + i);
@@ -177,53 +169,40 @@ revalidate:
                                i += ext3_rec_len_from_disk(de->rec_len);
                        }
                        offset = i;
-                       filp->f_pos = (filp->f_pos & ~(sb->s_blocksize - 1))
+                       ctx->pos = (ctx->pos & ~(sb->s_blocksize - 1))
                                | offset;
-                       filp->f_version = inode->i_version;
+                       file->f_version = inode->i_version;
                }
 
-               while (!error && filp->f_pos < inode->i_size
+               while (ctx->pos < inode->i_size
                       && offset < sb->s_blocksize) {
                        de = (struct ext3_dir_entry_2 *) (bh->b_data + offset);
                        if (!ext3_check_dir_entry ("ext3_readdir", inode, de,
                                                   bh, offset)) {
-                               /* On error, skip the f_pos to the
+                               /* On error, skip the to the
                                    next block. */
-                               filp->f_pos = (filp->f_pos |
+                               ctx->pos = (ctx->pos |
                                                (sb->s_blocksize - 1)) + 1;
-                               brelse (bh);
-                               ret = stored;
-                               goto out;
+                               break;
                        }
                        offset += ext3_rec_len_from_disk(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.
-                                */
-                               u64 version = filp->f_version;
-
-                               error = filldir(dirent, de->name,
-                                               de->name_len,
-                                               filp->f_pos,
-                                               le32_to_cpu(de->inode),
-                                               get_dtype(sb, de->file_type));
-                               if (error)
-                                       break;
-                               if (version != filp->f_version)
-                                       goto revalidate;
-                               stored ++;
+                               if (!dir_emit(ctx, de->name, de->name_len,
+                                             le32_to_cpu(de->inode),
+                                             get_dtype(sb, de->file_type))) {
+                                       brelse(bh);
+                                       return 0;
+                               }
                        }
-                       filp->f_pos += ext3_rec_len_from_disk(de->rec_len);
+                       ctx->pos += ext3_rec_len_from_disk(de->rec_len);
                }
                offset = 0;
                brelse (bh);
+               if (ctx->pos < inode->i_size)
+                       if (!dir_relax(inode))
+                               return 0;
        }
-out:
-       return ret;
+       return 0;
 }
 
 static inline int is_32bit_api(void)
@@ -452,62 +431,54 @@ int ext3_htree_store_dirent(struct file *dir_file, __u32 hash,
  * for all entres on the fname linked list.  (Normally there is only
  * one entry on the linked list, unless there are 62 bit hash collisions.)
  */
-static int call_filldir(struct file * filp, void * dirent,
-                       filldir_t filldir, struct fname *fname)
+static bool call_filldir(struct file *file, struct dir_context *ctx,
+                       struct fname *fname)
 {
-       struct dir_private_info *info = filp->private_data;
-       loff_t  curr_pos;
-       struct inode *inode = file_inode(filp);
-       struct super_block * sb;
-       int error;
-
-       sb = inode->i_sb;
+       struct dir_private_info *info = file->private_data;
+       struct inode *inode = file_inode(file);
+       struct super_block *sb = inode->i_sb;
 
        if (!fname) {
                printk("call_filldir: called with null fname?!?\n");
-               return 0;
+               return true;
        }
-       curr_pos = hash2pos(filp, fname->hash, fname->minor_hash);
+       ctx->pos = hash2pos(file, fname->hash, fname->minor_hash);
        while (fname) {
-               error = filldir(dirent, fname->name,
-                               fname->name_len, curr_pos,
+               if (!dir_emit(ctx, fname->name, fname->name_len,
                                fname->inode,
-                               get_dtype(sb, fname->file_type));
-               if (error) {
-                       filp->f_pos = curr_pos;
+                               get_dtype(sb, fname->file_type))) {
                        info->extra_fname = fname;
-                       return error;
+                       return false;
                }
                fname = fname->next;
        }
-       return 0;
+       return true;
 }
 
-static int ext3_dx_readdir(struct file * filp,
-                        void * dirent, filldir_t filldir)
+static int ext3_dx_readdir(struct file *file, struct dir_context *ctx)
 {
-       struct dir_private_info *info = filp->private_data;
-       struct inode *inode = file_inode(filp);
+       struct dir_private_info *info = file->private_data;
+       struct inode *inode = file_inode(file);
        struct fname *fname;
        int     ret;
 
        if (!info) {
-               info = ext3_htree_create_dir_info(filp, filp->f_pos);
+               info = ext3_htree_create_dir_info(file, ctx->pos);
                if (!info)
                        return -ENOMEM;
-               filp->private_data = info;
+               file->private_data = info;
        }
 
-       if (filp->f_pos == ext3_get_htree_eof(filp))
+       if (ctx->pos == ext3_get_htree_eof(file))
                return 0;       /* EOF */
 
        /* Some one has messed with f_pos; reset the world */
-       if (info->last_pos != filp->f_pos) {
+       if (info->last_pos != ctx->pos) {
                free_rb_tree_fname(&info->root);
                info->curr_node = NULL;
                info->extra_fname = NULL;
-               info->curr_hash = pos2maj_hash(filp, filp->f_pos);
-               info->curr_minor_hash = pos2min_hash(filp, filp->f_pos);
+               info->curr_hash = pos2maj_hash(file, ctx->pos);
+               info->curr_minor_hash = pos2min_hash(file, ctx->pos);
        }
 
        /*
@@ -515,7 +486,7 @@ static int ext3_dx_readdir(struct file * filp,
         * chain, return them first.
         */
        if (info->extra_fname) {
-               if (call_filldir(filp, dirent, filldir, info->extra_fname))
+               if (!call_filldir(file, ctx, info->extra_fname))
                        goto finished;
                info->extra_fname = NULL;
                goto next_node;
@@ -529,17 +500,17 @@ static int ext3_dx_readdir(struct file * filp,
                 * cached entries.
                 */
                if ((!info->curr_node) ||
-                   (filp->f_version != inode->i_version)) {
+                   (file->f_version != inode->i_version)) {
                        info->curr_node = NULL;
                        free_rb_tree_fname(&info->root);
-                       filp->f_version = inode->i_version;
-                       ret = ext3_htree_fill_tree(filp, info->curr_hash,
+                       file->f_version = inode->i_version;
+                       ret = ext3_htree_fill_tree(file, info->curr_hash,
                                                   info->curr_minor_hash,
                                                   &info->next_hash);
                        if (ret < 0)
                                return ret;
                        if (ret == 0) {
-                               filp->f_pos = ext3_get_htree_eof(filp);
+                               ctx->pos = ext3_get_htree_eof(file);
                                break;
                        }
                        info->curr_node = rb_first(&info->root);
@@ -548,7 +519,7 @@ static int ext3_dx_readdir(struct file * filp,
                fname = rb_entry(info->curr_node, struct fname, rb_hash);
                info->curr_hash = fname->hash;
                info->curr_minor_hash = fname->minor_hash;
-               if (call_filldir(filp, dirent, filldir, fname))
+               if (!call_filldir(file, ctx, fname))
                        break;
        next_node:
                info->curr_node = rb_next(info->curr_node);
@@ -559,7 +530,7 @@ static int ext3_dx_readdir(struct file * filp,
                        info->curr_minor_hash = fname->minor_hash;
                } else {
                        if (info->next_hash == ~0) {
-                               filp->f_pos = ext3_get_htree_eof(filp);
+                               ctx->pos = ext3_get_htree_eof(file);
                                break;
                        }
                        info->curr_hash = info->next_hash;
@@ -567,7 +538,7 @@ static int ext3_dx_readdir(struct file * filp,
                }
        }
 finished:
-       info->last_pos = filp->f_pos;
+       info->last_pos = ctx->pos;
        return 0;
 }
 
@@ -582,7 +553,7 @@ static int ext3_release_dir (struct inode * inode, struct file * filp)
 const struct file_operations ext3_dir_operations = {
        .llseek         = ext3_dir_llseek,
        .read           = generic_read_dir,
-       .readdir        = ext3_readdir,
+       .iterate        = ext3_readdir,
        .unlocked_ioctl = ext3_ioctl,
 #ifdef CONFIG_COMPAT
        .compat_ioctl   = ext3_compat_ioctl,
index 40293a6ce804b437c9123a2006a9ec73ec5b281b..aa9770c7e8dff5e581adebfcc8a035cccc42b611 100644 (file)
@@ -2725,5 +2725,11 @@ static inline bool dir_emit_dots(struct file *file, struct dir_context *ctx)
        }
        return true;
 }
+static inline bool dir_relax(struct inode *inode)
+{
+       mutex_unlock(&inode->i_mutex);
+       mutex_lock(&inode->i_mutex);
+       return !IS_DEADDIR(inode);
+}
 
 #endif /* _LINUX_FS_H */