Skip to content

Commit a06ec28

Browse files
committed
hfs: add logic of correcting a next unused CNID
The generic/736 xfstest fails for HFS case: BEGIN TEST default (1 test): hfs Mon May 5 03:18:32 UTC 2025 DEVICE: /dev/vdb HFS_MKFS_OPTIONS: MOUNT_OPTIONS: MOUNT_OPTIONS FSTYP -- hfs PLATFORM -- Linux/x86_64 kvm-xfstests 6.15.0-rc4-xfstests-g00b827f0cffa #1 SMP PREEMPT_DYNAMIC Fri May 25 MKFS_OPTIONS -- /dev/vdc MOUNT_OPTIONS -- /dev/vdc /vdc generic/736 [03:18:33][ 3.510255] run fstests generic/736 at 2025-05-05 03:18:33 _check_generic_filesystem: filesystem on /dev/vdb is inconsistent (see /results/hfs/results-default/generic/736.full for details) Ran: generic/736 Failures: generic/736 Failed 1 of 1 tests The HFS volume becomes corrupted after the test run: sudo fsck.hfs -d /dev/loop50 ** /dev/loop50 Using cacheBlockSize=32K cacheTotalBlock=1024 cacheSize=32768K. Executing fsck_hfs (version 540.1-Linux). ** Checking HFS volume. The volume name is untitled ** Checking extents overflow file. ** Checking catalog file. ** Checking catalog hierarchy. ** Checking volume bitmap. ** Checking volume information. invalid MDB drNxtCNID Master Directory Block needs minor repair (1, 0) Verify Status: VIStat = 0x8000, ABTStat = 0x0000 EBTStat = 0x0000 CBTStat = 0x0000 CatStat = 0x00000000 ** Repairing volume. ** Rechecking volume. ** Checking HFS volume. The volume name is untitled ** Checking extents overflow file. ** Checking catalog file. ** Checking catalog hierarchy. ** Checking volume bitmap. ** Checking volume information. ** The volume untitled was repaired successfully. The main reason of the issue is the absence of logic that corrects mdb->drNxtCNID/HFS_SB(sb)->next_id (next unused CNID) after deleting a record in Catalog File. This patch introduces a hfs_correct_next_unused_CNID() method that implements the necessary logic. In the case of Catalog File's record delete operation, the function logic checks that (deleted_CNID + 1) == next_unused_CNID and it finds/sets the new value of next_unused_CNID. sudo ./check generic/736 FSTYP -- hfs PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.15.0+ #6 SMP PREEMPT_DYNAMIC Tue Jun 10 15:02:48 PDT 2025 MKFS_OPTIONS -- /dev/loop51 MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch generic/736 33s Ran: generic/736 Passed all 1 tests sudo fsck.hfs -d /dev/loop50 ** /dev/loop50 Using cacheBlockSize=32K cacheTotalBlock=1024 cacheSize=32768K. Executing fsck_hfs (version 540.1-Linux). ** Checking HFS volume. The volume name is untitled ** Checking extents overflow file. ** Checking catalog file. ** Checking catalog hierarchy. ** Checking volume bitmap. ** Checking volume information. ** The volume untitled appears to be OK Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com> Link: https://lore.kernel.org/r/20250610231609.551930-1-slava@dubeyko.com Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
1 parent 9b3d15a commit a06ec28

File tree

5 files changed

+158
-14
lines changed

5 files changed

+158
-14
lines changed

fs/hfs/catalog.c

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,124 @@ int hfs_cat_find_brec(struct super_block *sb, u32 cnid,
211211
return hfs_brec_find(fd);
212212
}
213213

