]> git.ipfire.org Git - thirdparty/kernel/stable.git/blobdiff - fs/ext4/namei.c
ext4: allow directory holes
[thirdparty/kernel/stable.git] / fs / ext4 / namei.c
index 5d9ffa8efbfd817618cf64dc33ba678eef758d37..27b1fb2612bcdbb8e3b0e5cc97dc03d53bc08ee4 100644 (file)
@@ -81,8 +81,18 @@ static struct buffer_head *ext4_append(handle_t *handle,
 static int ext4_dx_csum_verify(struct inode *inode,
                               struct ext4_dir_entry *dirent);
 
+/*
+ * Hints to ext4_read_dirblock regarding whether we expect a directory
+ * block being read to be an index block, or a block containing
+ * directory entries (and if the latter, whether it was found via a
+ * logical block in an htree index block).  This is used to control
+ * what sort of sanity checkinig ext4_read_dirblock() will do on the
+ * directory block read from the storage device.  EITHER will means
+ * the caller doesn't know what kind of directory block will be read,
+ * so no specific verification will be done.
+ */
 typedef enum {
-       EITHER, INDEX, DIRENT
+       EITHER, INDEX, DIRENT, DIRENT_HTREE
 } dirblock_type_t;
 
 #define ext4_read_dirblock(inode, block, type) \
@@ -108,11 +118,14 @@ static struct buffer_head *__ext4_read_dirblock(struct inode *inode,
 
                return bh;
        }
-       if (!bh) {
+       if (!bh && (type == INDEX || type == DIRENT_HTREE)) {
                ext4_error_inode(inode, func, line, block,
-                                "Directory hole found");
+                                "Directory hole found for htree %s block",
+                                (type == INDEX) ? "index" : "leaf");
                return ERR_PTR(-EFSCORRUPTED);
        }
+       if (!bh)
+               return NULL;
        dirent = (struct ext4_dir_entry *) bh->b_data;
        /* Determine whether or not we have an index block */
        if (is_dx(inode)) {
@@ -979,7 +992,7 @@ static int htree_dirblock_to_tree(struct file *dir_file,
 
        dxtrace(printk(KERN_INFO "In htree dirblock_to_tree: block %lu\n",
                                                        (unsigned long)block));
-       bh = ext4_read_dirblock(dir, block, DIRENT);
+       bh = ext4_read_dirblock(dir, block, DIRENT_HTREE);
        if (IS_ERR(bh))
                return PTR_ERR(bh);
 
@@ -1509,7 +1522,7 @@ static struct buffer_head * ext4_dx_find_entry(struct inode *dir,
                return (struct buffer_head *) frame;
        do {
                block = dx_get_block(frame->at);
-               bh = ext4_read_dirblock(dir, block, DIRENT);
+               bh = ext4_read_dirblock(dir, block, DIRENT_HTREE);
                if (IS_ERR(bh))
                        goto errout;
 
@@ -2079,6 +2092,11 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
        blocks = dir->i_size >> sb->s_blocksize_bits;
        for (block = 0; block < blocks; block++) {
                bh = ext4_read_dirblock(dir, block, DIRENT);
+               if (bh == NULL) {
+                       bh = ext4_bread(handle, dir, block,
+                                       EXT4_GET_BLOCKS_CREATE);
+                       goto add_to_new_block;
+               }
                if (IS_ERR(bh)) {
                        retval = PTR_ERR(bh);
                        bh = NULL;
@@ -2099,6 +2117,7 @@ static int ext4_add_entry(handle_t *handle, struct dentry *dentry,
                brelse(bh);
        }
        bh = ext4_append(handle, dir, &block);
+add_to_new_block:
        if (IS_ERR(bh)) {
                retval = PTR_ERR(bh);
                bh = NULL;
@@ -2143,7 +2162,7 @@ again:
                return PTR_ERR(frame);
        entries = frame->entries;
        at = frame->at;
-       bh = ext4_read_dirblock(dir, dx_get_block(frame->at), DIRENT);
+       bh = ext4_read_dirblock(dir, dx_get_block(frame->at), DIRENT_HTREE);
        if (IS_ERR(bh)) {
                err = PTR_ERR(bh);
                bh = NULL;
@@ -2691,7 +2710,10 @@ bool ext4_empty_dir(struct inode *inode)
                EXT4_ERROR_INODE(inode, "invalid size");
                return true;
        }
-       bh = ext4_read_dirblock(inode, 0, EITHER);
+       /* The first directory block must not be a hole,
+        * so treat it as DIRENT_HTREE
+        */
+       bh = ext4_read_dirblock(inode, 0, DIRENT_HTREE);
        if (IS_ERR(bh))
                return true;
 
@@ -2713,6 +2735,10 @@ bool ext4_empty_dir(struct inode *inode)
                        brelse(bh);
                        lblock = offset >> EXT4_BLOCK_SIZE_BITS(sb);
                        bh = ext4_read_dirblock(inode, lblock, EITHER);
+                       if (bh == NULL) {
+                               offset += sb->s_blocksize;
+                               continue;
+                       }
                        if (IS_ERR(bh))
                                return true;
                        de = (struct ext4_dir_entry_2 *) bh->b_data;
@@ -3256,7 +3282,10 @@ static struct buffer_head *ext4_get_first_dir_block(handle_t *handle,
        struct buffer_head *bh;
 
        if (!ext4_has_inline_data(inode)) {
-               bh = ext4_read_dirblock(inode, 0, EITHER);
+               /* The first directory block must not be a hole, so
+                * treat it as DIRENT_HTREE
+                */
+               bh = ext4_read_dirblock(inode, 0, DIRENT_HTREE);
                if (IS_ERR(bh)) {
                        *retval = PTR_ERR(bh);
                        return NULL;