hfsplus: fix "unused node is not erased" error
authorSergei Antonov <saproj@gmail.com>
Fri, 6 Jun 2014 21:36:28 +0000 (14:36 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 6 Jun 2014 23:08:10 +0000 (16:08 -0700)
Zero newly allocated extents in the catalog tree if volume attributes
tell us to.  Not doing so we risk getting the "unused node is not
erased" error.  See kHFSUnusedNodeFix flag in Apple's source code for
reference.

There was a previous commit clearing the node when it is freed: commit
899bed05e9f6 ("hfsplus: fix issue with unzeroed unused b-tree nodes").
But it did not handle newly allocated extents (this patch fixes it).
And it zeroed nodes in all trees unconditionally which is an overkill.

This patch adds a condition and also switches to 'tree->node_size' as a
simpler method of getting the length to zero.

Signed-off-by: Sergei Antonov <saproj@gmail.com>
Cc: Anton Altaparmakov <aia21@cam.ac.uk>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christoph Hellwig <hch@infradead.org>
Cc: Vyacheslav Dubeyko <slava@dubeyko.com>
Cc: Hin-Tak Leung <htl10@users.sourceforge.net>
Cc: Kyle Laracey <kalaracey@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
fs/hfsplus/bnode.c
fs/hfsplus/btree.c
fs/hfsplus/extents.c
fs/hfsplus/hfsplus_fs.h
fs/hfsplus/hfsplus_raw.h
fs/hfsplus/xattr.c

index 285502af8df11c35d34559bb28b2b76db3fa974c..759708fd9331cc37a6775c31e068117eddbec11d 100644 (file)
@@ -646,8 +646,8 @@ void hfs_bnode_put(struct hfs_bnode *node)
                if (test_bit(HFS_BNODE_DELETED, &node->flags)) {
                        hfs_bnode_unhash(node);
                        spin_unlock(&tree->hash_lock);
-                       hfs_bnode_clear(node, 0,
-                               PAGE_CACHE_SIZE * tree->pages_per_bnode);
+                       if (hfs_bnode_need_zeroout(tree))
+                               hfs_bnode_clear(node, 0, tree->node_size);
                        hfs_bmap_free(node);
                        hfs_bnode_free(node);
                        return;
@@ -656,3 +656,16 @@ void hfs_bnode_put(struct hfs_bnode *node)
        }
 }
 
+/*
+ * Unused nodes have to be zeroed if this is the catalog tree and
+ * a corresponding flag in the volume header is set.
+ */
+bool hfs_bnode_need_zeroout(struct hfs_btree *tree)
+{
+       struct super_block *sb = tree->inode->i_sb;
+       struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
+       const u32 volume_attr = be32_to_cpu(sbi->s_vhdr->attributes);
+
+       return tree->cnid == HFSPLUS_CAT_CNID &&
+               volume_attr & HFSPLUS_VOL_UNUSED_NODE_FIX;
+}
index 0fcec8b2a90b84160ed4c8109aa8dcda912487e7..3345c7553edc131302b5a2202418f3c47a0e4f03 100644 (file)
@@ -358,7 +358,7 @@ struct hfs_bnode *hfs_bmap_alloc(struct hfs_btree *tree)
                u32 count;
                int res;
 
-               res = hfsplus_file_extend(inode);
+               res = hfsplus_file_extend(inode, hfs_bnode_need_zeroout(tree));
                if (res)
                        return ERR_PTR(res);
                hip->phys_size = inode->i_size =
index a7aafb35b6243d17f3bc23d2e22ad3a9e4de5a8b..a09fcb68c36472cf9877c4a16dedc5f39ba87f0b 100644 (file)
@@ -235,7 +235,7 @@ int hfsplus_get_block(struct inode *inode, sector_t iblock,
                if (iblock > hip->fs_blocks || !create)
                        return -EIO;
                if (ablock >= hip->alloc_blocks) {
-                       res = hfsplus_file_extend(inode);
+                       res = hfsplus_file_extend(inode, false);
                        if (res)
                                return res;
                }
@@ -425,7 +425,7 @@ int hfsplus_free_fork(struct super_block *sb, u32 cnid,
        return res;
 }
 
-int hfsplus_file_extend(struct inode *inode)
+int hfsplus_file_extend(struct inode *inode, bool zeroout)
 {
        struct super_block *sb = inode->i_sb;
        struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
@@ -463,6 +463,12 @@ int hfsplus_file_extend(struct inode *inode)
                }
        }
 
+       if (zeroout) {
+               res = sb_issue_zeroout(sb, start, len, GFP_NOFS);
+               if (res)
+                       goto out;
+       }
+
        hfs_dbg(EXTENT, "extend %lu: %u,%u\n", inode->i_ino, start, len);
 
        if (hip->alloc_blocks <= hip->first_blocks) {
index 6c08ff6b11b2c16ea65e59ece543db74038804d4..d5ab79bd2f0f953d364e898b455514e2a9061831 100644 (file)
@@ -414,6 +414,7 @@ void hfs_bnode_free(struct hfs_bnode *);
 struct hfs_bnode *hfs_bnode_create(struct hfs_btree *, u32);
 void hfs_bnode_get(struct hfs_bnode *);
 void hfs_bnode_put(struct hfs_bnode *);
+bool hfs_bnode_need_zeroout(struct hfs_btree *);
 
 /* brec.c */
 u16 hfs_brec_lenoff(struct hfs_bnode *, u16, u16 *);
@@ -460,7 +461,7 @@ int hfsplus_ext_write_extent(struct inode *);
 int hfsplus_get_block(struct inode *, sector_t, struct buffer_head *, int);
 int hfsplus_free_fork(struct super_block *, u32,
                struct hfsplus_fork_raw *, int);
-int hfsplus_file_extend(struct inode *);
+int hfsplus_file_extend(struct inode *, bool zeroout);
 void hfsplus_file_truncate(struct inode *);
 
 /* inode.c */
index 5a126828d85eb011714b067976f0a14981103c7c..8298d0985f810bbc6a4a45c54288c0701d7890e6 100644 (file)
@@ -144,6 +144,7 @@ struct hfsplus_vh {
 #define HFSPLUS_VOL_NODEID_REUSED      (1 << 12)
 #define HFSPLUS_VOL_JOURNALED          (1 << 13)
 #define HFSPLUS_VOL_SOFTLOCK           (1 << 15)
+#define HFSPLUS_VOL_UNUSED_NODE_FIX    (1 << 31)
 
 /* HFS+ BTree node descriptor */
 struct hfs_bnode_desc {
index c03c94611cce98168beef627e500108c5a4fc97a..aab093c27c593af02aa389166e52ca48e72e89db 100644 (file)
@@ -196,7 +196,7 @@ check_attr_tree_state_again:
        }
 
        while (hip->alloc_blocks < hip->clump_blocks) {
-               err = hfsplus_file_extend(attr_file);
+               err = hfsplus_file_extend(attr_file, false);
                if (unlikely(err)) {
                        pr_err("failed to extend attributes file\n");
                        goto end_attr_file_creation;