214+
static inline
215+
void hfs_set_next_unused_CNID(struct super_block *sb,
216+
u32 deleted_cnid, u32 found_cnid)
217+
{
218+
if (found_cnid < HFS_FIRSTUSER_CNID) {
219+
atomic64_cmpxchg(&HFS_SB(sb)->next_id,
220+
deleted_cnid + 1, HFS_FIRSTUSER_CNID);
221+
} else {
222+
atomic64_cmpxchg(&HFS_SB(sb)->next_id,
223+
deleted_cnid + 1, found_cnid + 1);
224+
}
225+
}
226+
227+
/*
228+
* hfs_correct_next_unused_CNID()
229+
*
230+
* Correct the next unused CNID of Catalog Tree.
231+
*/
232+
static
233+
int hfs_correct_next_unused_CNID(struct super_block *sb, u32 cnid)
234+
{
235+
struct hfs_btree *cat_tree;
236+
struct hfs_bnode *node;
237+
s64 leaf_head;
238+
s64 leaf_tail;
239+
s64 node_id;
240+
241+
hfs_dbg(CAT_MOD, "correct next unused CNID: cnid %u, next_id %lld\n",
242+
cnid, atomic64_read(&HFS_SB(sb)->next_id));
243+
244+
if ((cnid + 1) < atomic64_read(&HFS_SB(sb)->next_id)) {
245+
/* next ID should be unchanged */
246+
return 0;
247+
}
248+
249+
cat_tree = HFS_SB(sb)->cat_tree;
250+
leaf_head = cat_tree->leaf_head;
251+
leaf_tail = cat_tree->leaf_tail;
252+
253+
if (leaf_head > leaf_tail) {
254+
pr_err("node is corrupted: leaf_head %lld, leaf_tail %lld\n",
255+
leaf_head, leaf_tail);
256+
return -ERANGE;
257+
}
258+
259+
node = hfs_bnode_find(cat_tree, leaf_tail);
260+
if (IS_ERR(node)) {
261+
pr_err("fail to find leaf node: node ID %lld\n",
262+
leaf_tail);
263+
return -ENOENT;
264+
}
265+
266+
node_id = leaf_tail;
267+
268+
do {
269+
int i;
270+
271+
if (node_id != leaf_tail) {
272+
node = hfs_bnode_find(cat_tree, node_id);
273+
if (IS_ERR(node))
274+
return -ENOENT;
275+
}
276+
277+
hfs_dbg(CAT_MOD, "node_id %lld, leaf_tail %lld, leaf_head %lld\n",
278+
node_id, leaf_tail, leaf_head);
279+
280+
hfs_bnode_dump(node);
281+
282+
for (i = node->num_recs - 1; i >= 0; i--) {
283+
hfs_cat_rec rec;
284+
u16 off, len, keylen;
285+
int entryoffset;
286+
int entrylength;
287+
u32 found_cnid;
288+
289+
len = hfs_brec_lenoff(node, i, &off);
290+
keylen = hfs_brec_keylen(node, i);
291+
if (keylen == 0) {
292+
pr_err("fail to get the keylen: "
293+
"node_id %lld, record index %d\n",
294+
node_id, i);
295+
return -EINVAL;
296+
}
297+
298+
entryoffset = off + keylen;
299+
entrylength = len - keylen;
300+
301+
if (entrylength > sizeof(rec)) {
302+
pr_err("unexpected record length: "
303+
"entrylength %d\n",
304+
entrylength);
305+
return -EINVAL;
306+
}
307+
308+
hfs_bnode_read(node, &rec, entryoffset, entrylength);
309+
310+
if (rec.type == HFS_CDR_DIR) {
311+
found_cnid = be32_to_cpu(rec.dir.DirID);
312+
hfs_dbg(CAT_MOD, "found_cnid %u\n", found_cnid);
313+
hfs_set_next_unused_CNID(sb, cnid, found_cnid);
314+
hfs_bnode_put(node);
315+
return 0;
316+
} else if (rec.type == HFS_CDR_FIL) {
317+
found_cnid = be32_to_cpu(rec.file.FlNum);
318+
hfs_dbg(CAT_MOD, "found_cnid %u\n", found_cnid);
319+
hfs_set_next_unused_CNID(sb, cnid, found_cnid);
320+
hfs_bnode_put(node);
321+
return 0;
322+
}
323+
}
324+
325+
hfs_bnode_put(node);
326+
327+
node_id = node->prev;
328+
} while (node_id >= leaf_head);
329+
330+
return -ENOENT;
331+
}
214332

