diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-09 16:00:21 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-02-09 16:00:21 -0800 |
| commit | 4fb7d86fbef0e294f4bb6bc46930c5789d332dc7 (patch) | |
| tree | 9a705efec65dfb1029bb3f0ed1a5bff5de608f69 | |
| parent | d10a88ce1651578c0948fbf26d7aaff298b486b2 (diff) | |
| parent | ebebb04baefdace1e0dc17f7779e5549063ca592 (diff) | |
Merge tag 'hfs-v7.0-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/hfs
Pull hfs/hfsplus updates from Viacheslav Dubeyko:
"This pull request contains several fixes of syzbot reported issues and
HFS+ fixes of xfstests failures.
- fix an issue reported by syzbot triggering BUG_ON() in the case of
corrupted superblock, replacing the BUG_ON()s with proper error
handling (Jori Koolstra)
- fix memory leaks in the mount logic of HFS/HFS+ file systems. When
HFS/HFS+ were converted to the new mount api a bug was introduced
by changing the allocation pattern of sb->s_fs_info (Mehdi Ben Hadj
Khelifa)
- fix hfs_bnode_create() by returning ERR_PTR(-EEXIST) instead of
the node pointer when it's already hashed. This avoids a double
unload_nls() on mount failure (suggested by Shardul Bankar)
- set inode's mode as regular file for system inodes (Tetsuo Handa)
The rest fix failures in generic/020, generic/037, generic/062,
generic/480, and generic/498 xfstests for the case of HFS+ file
system. Currently, only 30 xfstests' test-cases experience failures
for HFS+ file system (initially, it was around 100 failed xfstests)"
* tag 'hfs-v7.0-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/hfs:
hfsplus: avoid double unload_nls() on mount failure
hfsplus: fix warning issue in inode.c
hfsplus: fix generic/062 xfstests failure
hfsplus: fix generic/037 xfstests failure
hfsplus: pretend special inodes as regular files
hfsplus: return error when node already exists in hfs_bnode_create
hfs: Replace BUG_ON with error handling for CNID count checks
hfsplus: fix generic/020 xfstests failure
hfsplus: fix volume corruption issue for generic/498
hfsplus: fix volume corruption issue for generic/480
hfsplus: ensure sb->s_fs_info is always cleaned up
hfs: ensure sb->s_fs_info is always cleaned up
| -rw-r--r-- | fs/hfs/dir.c | 15 | ||||
| -rw-r--r-- | fs/hfs/hfs_fs.h | 1 | ||||
| -rw-r--r-- | fs/hfs/inode.c | 30 | ||||
| -rw-r--r-- | fs/hfs/mdb.c | 66 | ||||
| -rw-r--r-- | fs/hfs/super.c | 13 | ||||
| -rw-r--r-- | fs/hfsplus/attributes.c | 189 | ||||
| -rw-r--r-- | fs/hfsplus/bnode.c | 2 | ||||
| -rw-r--r-- | fs/hfsplus/dir.c | 46 | ||||
| -rw-r--r-- | fs/hfsplus/hfsplus_fs.h | 3 | ||||
| -rw-r--r-- | fs/hfsplus/inode.c | 40 | ||||
| -rw-r--r-- | fs/hfsplus/super.c | 20 | ||||
| -rw-r--r-- | fs/hfsplus/xattr.c | 104 |
12 files changed, 407 insertions, 122 deletions
diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c index 86a6b317b474..0c615c078650 100644 --- a/fs/hfs/dir.c +++ b/fs/hfs/dir.c @@ -196,8 +196,8 @@ static int hfs_create(struct mnt_idmap *idmap, struct inode *dir, int res; inode = hfs_new_inode(dir, &dentry->d_name, mode); - if (!inode) - return -ENOMEM; + if (IS_ERR(inode)) + return PTR_ERR(inode); res = hfs_cat_create(inode->i_ino, dir, &dentry->d_name, inode); if (res) { @@ -226,8 +226,8 @@ static struct dentry *hfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, int res; inode = hfs_new_inode(dir, &dentry->d_name, S_IFDIR | mode); - if (!inode) - return ERR_PTR(-ENOMEM); + if (IS_ERR(inode)) + return ERR_CAST(inode); res = hfs_cat_create(inode->i_ino, dir, &dentry->d_name, inode); if (res) { @@ -254,11 +254,18 @@ static struct dentry *hfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, */ static int hfs_remove(struct inode *dir, struct dentry *dentry) { + struct super_block *sb = dir->i_sb; struct inode *inode = d_inode(dentry); int res; if (S_ISDIR(inode->i_mode) && inode->i_size != 2) return -ENOTEMPTY; + + if (unlikely(!is_hfs_cnid_counts_valid(sb))) { + pr_err("cannot remove file/folder\n"); + return -ERANGE; + } + res = hfs_cat_delete(inode->i_ino, dir, &dentry->d_name); if (res) return res; diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h index e94dbc04a1e4..ac0e83f77a0f 100644 --- a/fs/hfs/hfs_fs.h +++ b/fs/hfs/hfs_fs.h @@ -199,6 +199,7 @@ extern void hfs_delete_inode(struct inode *inode); extern const struct xattr_handler * const hfs_xattr_handlers[]; /* mdb.c */ +extern bool is_hfs_cnid_counts_valid(struct super_block *sb); extern int hfs_mdb_get(struct super_block *sb); extern void hfs_mdb_commit(struct super_block *sb); extern void hfs_mdb_close(struct super_block *sb); diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c index 524db1389737..878535db64d6 100644 --- a/fs/hfs/inode.c +++ b/fs/hfs/inode.c @@ -187,16 +187,23 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t s64 next_id; s64 file_count; s64 folder_count; + int err = -ENOMEM; if (!inode) - return NULL; + goto out_err; + + err = -ERANGE; mutex_init(&HFS_I(inode)->extents_lock); INIT_LIST_HEAD(&HFS_I(inode)->open_dir_list); spin_lock_init(&HFS_I(inode)->open_dir_lock); hfs_cat_build_key(sb, (btree_key *)&HFS_I(inode)->cat_key, dir->i_ino, name); next_id = atomic64_inc_return(&HFS_SB(sb)->next_id); - BUG_ON(next_id > U32_MAX); + if (next_id > U32_MAX) { + atomic64_dec(&HFS_SB(sb)->next_id); + pr_err("cannot create new inode: next CNID exceeds limit\n"); + goto out_discard; + } inode->i_ino = (u32)next_id; inode->i_mode = mode; inode->i_uid = current_fsuid(); @@ -210,7 +217,11 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t if (S_ISDIR(mode)) { inode->i_size = 2; folder_count = atomic64_inc_return(&HFS_SB(sb)->folder_count); - BUG_ON(folder_count > U32_MAX); + if (folder_count> U32_MAX) { + atomic64_dec(&HFS_SB(sb)->folder_count); + pr_err("cannot create new inode: folder count exceeds limit\n"); + goto out_discard; + } if (dir->i_ino == HFS_ROOT_CNID) HFS_SB(sb)->root_dirs++; inode->i_op = &hfs_dir_inode_operations; @@ -220,7 +231,11 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t } else if (S_ISREG(mode)) { HFS_I(inode)->clump_blocks = HFS_SB(sb)->clumpablks; file_count = atomic64_inc_return(&HFS_SB(sb)->file_count); - BUG_ON(file_count > U32_MAX); + if (file_count > U32_MAX) { + atomic64_dec(&HFS_SB(sb)->file_count); + pr_err("cannot create new inode: file count exceeds limit\n"); + goto out_discard; + } if (dir->i_ino == HFS_ROOT_CNID) HFS_SB(sb)->root_files++; inode->i_op = &hfs_file_inode_operations; @@ -244,6 +259,11 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t hfs_mark_mdb_dirty(sb); return inode; + + out_discard: + iput(inode); + out_err: + return ERR_PTR(err); } void hfs_delete_inode(struct inode *inode) @@ -252,7 +272,6 @@ void hfs_delete_inode(struct inode *inode) hfs_dbg("ino %lu\n", inode->i_ino); if (S_ISDIR(inode->i_mode)) { - BUG_ON(atomic64_read(&HFS_SB(sb)->folder_count) > U32_MAX); atomic64_dec(&HFS_SB(sb)->folder_count); if (HFS_I(inode)->cat_key.ParID == cpu_to_be32(HFS_ROOT_CNID)) HFS_SB(sb)->root_dirs--; @@ -261,7 +280,6 @@ void hfs_delete_inode(struct inode *inode) return; } - BUG_ON(atomic64_read(&HFS_SB(sb)->file_count) > U32_MAX); atomic64_dec(&HFS_SB(sb)->file_count); if (HFS_I(inode)->cat_key.ParID == cpu_to_be32(HFS_ROOT_CNID)) HFS_SB(sb)->root_files--; diff --git a/fs/hfs/mdb.c b/fs/hfs/mdb.c index 53f3fae60217..a97cea35ca2e 100644 --- a/fs/hfs/mdb.c +++ b/fs/hfs/mdb.c @@ -64,6 +64,27 @@ static int hfs_get_last_session(struct super_block *sb, return 0; } +bool is_hfs_cnid_counts_valid(struct super_block *sb) +{ + struct hfs_sb_info *sbi = HFS_SB(sb); + bool corrupted = false; + + if (unlikely(atomic64_read(&sbi->next_id) > U32_MAX)) { + pr_warn("next CNID exceeds limit\n"); + corrupted = true; + } + if (unlikely(atomic64_read(&sbi->file_count) > U32_MAX)) { + pr_warn("file count exceeds limit\n"); + corrupted = true; + } + if (unlikely(atomic64_read(&sbi->folder_count) > U32_MAX)) { + pr_warn("folder count exceeds limit\n"); + corrupted = true; + } + + return !corrupted; +} + /* * hfs_mdb_get() * @@ -92,7 +113,7 @@ int hfs_mdb_get(struct super_block *sb) /* See if this is an HFS filesystem */ bh = sb_bread512(sb, part_start + HFS_MDB_BLK, mdb); if (!bh) - goto out; + return -EIO; if (mdb->drSigWord == cpu_to_be16(HFS_SUPER_MAGIC)) break; @@ -102,13 +123,14 @@ int hfs_mdb_get(struct super_block *sb) * (should do this only for cdrom/loop though) */ if (hfs_part_find(sb, &part_start, &part_size)) - goto out; + return -EIO; } HFS_SB(sb)->alloc_blksz = size = be32_to_cpu(mdb->drAlBlkSiz); if (!size || (size & (HFS_SECTOR_SIZE - 1))) { pr_err("bad allocation block size %d\n", size); - goto out_bh; + brelse(bh); + return -EIO; } size = min(HFS_SB(sb)->alloc_blksz, (u32)PAGE_SIZE); @@ -125,14 +147,16 @@ int hfs_mdb_get(struct super_block *sb) brelse(bh); if (!sb_set_blocksize(sb, size)) { pr_err("unable to set blocksize to %u\n", size); - goto out; + return -EIO; } bh = sb_bread512(sb, part_start + HFS_MDB_BLK, mdb); if (!bh) - goto out; - if (mdb->drSigWord != cpu_to_be16(HFS_SUPER_MAGIC)) - goto out_bh; + return -EIO; + if (mdb->drSigWord != cpu_to_be16(HFS_SUPER_MAGIC)) { + brelse(bh); + return -EIO; + } HFS_SB(sb)->mdb_bh = bh; HFS_SB(sb)->mdb = mdb; @@ -156,6 +180,11 @@ int hfs_mdb_get(struct super_block *sb) atomic64_set(&HFS_SB(sb)->file_count, be32_to_cpu(mdb->drFilCnt)); atomic64_set(&HFS_SB(sb)->folder_count, be32_to_cpu(mdb->drDirCnt)); + if (!is_hfs_cnid_counts_valid(sb)) { + pr_warn("filesystem possibly corrupted, running fsck.hfs is recommended. Mounting read-only.\n"); + sb->s_flags |= SB_RDONLY; + } + /* TRY to get the alternate (backup) MDB. */ sect = part_start + part_size - 2; bh = sb_bread512(sb, sect, mdb2); @@ -174,7 +203,7 @@ int hfs_mdb_get(struct super_block *sb) HFS_SB(sb)->bitmap = kzalloc(8192, GFP_KERNEL); if (!HFS_SB(sb)->bitmap) - goto out; + return -EIO; /* read in the bitmap */ block = be16_to_cpu(mdb->drVBMSt) + part_start; @@ -185,7 +214,7 @@ int hfs_mdb_get(struct super_block *sb) bh = sb_bread(sb, off >> sb->s_blocksize_bits); if (!bh) { pr_err("unable to read volume bitmap\n"); - goto out; + return -EIO; } off2 = off & (sb->s_blocksize - 1); len = min((int)sb->s_blocksize - off2, size); @@ -199,17 +228,17 @@ int hfs_mdb_get(struct super_block *sb) HFS_SB(sb)->ext_tree = hfs_btree_open(sb, HFS_EXT_CNID, hfs_ext_keycmp); if (!HFS_SB(sb)->ext_tree) { pr_err("unable to open extent tree\n"); - goto out; + return -EIO; } HFS_SB(sb)->cat_tree = hfs_btree_open(sb, HFS_CAT_CNID, hfs_cat_keycmp); if (!HFS_SB(sb)->cat_tree) { pr_err("unable to open catalog tree\n"); - goto out; + return -EIO; } attrib = mdb->drAtrb; if (!(attrib & cpu_to_be16(HFS_SB_ATTRIB_UNMNT))) { - pr_warn("filesystem was not cleanly unmounted, running fsck.hfs is recommended. mounting read-only.\n"); + pr_warn("filesystem was not cleanly unmounted, running fsck.hfs is recommended. Mounting read-only.\n"); sb->s_flags |= SB_RDONLY; } if ((attrib & cpu_to_be16(HFS_SB_ATTRIB_SLOCK))) { @@ -229,12 +258,6 @@ int hfs_mdb_get(struct super_block *sb) } return 0; - -out_bh: - brelse(bh); -out: - hfs_mdb_put(sb); - return -EIO; } /* @@ -273,15 +296,12 @@ void hfs_mdb_commit(struct super_block *sb) /* These parameters may have been modified, so write them back */ mdb->drLsMod = hfs_mtime(); mdb->drFreeBks = cpu_to_be16(HFS_SB(sb)->free_ablocks); - BUG_ON(atomic64_read(&HFS_SB(sb)->next_id) > U32_MAX); mdb->drNxtCNID = cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->next_id)); mdb->drNmFls = cpu_to_be16(HFS_SB(sb)->root_files); mdb->drNmRtDirs = cpu_to_be16(HFS_SB(sb)->root_dirs); - BUG_ON(atomic64_read(&HFS_SB(sb)->file_count) > U32_MAX); mdb->drFilCnt = cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->file_count)); - BUG_ON(atomic64_read(&HFS_SB(sb)->folder_count) > U32_MAX); mdb->drDirCnt = cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->folder_count)); @@ -359,8 +379,6 @@ void hfs_mdb_close(struct super_block *sb) * Release the resources associated with the in-core MDB. */ void hfs_mdb_put(struct super_block *sb) { - if (!HFS_SB(sb)) - return; /* free the B-trees */ hfs_btree_close(HFS_SB(sb)->ext_tree); hfs_btree_close(HFS_SB(sb)->cat_tree); @@ -373,6 +391,4 @@ void hfs_mdb_put(struct super_block *sb) unload_nls(HFS_SB(sb)->nls_disk); kfree(HFS_SB(sb)->bitmap); - kfree(HFS_SB(sb)); - sb->s_fs_info = NULL; } diff --git a/fs/hfs/super.c b/fs/hfs/super.c index 47f50fa555a4..97546d6b41f4 100644 --- a/fs/hfs/super.c +++ b/fs/hfs/super.c @@ -34,6 +34,7 @@ MODULE_LICENSE("GPL"); static int hfs_sync_fs(struct super_block *sb, int wait) { + is_hfs_cnid_counts_valid(sb); hfs_mdb_commit(sb); return 0; } @@ -65,6 +66,8 @@ static void flush_mdb(struct work_struct *work) sbi->work_queued = 0; spin_unlock(&sbi->work_lock); + is_hfs_cnid_counts_valid(sb); + hfs_mdb_commit(sb); } @@ -431,10 +434,18 @@ static int hfs_init_fs_context(struct fs_context *fc) return 0; } +static void hfs_kill_super(struct super_block *sb) +{ + struct hfs_sb_info *hsb = HFS_SB(sb); + + kill_block_super(sb); + kfree(hsb); +} + static struct file_system_type hfs_fs_type = { .owner = THIS_MODULE, .name = "hfs", - .kill_sb = kill_block_super, + .kill_sb = hfs_kill_super, .fs_flags = FS_REQUIRES_DEV, .init_fs_context = hfs_init_fs_context, }; diff --git a/fs/hfsplus/attributes.c b/fs/hfsplus/attributes.c index ba26980cc503..4b79cd606276 100644 --- a/fs/hfsplus/attributes.c +++ b/fs/hfsplus/attributes.c @@ -117,8 +117,10 @@ static int hfsplus_attr_build_record(hfsplus_attr_entry *entry, int record_type, entry->inline_data.record_type = cpu_to_be32(record_type); if (size <= HFSPLUS_MAX_INLINE_DATA_SIZE) len = size; - else + else { + hfs_dbg("value size %zu is too big\n", size); return HFSPLUS_INVALID_ATTR_RECORD; + } entry->inline_data.length = cpu_to_be16(len); memcpy(entry->inline_data.raw_bytes, value, len); /* @@ -191,14 +193,66 @@ attr_not_found: return 0; } +static +int hfsplus_create_attr_nolock(struct inode *inode, const char *name, + const void *value, size_t size, + struct hfs_find_data *fd, + hfsplus_attr_entry *entry_ptr) +{ + struct super_block *sb = inode->i_sb; + int entry_size; + int err; + + hfs_dbg("name %s, ino %ld\n", + name ? name : NULL, inode->i_ino); + + if (name) { + err = hfsplus_attr_build_key(sb, fd->search_key, + inode->i_ino, name); + if (err) + return err; + } else + return -EINVAL; + + /* Mac OS X supports only inline data attributes. */ + entry_size = hfsplus_attr_build_record(entry_ptr, + HFSPLUS_ATTR_INLINE_DATA, + inode->i_ino, + value, size); + if (entry_size == HFSPLUS_INVALID_ATTR_RECORD) { + if (size > HFSPLUS_MAX_INLINE_DATA_SIZE) + err = -E2BIG; + else + err = -EINVAL; + hfs_dbg("unable to store value: err %d\n", err); + return err; + } + + err = hfs_brec_find(fd, hfs_find_rec_by_key); + if (err != -ENOENT) { + if (!err) + err = -EEXIST; + return err; + } + + err = hfs_brec_insert(fd, entry_ptr, entry_size); + if (err) { + hfs_dbg("unable to store value: err %d\n", err); + return err; + } + + hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY); + + return 0; +} + int hfsplus_create_attr(struct inode *inode, - const char *name, - const void *value, size_t size) + const char *name, + const void *value, size_t size) { struct super_block *sb = inode->i_sb; struct hfs_find_data fd; hfsplus_attr_entry *entry_ptr; - int entry_size; int err; hfs_dbg("name %s, ino %ld\n", @@ -222,39 +276,11 @@ int hfsplus_create_attr(struct inode *inode, if (err) goto failed_create_attr; - if (name) { - err = hfsplus_attr_build_key(sb, fd.search_key, - inode->i_ino, name); - if (err) - goto failed_create_attr; - } else { - err = -EINVAL; - goto failed_create_attr; - } - - /* Mac OS X supports only inline data attributes. */ - entry_size = hfsplus_attr_build_record(entry_ptr, - HFSPLUS_ATTR_INLINE_DATA, - inode->i_ino, - value, size); - if (entry_size == HFSPLUS_INVALID_ATTR_RECORD) { - err = -EINVAL; - goto failed_create_attr; - } - - err = hfs_brec_find(&fd, hfs_find_rec_by_key); - if (err != -ENOENT) { - if (!err) - err = -EEXIST; - goto failed_create_attr; - } - - err = hfs_brec_insert(&fd, entry_ptr, entry_size); + err = hfsplus_create_attr_nolock(inode, name, value, size, + &fd, entry_ptr); if (err) goto failed_create_attr; - hfsplus_mark_inode_dirty(inode, HFSPLUS_I_ATTR_DIRTY); - failed_create_attr: hfs_find_exit(&fd); @@ -304,6 +330,37 @@ static int __hfsplus_delete_attr(struct inode *inode, u32 cnid, return err; } +static +int hfsplus_delete_attr_nolock(struct inode *inode, const char *name, + struct hfs_find_data *fd) +{ + struct super_block *sb = inode->i_sb; + int err; + + hfs_dbg("name %s, ino %ld\n", + name ? name : NULL, inode->i_ino); + + if (name) { + err = hfsplus_attr_build_key(sb, fd->search_key, + inode->i_ino, name); + if (err) + return err; + } else { + pr_err("invalid extended attribute name\n"); + return -EINVAL; + } + + err = hfs_brec_find(fd, hfs_find_rec_by_key); + if (err) + return err; + + err = __hfsplus_delete_attr(inode, inode->i_ino, fd); + if (err) + return err; + + return 0; +} + int hfsplus_delete_attr(struct inode *inode, const char *name) { int err = 0; @@ -327,22 +384,7 @@ int hfsplus_delete_attr(struct inode *inode, const char *name) if (err) goto out; - if (name) { - err = hfsplus_attr_build_key(sb, fd.search_key, - inode->i_ino, name); - if (err) - goto out; - } else { - pr_err("invalid extended attribute name\n"); - err = -EINVAL; - goto out; - } - - err = hfs_brec_find(&fd, hfs_find_rec_by_key); - if (err) - goto out; - - err = __hfsplus_delete_attr(inode, inode->i_ino, &fd); + err = hfsplus_delete_attr_nolock(inode, name, &fd); if (err) goto out; @@ -384,3 +426,50 @@ end_delete_all: hfs_find_exit(&fd); return err; } + +int hfsplus_replace_attr(struct inode *inode, + const char *name, + const void *value, size_t size) +{ + struct super_block *sb = inode->i_sb; + struct hfs_find_data fd; + hfsplus_attr_entry *entry_ptr; + int err = 0; + + hfs_dbg("name %s, ino %ld\n", + name ? name : NULL, inode->i_ino); + + if (!HFSPLUS_SB(sb)->attr_tree) { + pr_err("attributes file doesn't exist\n"); + return -EINVAL; + } + + entry_ptr = hfsplus_alloc_attr_entry(); + if (!entry_ptr) + return -ENOMEM; + + err = hfs_find_init(HFSPLUS_SB(sb)->attr_tree, &fd); + if (err) + goto failed_init_replace_attr; + + /* Fail early and avoid ENOSPC during the btree operation */ + err = hfs_bmap_reserve(fd.tree, fd.tree->depth + 1); + if (err) + goto failed_replace_attr; + + err = hfsplus_delete_attr_nolock(inode, name, &fd); + if (err) + goto failed_replace_attr; + + err = hfsplus_create_attr_nolock(inode, name, value, size, + &fd, entry_ptr); + if (err) + goto failed_replace_attr; + +failed_replace_attr: + hfs_find_exit(&fd); + +failed_init_replace_attr: + hfsplus_destroy_attr_entry(entry_ptr); + return err; +} diff --git a/fs/hfsplus/bnode.c b/fs/hfsplus/bnode.c index 191661af9677..250a226336ea 100644 --- a/fs/hfsplus/bnode.c +++ b/fs/hfsplus/bnode.c @@ -629,7 +629,7 @@ struct hfs_bnode *hfs_bnode_create(struct hfs_btree *tree, u32 num) if (node) { pr_crit("new node %u already hashed?\n", num); WARN_ON(1); - return node; + return ERR_PTR(-EEXIST); } node = __hfs_bnode_create(tree, num); if (!node) diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c index cadf0b5f9342..ca5f74a140ec 100644 --- a/fs/hfsplus/dir.c +++ b/fs/hfsplus/dir.c @@ -313,6 +313,9 @@ static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir, if (!S_ISREG(inode->i_mode)) return -EPERM; + hfs_dbg("src_dir->i_ino %lu, dst_dir->i_ino %lu, inode->i_ino %lu\n", + src_dir->i_ino, dst_dir->i_ino, inode->i_ino); + mutex_lock(&sbi->vh_mutex); if (inode->i_ino == (u32)(unsigned long)src_dentry->d_fsdata) { for (;;) { @@ -332,7 +335,7 @@ static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir, cnid = sbi->next_cnid++; src_dentry->d_fsdata = (void *)(unsigned long)cnid; res = hfsplus_create_cat(cnid, src_dir, - &src_dentry->d_name, inode); + &src_dentry->d_name, inode); if (res) /* panic? */ goto out; @@ -350,6 +353,21 @@ static int hfsplus_link(struct dentry *src_dentry, struct inode *dst_dir, mark_inode_dirty(inode); sbi->file_count++; hfsplus_mark_mdb_dirty(dst_dir->i_sb); + + res = hfsplus_cat_write_inode(src_dir); + if (res) + goto out; + + res = hfsplus_cat_write_inode(dst_dir); + if (res) + goto out; + + res = hfsplus_cat_write_inode(sbi->hidden_dir); + if (res) + goto out; + + res = hfsplus_cat_write_inode(inode); + out: mutex_unlock(&sbi->vh_mutex); return res; @@ -367,6 +385,9 @@ static int hfsplus_unlink(struct inode *dir, struct dentry *dentry) if (HFSPLUS_IS_RSRC(inode)) return -EPERM; + hfs_dbg("dir->i_ino %lu, inode->i_ino %lu\n", + dir->i_ino, inode->i_ino); + mutex_lock(&sbi->vh_mutex); cnid = (u32)(unsigned long)dentry->d_fsdata; if (inode->i_ino == cnid && @@ -408,6 +429,15 @@ static int hfsplus_unlink(struct inode *dir, struct dentry *dentry) inode_set_ctime_current(inode); mark_inode_dirty(inode); out: + if (!res) { + res = hfsplus_cat_write_inode(dir); + if (!res) { + res = hfsplus_cat_write_inode(sbi->hidden_dir); + if (!res) + res = hfsplus_cat_write_inode(inode); + } + } + mutex_unlock(&sbi->vh_mutex); return res; } @@ -429,6 +459,8 @@ static int hfsplus_rmdir(struct inode *dir, struct dentry *dentry) inode_set_ctime_current(inode); hfsplus_delete_inode(inode); mark_inode_dirty(inode); + + res = hfsplus_cat_write_inode(dir); out: mutex_unlock(&sbi->vh_mutex); return res; @@ -465,6 +497,12 @@ static int hfsplus_symlink(struct mnt_idmap *idmap, struct inode *dir, hfsplus_instantiate(dentry, inode, inode->i_ino); mark_inode_dirty(inode); + + res = hfsplus_cat_write_inode(dir); + if (res) + goto out; + + res = hfsplus_cat_write_inode(inode); goto out; out_err: @@ -506,6 +544,12 @@ static int hfsplus_mknod(struct mnt_idmap *idmap, struct inode *dir, hfsplus_instantiate(dentry, inode, inode->i_ino); mark_inode_dirty(inode); + + res = hfsplus_cat_write_inode(dir); + if (res) + goto out; + + res = hfsplus_cat_write_inode(inode); goto out; failed_mknod: diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h index 45fe3a12ecba..5f891b73a646 100644 --- a/fs/hfsplus/hfsplus_fs.h +++ b/fs/hfsplus/hfsplus_fs.h @@ -344,6 +344,9 @@ int hfsplus_create_attr(struct inode *inode, const char *name, const void *value, size_t size); int hfsplus_delete_attr(struct inode *inode, const char *name); int hfsplus_delete_all_attrs(struct inode *dir, u32 cnid); +int hfsplus_replace_attr(struct inode *inode, + const char *name, + const void *value, size_t size); /* bitmap.c */ int hfsplus_block_allocate(struct super_block *sb, u32 size, u32 offset, diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index 7ae6745ca7ae..922ff41df042 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -328,6 +328,9 @@ int hfsplus_file_fsync(struct file *file, loff_t start, loff_t end, struct hfsplus_vh *vhdr = sbi->s_vhdr; int error = 0, error2; + hfs_dbg("inode->i_ino %lu, start %llu, end %llu\n", + inode->i_ino, start, end); + error = file_write_and_wait_range(file, start, end); if (error) return error; @@ -393,6 +396,19 @@ static const struct inode_operations hfsplus_file_inode_operations = { .fileattr_set = hfsplus_fileattr_set, }; +static const struct inode_operations hfsplus_symlink_inode_operations = { + .get_link = page_get_link, + .setattr = hfsplus_setattr, + .getattr = hfsplus_getattr, + .listxattr = hfsplus_listxattr, +}; + +static const struct inode_operations hfsplus_special_inode_operations = { + .setattr = hfsplus_setattr, + .getattr = hfsplus_getattr, + .listxattr = hfsplus_listxattr, +}; + static const struct file_operations hfsplus_file_operations = { .llseek = generic_file_llseek, .read_iter = generic_file_read_iter, @@ -452,12 +468,17 @@ struct inode *hfsplus_new_inode(struct super_block *sb, struct inode *dir, hip->clump_blocks = sbi->data_clump_blocks; } else if (S_ISLNK(inode->i_mode)) { sbi->file_count++; - inode->i_op = &page_symlink_inode_operations; + inode->i_op = &hfsplus_symlink_inode_operations; inode_nohighmem(inode); inode->i_mapping->a_ops = &hfsplus_aops; hip->clump_blocks = 1; + } else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) || + S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) { + sbi->file_count++; + inode->i_op = &hfsplus_special_inode_operations; } else sbi->file_count++; + insert_inode_hash(inode); mark_inode_dirty(inode); hfsplus_mark_mdb_dirty(sb); @@ -588,10 +609,11 @@ int hfsplus_cat_read_inode(struct inode *inode, struct hfs_find_data *fd) inode->i_fop = &hfsplus_file_operations; inode->i_mapping->a_ops = &hfsplus_aops; } else if (S_ISLNK(inode->i_mode)) { - inode->i_op = &page_symlink_inode_operations; + inode->i_op = &hfsplus_symlink_inode_operations; inode_nohighmem(inode); inode->i_mapping->a_ops = &hfsplus_aops; } else { + inode->i_op = &hfsplus_special_inode_operations; init_special_inode(inode, inode->i_mode, be32_to_cpu(file->permissions.dev)); } @@ -612,17 +634,20 @@ out: int hfsplus_cat_write_inode(struct inode *inode) { struct inode *main_inode = inode; + struct hfs_btree *tree = HFSPLUS_SB(inode->i_sb)->cat_tree; struct hfs_find_data fd; hfsplus_cat_entry entry; int res = 0; + hfs_dbg("inode->i_ino %lu\n", inode->i_ino); + if (HFSPLUS_IS_RSRC(inode)) main_inode = HFSPLUS_I(inode)->rsrc_inode; if (!main_inode->i_nlink) return 0; - if (hfs_find_init(HFSPLUS_SB(main_inode->i_sb)->cat_tree, &fd)) + if (hfs_find_init(tree, &fd)) /* panic? */ return -EIO; @@ -687,6 +712,15 @@ int hfsplus_cat_write_inode(struct inode *inode) set_bit(HFSPLUS_I_CAT_DIRTY, &HFSPLUS_I(inode)->flags); out: hfs_find_exit(&fd); + + if (!res) { + res = hfs_btree_write(tree); + if (res) { + pr_err("b-tree write err: %d, ino %lu\n", + res, inode->i_ino); + } + } + return res; } diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c index aaffa9e060a0..592d8fbb748c 100644 --- a/fs/hfsplus/super.c +++ b/fs/hfsplus/super.c @@ -53,6 +53,12 @@ static int hfsplus_system_read_inode(struct inode *inode) return -EIO; } + /* + * Assign a dummy file type, for may_open() requires that + * an inode has a valid file type. + */ + inode->i_mode = S_IFREG; + return 0; } @@ -344,8 +350,6 @@ static void hfsplus_put_super(struct super_block *sb) hfs_btree_close(sbi->ext_tree); kfree(sbi->s_vhdr_buf); kfree(sbi->s_backup_vhdr_buf); - call_rcu(&sbi->rcu, delayed_free); - hfs_dbg("finished\n"); } @@ -648,9 +652,7 @@ out_free_vhdr: kfree(sbi->s_vhdr_buf); kfree(sbi->s_backup_vhdr_buf); out_unload_nls: - unload_nls(sbi->nls); unload_nls(nls); - kfree(sbi); return err; } @@ -709,10 +711,18 @@ static int hfsplus_init_fs_context(struct fs_context *fc) return 0; } +static void hfsplus_kill_super(struct super_block *sb) +{ + struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb); + + kill_block_super(sb); + call_rcu(&sbi->rcu, delayed_free); +} + static struct file_system_type hfsplus_fs_type = { .owner = THIS_MODULE, .name = "hfsplus", - .kill_sb = kill_block_super, + .kill_sb = hfsplus_kill_super, .fs_flags = FS_REQUIRES_DEV, .init_fs_context = hfsplus_init_fs_context, }; diff --git a/fs/hfsplus/xattr.c b/fs/hfsplus/xattr.c index da95a9de9a65..9904944cbd54 100644 --- a/fs/hfsplus/xattr.c +++ b/fs/hfsplus/xattr.c @@ -258,6 +258,15 @@ end_attr_file_creation: return err; } +static inline +bool is_xattr_operation_supported(struct inode *inode) +{ + if (HFSPLUS_IS_RSRC(inode)) + return false; + + return true; +} + int __hfsplus_setxattr(struct inode *inode, const char *name, const void *value, size_t size, int flags) { @@ -268,9 +277,11 @@ int __hfsplus_setxattr(struct inode *inode, const char *name, u16 folder_finderinfo_len = sizeof(DInfo) + sizeof(DXInfo); u16 file_finderinfo_len = sizeof(FInfo) + sizeof(FXInfo); - if ((!S_ISREG(inode->i_mode) && - !S_ISDIR(inode->i_mode)) || - HFSPLUS_IS_RSRC(inode)) + hfs_dbg("ino %lu, name %s, value %p, size %zu\n", + inode->i_ino, name ? name : NULL, + value, size); + + if (!is_xattr_operation_supported(inode)) return -EOPNOTSUPP; if (value == NULL) @@ -341,12 +352,11 @@ int __hfsplus_setxattr(struct inode *inode, const char *name, err = -EOPNOTSUPP; goto end_setxattr; } - err = hfsplus_delete_attr(inode, name); - if (err) - goto end_setxattr; - err = hfsplus_create_attr(inode, name, value, size); - if (err) + err = hfsplus_replace_attr(inode, name, value, size); + if (err) { + hfs_dbg("unable to replace xattr: err %d\n", err); goto end_setxattr; + } } else { if (flags & XATTR_REPLACE) { pr_err("cannot replace xattr\n"); @@ -354,8 +364,10 @@ int __hfsplus_setxattr(struct inode *inode, const char *name, goto end_setxattr; } err = hfsplus_create_attr(inode, name, value, size); - if (err) + if (err) { + hfs_dbg("unable to store value: err %d\n", err); goto end_setxattr; + } } cat_entry_type = hfs_bnode_read_u16(cat_fd.bnode, cat_fd.entryoffset); @@ -389,12 +401,13 @@ int __hfsplus_setxattr(struct inode *inode, const char *name, end_setxattr: hfs_find_exit(&cat_fd); + hfs_dbg("finished: res %d\n", err); return err; } -static int name_len(const char *xattr_name, int xattr_name_len) +static size_t name_len(const char *xattr_name, size_t xattr_name_len) { - int len = xattr_name_len + 1; + size_t len = xattr_name_len + 1; if (!is_known_namespace(xattr_name)) len += XATTR_MAC_OSX_PREFIX_LEN; @@ -402,15 +415,22 @@ static int name_len(const char *xattr_name, int xattr_name_len) return len; } -static ssize_t copy_name(char *buffer, const char *xattr_name, int name_len) +static ssize_t copy_name(char *buffer, const char *xattr_name, size_t name_len) { ssize_t len; - if (!is_known_namespace(xattr_name)) + memset(buffer, 0, name_len); + + if (!is_known_namespace(xattr_name)) { len = scnprintf(buffer, name_len + XATTR_MAC_OSX_PREFIX_LEN, "%s%s", XATTR_MAC_OSX_PREFIX, xattr_name); - else + } else { len = strscpy(buffer, xattr_name, name_len + 1); + if (len < 0) { + pr_err("fail to copy name: err %zd\n", len); + len = 0; + } + } /* include NUL-byte in length for non-empty name */ if (len >= 0) @@ -423,16 +443,26 @@ int hfsplus_setxattr(struct inode *inode, const char *name, const char *prefix, size_t prefixlen) { char *xattr_name; + size_t xattr_name_len = + NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1; int res; - xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1, - GFP_KERNEL); + hfs_dbg("ino %lu, name %s, prefix %s, prefixlen %zu, " + "value %p, size %zu\n", + inode->i_ino, name ? name : NULL, + prefix ? prefix : NULL, prefixlen, + value, size); + + xattr_name = kmalloc(xattr_name_len, GFP_KERNEL); if (!xattr_name) return -ENOMEM; strcpy(xattr_name, prefix); strcpy(xattr_name + prefixlen, name); res = __hfsplus_setxattr(inode, xattr_name, value, size, flags); kfree(xattr_name); + + hfs_dbg("finished: res %d\n", res); + return res; } @@ -496,9 +526,7 @@ ssize_t __hfsplus_getxattr(struct inode *inode, const char *name, u16 record_length = 0; ssize_t res; - if ((!S_ISREG(inode->i_mode) && - !S_ISDIR(inode->i_mode)) || - HFSPLUS_IS_RSRC(inode)) + if (!is_xattr_operation_supported(inode)) return -EOPNOTSUPP; if (!strcmp_xattr_finder_info(name)) @@ -579,6 +607,10 @@ ssize_t hfsplus_getxattr(struct inode *inode, const char *name, int res; char *xattr_name; + hfs_dbg("ino %lu, name %s, prefix %s\n", + inode->i_ino, name ? name : NULL, + prefix ? prefix : NULL); + xattr_name = kmalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + 1, GFP_KERNEL); if (!xattr_name) @@ -589,6 +621,9 @@ ssize_t hfsplus_getxattr(struct inode *inode, const char *name, res = __hfsplus_getxattr(inode, xattr_name, value, size); kfree(xattr_name); + + hfs_dbg("finished: res %d\n", res); + return res; } @@ -679,11 +714,12 @@ ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size) struct hfs_find_data fd; struct hfsplus_attr_key attr_key; char *strbuf; + size_t strbuf_size; int xattr_name_len; - if ((!S_ISREG(inode->i_mode) && - !S_ISDIR(inode->i_mode)) || - HFSPLUS_IS_RSRC(inode)) + hfs_dbg("ino %lu\n", inode->i_ino); + + if (!is_xattr_operation_supported(inode)) return -EOPNOTSUPP; res = hfsplus_listxattr_finder_info(dentry, buffer, size); @@ -698,8 +734,9 @@ ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size) return err; } - strbuf = kzalloc(NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + - XATTR_MAC_OSX_PREFIX_LEN + 1, GFP_KERNEL); + strbuf_size = NLS_MAX_CHARSET_SIZE * HFSPLUS_ATTR_MAX_STRLEN + + XATTR_MAC_OSX_PREFIX_LEN + 1; + strbuf = kzalloc(strbuf_size, GFP_KERNEL); if (!strbuf) { res = -ENOMEM; goto out; @@ -708,8 +745,7 @@ ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size) err = hfsplus_find_attr(inode->i_sb, inode->i_ino, NULL, &fd); if (err) { if (err == -ENOENT) { - if (res == 0) - res = -ENODATA; + res = 0; goto end_listxattr; } else { res = err; @@ -746,6 +782,9 @@ ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size) res += name_len(strbuf, xattr_name_len); } else if (can_list(strbuf)) { if (size < (res + name_len(strbuf, xattr_name_len))) { + pr_err("size %zu, res %zd, name_len %zu\n", + size, res, + name_len(strbuf, xattr_name_len)); res = -ERANGE; goto end_listxattr; } else @@ -753,6 +792,10 @@ ssize_t hfsplus_listxattr(struct dentry *dentry, char *buffer, size_t size) strbuf, xattr_name_len); } + memset(fd.key->attr.key_name.unicode, 0, + sizeof(fd.key->attr.key_name.unicode)); + memset(strbuf, 0, strbuf_size); + if (hfs_brec_goto(&fd, 1)) goto end_listxattr; } @@ -761,6 +804,9 @@ end_listxattr: kfree(strbuf); out: hfs_find_exit(&fd); + + hfs_dbg("finished: res %zd\n", res); + return res; } @@ -773,6 +819,9 @@ static int hfsplus_removexattr(struct inode *inode, const char *name) int is_xattr_acl_deleted; int is_all_xattrs_deleted; + hfs_dbg("ino %lu, name %s\n", + inode->i_ino, name ? name : NULL); + if (!HFSPLUS_SB(inode->i_sb)->attr_tree) return -EOPNOTSUPP; @@ -833,6 +882,9 @@ static int hfsplus_removexattr(struct inode *inode, const char *name) end_removexattr: hfs_find_exit(&cat_fd); + + hfs_dbg("finished: err %d\n", err); + return err; } |