215333
/*
216334
* hfs_cat_delete()
@@ -271,6 +389,11 @@ int hfs_cat_delete(u32 cnid, struct inode *dir, const struct qstr *str)
271389
dir->i_size--;
272390
inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
273391
mark_inode_dirty(dir);
392+
393+
res = hfs_correct_next_unused_CNID(sb, cnid);
394+
if (res)
395+
goto out;
396+
274397
res = 0;
275398
out:
276399
hfs_find_exit(&fd);

fs/hfs/hfs_fs.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,13 @@ struct hfs_sb_info {
112112
the extents b-tree */
113113
struct hfs_btree *cat_tree; /* Information about
114114
the catalog b-tree */
115-
u32 file_count; /* The number of
115+
atomic64_t file_count; /* The number of
116116
regular files in
117117
the filesystem */
118-
u32 folder_count; /* The number of
118+
atomic64_t folder_count; /* The number of
119119
directories in the
120120
filesystem */
121-
u32 next_id; /* The next available
121+
atomic64_t next_id; /* The next available
122122
file id number */
123123
u32 clumpablks; /* The number of allocation
124124
blocks to try to add when

fs/hfs/inode.c

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,20 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t
183183
{
184184
struct super_block *sb = dir->i_sb;
185185
struct inode *inode = new_inode(sb);
186+
s64 next_id;
187+
s64 file_count;
188+
s64 folder_count;
189+
186190
if (!inode)
187191
return NULL;
188192

189193
mutex_init(&HFS_I(inode)->extents_lock);
190194
INIT_LIST_HEAD(&HFS_I(inode)->open_dir_list);
191195
spin_lock_init(&HFS_I(inode)->open_dir_lock);
192196
hfs_cat_build_key(sb, (btree_key *)&HFS_I(inode)->cat_key, dir->i_ino, name);
193-
inode->i_ino = HFS_SB(sb)->next_id++;
197+
next_id = atomic64_inc_return(&HFS_SB(sb)->next_id);
198+
BUG_ON(next_id > U32_MAX);
199+
inode->i_ino = (u32)next_id;
194200
inode->i_mode = mode;
195201
inode->i_uid = current_fsuid();
196202
inode->i_gid = current_fsgid();
@@ -202,7 +208,8 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t
202208
HFS_I(inode)->tz_secondswest = sys_tz.tz_minuteswest * 60;
203209
if (S_ISDIR(mode)) {
204210
inode->i_size = 2;
205-
HFS_SB(sb)->folder_count++;
211+
folder_count = atomic64_inc_return(&HFS_SB(sb)->folder_count);
212+
BUG_ON(folder_count > U32_MAX);
206213
if (dir->i_ino == HFS_ROOT_CNID)
207214
HFS_SB(sb)->root_dirs++;
208215
inode->i_op = &hfs_dir_inode_operations;
@@ -211,7 +218,8 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t
211218
inode->i_mode &= ~HFS_SB(inode->i_sb)->s_dir_umask;
212219
} else if (S_ISREG(mode)) {
213220
HFS_I(inode)->clump_blocks = HFS_SB(sb)->clumpablks;
214-
HFS_SB(sb)->file_count++;
221+
file_count = atomic64_inc_return(&HFS_SB(sb)->file_count);
222+
BUG_ON(file_count > U32_MAX);
215223
if (dir->i_ino == HFS_ROOT_CNID)
216224
HFS_SB(sb)->root_files++;
217225
inode->i_op = &hfs_file_inode_operations;
@@ -243,14 +251,17 @@ void hfs_delete_inode(struct inode *inode)
243251

244252
hfs_dbg(INODE, "delete_inode: %lu\n", inode->i_ino);
245253
if (S_ISDIR(inode->i_mode)) {
246-
HFS_SB(sb)->folder_count--;
254+
BUG_ON(atomic64_read(&HFS_SB(sb)->folder_count) > U32_MAX);
255+
atomic64_dec(&HFS_SB(sb)->folder_count);
247256
if (HFS_I(inode)->cat_key.ParID == cpu_to_be32(HFS_ROOT_CNID))
248257
HFS_SB(sb)->root_dirs--;
249258
set_bit(HFS_FLG_MDB_DIRTY, &HFS_SB(sb)->flags);
250259
hfs_mark_mdb_dirty(sb);
251260
return;
252261
}
253-
HFS_SB(sb)->file_count--;
262+
263+
BUG_ON(atomic64_read(&HFS_SB(sb)->file_count) > U32_MAX);
264+
atomic64_dec(&HFS_SB(sb)->file_count);
254265
if (HFS_I(inode)->cat_key.ParID == cpu_to_be32(HFS_ROOT_CNID))
255266
HFS_SB(sb)->root_files--;
256267
if (S_ISREG(inode->i_mode)) {

fs/hfs/mdb.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,11 @@ int hfs_mdb_get(struct super_block *sb)
150150

151151
/* These parameters are read from and written to the MDB */
152152
HFS_SB(sb)->free_ablocks = be16_to_cpu(mdb->drFreeBks);
153-
HFS_SB(sb)->next_id = be32_to_cpu(mdb->drNxtCNID);
153+
atomic64_set(&HFS_SB(sb)->next_id, be32_to_cpu(mdb->drNxtCNID));
154154
HFS_SB(sb)->root_files = be16_to_cpu(mdb->drNmFls);
155155
HFS_SB(sb)->root_dirs = be16_to_cpu(mdb->drNmRtDirs);
156-
HFS_SB(sb)->file_count = be32_to_cpu(mdb->drFilCnt);
157-
HFS_SB(sb)->folder_count = be32_to_cpu(mdb->drDirCnt);
156+
atomic64_set(&HFS_SB(sb)->file_count, be32_to_cpu(mdb->drFilCnt));
157+
atomic64_set(&HFS_SB(sb)->folder_count, be32_to_cpu(mdb->drDirCnt));
158158

159159
/* TRY to get the alternate (backup) MDB. */
160160
sect = part_start + part_size - 2;
@@ -273,11 +273,17 @@ void hfs_mdb_commit(struct super_block *sb)
273273
/* These parameters may have been modified, so write them back */
274274
mdb->drLsMod = hfs_mtime();
275275
mdb->drFreeBks = cpu_to_be16(HFS_SB(sb)->free_ablocks);
276-
mdb->drNxtCNID = cpu_to_be32(HFS_SB(sb)->next_id);
276+
BUG_ON(atomic64_read(&HFS_SB(sb)->next_id) > U32_MAX);
277+
mdb->drNxtCNID =
278+
cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->next_id));
277279
mdb->drNmFls = cpu_to_be16(HFS_SB(sb)->root_files);
278280
mdb->drNmRtDirs = cpu_to_be16(HFS_SB(sb)->root_dirs);
279-
mdb->drFilCnt = cpu_to_be32(HFS_SB(sb)->file_count);
280-
mdb->drDirCnt = cpu_to_be32(HFS_SB(sb)->folder_count);
281+
BUG_ON(atomic64_read(&HFS_SB(sb)->file_count) > U32_MAX);
282+
mdb->drFilCnt =
283+
cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->file_count));
284+
BUG_ON(atomic64_read(&HFS_SB(sb)->folder_count) > U32_MAX);
285+
mdb->drDirCnt =
286+
cpu_to_be32((u32)atomic64_read(&HFS_SB(sb)->folder_count));
281287

282288
/* write MDB to disk */
283289
mark_buffer_dirty(HFS_SB(sb)->mdb_bh);

fs/hfs/super.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ static int hfs_fill_super(struct super_block *sb, struct fs_context *fc)
319319
int silent = fc->sb_flags & SB_SILENT;
320320
int res;
321321

322+
atomic64_set(&sbi->file_count, 0);
323+
atomic64_set(&sbi->folder_count, 0);
324+
atomic64_set(&sbi->next_id, 0);
325+
322326
/* load_nls_default does not fail */
323327
if (sbi->nls_disk && !sbi->nls_io)
324328
sbi->nls_io = load_nls_default();

0 commit comments

Comments
 (0)