diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-17 18:34:43 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-17 18:34:43 -0700 |
| commit | 63c070cba0c6a586d33730ddbf63065cf28276b7 (patch) | |
| tree | 456207ef242efa3884158c36d896965ba4cbafe0 | |
| parent | 0de672c7e1920acc5675d0dd22718c4f6e6b4447 (diff) | |
| parent | 24a098541a765ed017274f8094004404a83b0daf (diff) | |
Merge tag 'nilfs2-v7.3-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/nilfs2
Pull nilfs2 updates from Viacheslav Dubeyko:
"This contains fixes of syzbot reported issue and various fixes in
NILFS2 functionality:
- Reject super-root inode sizes whose computed on-disk footprint
exceeds the filesystem block size (David Lee)
- Replace WARN_ON() in nilfs_cpfile_delete_checkpoints() with
returning -EIO and reporting a filesystem error via nilfs_error()
in the case of corrupted checkpoint count on the storage medium
(Igor Putko)
- Fixed a potential infinite loop in nilfs_clean_segments() reported
by syzbot (Joshua Crofts)
In nilfs_clean_segments(), if err is non-zero, logic logs the error
and sleeps but doesn't abort when it encounters a terminal error
like -EROFS. This causes the thread to loop forever.
Fix this by breaking out of the loop if nilfs_segctor_construct()
returns -EROFS.
- Fix small grammar mistake in the description for nilfs2 recovery
code (Manoj K M)
- Multiple fixes by Ryusuke Konishi:
- fix the list corruption issue recently detected by syzbot, that
can occur when out-of-range values are intentionally passed to
certain GC ioctl parameters
- fix a flaw in the original B-tree implementation related to
truncation and resolves the reported out-of-bounds memory
access issue
- fix an issue reported by syzbot where a kernel BUG could be
triggered depending on timing after filesystem corruption is
detected
- fix an issue where a WARN_ON check is triggered by sufile
functions within the log writer after the filesystem degrades
to read-only mode
- Check for sorted keys when reading btree node blocks into the cache
(Wang Jianjian)
This prevents unexpected errors during the block number assignment
phase in log writing caused by key order inconsistencies, as well
as the kernel warnings reported by syzbot"
* tag 'nilfs2-v7.3-tag1' of git://git.kernel.org/pub/scm/linux/kernel/git/vdubeyko/nilfs2:
nilfs2: standardize the inode number type to u64
nilfs2: enhance btree node keys check
nilfs2: suppress false positive WARN_ONs for sufile after an FS error
nilfs2: fix BUG in nilfs_copy_dirty_pages() on dirty state mismatch
nilfs2: prevent out-of-bounds read in super root block parsing
nilfs2: fix infinite loop in nilfs_clean_segments()
nilfs2: fix slab-out-of-bounds in nilfs_direct_propagate after truncation
Documentation: fix grammar in description of nilfs2 recovery code
nilfs2: handle corrupted checkpoint count gracefully during deletion
nilfs2: reject invalid block index in GC ioctl
| -rw-r--r-- | Documentation/filesystems/nilfs2.rst | 2 | ||||
| -rw-r--r-- | fs/nilfs2/bmap.c | 2 | ||||
| -rw-r--r-- | fs/nilfs2/bmap.h | 2 | ||||
| -rw-r--r-- | fs/nilfs2/btree.c | 58 | ||||
| -rw-r--r-- | fs/nilfs2/cpfile.c | 20 | ||||
| -rw-r--r-- | fs/nilfs2/dir.c | 11 | ||||
| -rw-r--r-- | fs/nilfs2/direct.c | 4 | ||||
| -rw-r--r-- | fs/nilfs2/ifile.c | 13 | ||||
| -rw-r--r-- | fs/nilfs2/ifile.h | 10 | ||||
| -rw-r--r-- | fs/nilfs2/inode.c | 24 | ||||
| -rw-r--r-- | fs/nilfs2/ioctl.c | 22 | ||||
| -rw-r--r-- | fs/nilfs2/namei.c | 6 | ||||
| -rw-r--r-- | fs/nilfs2/nilfs.h | 15 | ||||
| -rw-r--r-- | fs/nilfs2/page.c | 26 | ||||
| -rw-r--r-- | fs/nilfs2/recovery.c | 4 | ||||
| -rw-r--r-- | fs/nilfs2/segment.c | 42 | ||||
| -rw-r--r-- | fs/nilfs2/sufile.h | 28 | ||||
| -rw-r--r-- | fs/nilfs2/the_nilfs.c | 6 |
18 files changed, 206 insertions, 89 deletions
diff --git a/Documentation/filesystems/nilfs2.rst b/Documentation/filesystems/nilfs2.rst index e3a5c8977f2c..a0124d22a237 100644 --- a/Documentation/filesystems/nilfs2.rst +++ b/Documentation/filesystems/nilfs2.rst @@ -252,7 +252,7 @@ The following figure shows a typical organization of the logs:: To stride over segment boundaries, this sequence of files may be split into multiple logs. The sequence of logs that should be treated as logically one log, is delimited with flags marked in the segment -summary. The recovery code of nilfs2 looks this boundary information +summary. The recovery code of nilfs2 looks at this boundary information to ensure atomicity of updates. The super root block is inserted for every checkpoints. It includes diff --git a/fs/nilfs2/bmap.c b/fs/nilfs2/bmap.c index 5f0f1f283af0..83f6ea30cc8b 100644 --- a/fs/nilfs2/bmap.c +++ b/fs/nilfs2/bmap.c @@ -175,7 +175,7 @@ static int nilfs_bmap_do_delete(struct nilfs_bmap *bmap, __u64 key) return ret; } - return bmap->b_ops->bop_delete(bmap, key); + return bmap->b_ops->bop_delete(bmap, key, false); } /** diff --git a/fs/nilfs2/bmap.h b/fs/nilfs2/bmap.h index 4656df392722..a72f3c308a5d 100644 --- a/fs/nilfs2/bmap.h +++ b/fs/nilfs2/bmap.h @@ -63,7 +63,7 @@ struct nilfs_bmap_operations { int (*bop_lookup_contig)(const struct nilfs_bmap *, __u64, __u64 *, unsigned int); int (*bop_insert)(struct nilfs_bmap *, __u64, __u64); - int (*bop_delete)(struct nilfs_bmap *, __u64); + int (*bop_delete)(struct nilfs_bmap *bmap, __u64 key, bool deform); void (*bop_clear)(struct nilfs_bmap *); int (*bop_propagate)(struct nilfs_bmap *, struct buffer_head *); diff --git a/fs/nilfs2/btree.c b/fs/nilfs2/btree.c index 64d5f7c5ab44..6b8332e8c0db 100644 --- a/fs/nilfs2/btree.c +++ b/fs/nilfs2/btree.c @@ -341,7 +341,8 @@ static int nilfs_btree_node_broken(const struct nilfs_btree_node *node, sector_t blocknr) { int level, flags, nchildren; - int ret = 0; + __u64 key, prev_key; + int i; level = nilfs_btree_node_get_level(node); flags = nilfs_btree_node_get_flags(node); @@ -356,9 +357,21 @@ static int nilfs_btree_node_broken(const struct nilfs_btree_node *node, "bad btree node (ino=%llu, blocknr=%llu): level = %d, flags = 0x%x, nchildren = %d", inode->i_ino, (unsigned long long)blocknr, level, flags, nchildren); - ret = 1; + return 1; } - return ret; + + for (i = 1, prev_key = nilfs_btree_node_get_key(node, 0); + i < nchildren; i++, prev_key = key) { + key = nilfs_btree_node_get_key(node, i); + if (unlikely(key <= prev_key)) { + nilfs_crit(inode->i_sb, + "bad btree node (ino=%llu, blocknr=%llu): unsorted keys at index %d (%llu) and %d (%llu)", + inode->i_ino, (unsigned long long)blocknr, + i - 1, prev_key, i, key); + return 1; + } + } + return 0; } /** @@ -1425,6 +1438,28 @@ static void nilfs_btree_shrink(struct nilfs_bmap *btree, path[level].bp_bh = NULL; } +/** + * nilfs_btree_discard - discard the last node for the mapping transformation + * @btree: bmap struct of btree + * @path: array of nilfs_btree_path struct + * @level: level of the B-tree node being operated on + * @keyp: argument for passing a key (unused) + * @ptrp: argument for passing a pointer (unused) + */ +static void nilfs_btree_discard(struct nilfs_bmap *btree, + struct nilfs_btree_path *path, int level, + __u64 *keyp, __u64 *ptrp) +{ + struct nilfs_btree_node *root = nilfs_btree_get_root(btree); + + nilfs_btree_node_delete(root, 0, NULL, NULL, + NILFS_BTREE_ROOT_NCHILDREN_MAX); + nilfs_btree_node_set_level(root, level); + + nilfs_btnode_delete(path[level].bp_bh); + path[level].bp_bh = NULL; +} + static void nilfs_btree_nop(struct nilfs_bmap *btree, struct nilfs_btree_path *path, int level, __u64 *keyp, __u64 *ptrp) @@ -1435,7 +1470,7 @@ static int nilfs_btree_prepare_delete(struct nilfs_bmap *btree, struct nilfs_btree_path *path, int *levelp, struct nilfs_bmap_stats *stats, - struct inode *dat) + struct inode *dat, bool deform) { struct buffer_head *bh; struct nilfs_btree_node *node, *parent, *sib; @@ -1522,15 +1557,17 @@ static int nilfs_btree_prepare_delete(struct nilfs_bmap *btree, if (nilfs_btree_node_get_nchildren(node) - 1 <= NILFS_BTREE_ROOT_NCHILDREN_MAX) { path[level].bp_op = nilfs_btree_shrink; - stats->bs_nblocks += 2; - level++; - path[level].bp_op = nilfs_btree_nop; - goto shrink_root_child; + } else if (deform) { + path[level].bp_op = nilfs_btree_discard; } else { path[level].bp_op = nilfs_btree_do_delete; stats->bs_nblocks++; goto out; } + stats->bs_nblocks += 2; + level++; + path[level].bp_op = nilfs_btree_nop; + goto shrink_root_child; } } @@ -1581,7 +1618,7 @@ static void nilfs_btree_commit_delete(struct nilfs_bmap *btree, nilfs_bmap_set_dirty(btree); } -static int nilfs_btree_delete(struct nilfs_bmap *btree, __u64 key) +static int nilfs_btree_delete(struct nilfs_bmap *btree, __u64 key, bool deform) { struct nilfs_btree_path *path; @@ -1601,7 +1638,8 @@ static int nilfs_btree_delete(struct nilfs_bmap *btree, __u64 key) dat = NILFS_BMAP_USE_VBN(btree) ? nilfs_bmap_get_dat(btree) : NULL; - ret = nilfs_btree_prepare_delete(btree, path, &level, &stats, dat); + ret = nilfs_btree_prepare_delete(btree, path, &level, &stats, dat, + deform); if (ret < 0) goto out; nilfs_btree_commit_delete(btree, path, level, dat); diff --git a/fs/nilfs2/cpfile.c b/fs/nilfs2/cpfile.c index 4bbdc832d7f2..d3349fa58abe 100644 --- a/fs/nilfs2/cpfile.c +++ b/fs/nilfs2/cpfile.c @@ -81,18 +81,26 @@ nilfs_cpfile_block_add_valid_checkpoints(const struct inode *cpfile, return count; } -static unsigned int +static int nilfs_cpfile_block_sub_valid_checkpoints(const struct inode *cpfile, struct buffer_head *bh, unsigned int n) { struct nilfs_checkpoint *cp; - unsigned int count; + unsigned int checkpoints_count; + int count; cp = kmap_local_folio(bh->b_folio, offset_in_folio(bh->b_folio, bh->b_data)); - WARN_ON(le32_to_cpu(cp->cp_checkpoints_count) < n); - count = le32_to_cpu(cp->cp_checkpoints_count) - n; + checkpoints_count = le32_to_cpu(cp->cp_checkpoints_count); + if (unlikely(checkpoints_count < n)) { + nilfs_error(cpfile->i_sb, + "deleted checkpoints count %u exceeds block count %u", + n, checkpoints_count); + kunmap_local(cp); + return -EIO; + } + count = checkpoints_count - n; cp->cp_checkpoints_count = cpu_to_le32(count); kunmap_local(cp); return count; @@ -522,6 +530,10 @@ int nilfs_cpfile_delete_checkpoints(struct inode *cpfile, count = nilfs_cpfile_block_sub_valid_checkpoints(cpfile, cp_bh, nicps); brelse(cp_bh); + if (unlikely(count < 0)) { + ret = count; + break; + } if (count) continue; diff --git a/fs/nilfs2/dir.c b/fs/nilfs2/dir.c index 3653db5cdb65..8b53802b6ebd 100644 --- a/fs/nilfs2/dir.c +++ b/fs/nilfs2/dir.c @@ -169,17 +169,16 @@ Einumber: error = "disallowed inode number"; bad_entry: nilfs_error(sb, - "bad entry in directory #%llu: %s - offset=%lu, inode=%lu, rec_len=%zd, name_len=%d", + "bad entry in directory #%llu: %s - offset=%lu, inode=%llu, rec_len=%zd, name_len=%d", dir->i_ino, error, (folio->index << PAGE_SHIFT) + offs, - (unsigned long)le64_to_cpu(p->inode), - rec_len, p->name_len); + le64_to_cpu(p->inode), rec_len, p->name_len); goto fail; Eend: p = (struct nilfs_dir_entry *)(kaddr + offs); nilfs_error(sb, - "entry in directory #%llu spans the page boundary offset=%lu, inode=%lu", + "entry in directory #%llu spans the page boundary offset=%lu, inode=%llu", dir->i_ino, (folio->index << PAGE_SHIFT) + offs, - (unsigned long)le64_to_cpu(p->inode)); + le64_to_cpu(p->inode)); fail: return false; } @@ -387,7 +386,7 @@ fail: return NULL; } -int nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr, ino_t *ino) +int nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr, u64 *ino) { struct nilfs_dir_entry *de; struct folio *folio; diff --git a/fs/nilfs2/direct.c b/fs/nilfs2/direct.c index 8bd0b1374e25..b8643d3aa2f8 100644 --- a/fs/nilfs2/direct.c +++ b/fs/nilfs2/direct.c @@ -144,7 +144,7 @@ static int nilfs_direct_insert(struct nilfs_bmap *bmap, __u64 key, __u64 ptr) return ret; } -static int nilfs_direct_delete(struct nilfs_bmap *bmap, __u64 key) +static int nilfs_direct_delete(struct nilfs_bmap *bmap, __u64 key, bool deform) { union nilfs_bmap_ptr_req req; struct inode *dat; @@ -234,7 +234,7 @@ int nilfs_direct_delete_and_convert(struct nilfs_bmap *bmap, /* no need to allocate any resource for conversion */ /* delete */ - ret = bmap->b_ops->bop_delete(bmap, key); + ret = bmap->b_ops->bop_delete(bmap, key, true); if (ret < 0) return ret; diff --git a/fs/nilfs2/ifile.c b/fs/nilfs2/ifile.c index 99eb8a59009e..c99e1e6356ae 100644 --- a/fs/nilfs2/ifile.c +++ b/fs/nilfs2/ifile.c @@ -49,7 +49,7 @@ static inline struct nilfs_ifile_info *NILFS_IFILE_I(struct inode *ifile) * * %-ENOMEM - Insufficient memory available. * * %-ENOSPC - No inode left. */ -int nilfs_ifile_create_inode(struct inode *ifile, ino_t *out_ino, +int nilfs_ifile_create_inode(struct inode *ifile, u64 *out_ino, struct buffer_head **out_bh) { struct nilfs_palloc_req req; @@ -72,7 +72,7 @@ int nilfs_ifile_create_inode(struct inode *ifile, ino_t *out_ino, nilfs_palloc_commit_alloc_entry(ifile, &req); mark_buffer_dirty(req.pr_entry_bh); nilfs_mdt_mark_dirty(ifile); - *out_ino = (ino_t)req.pr_entry_nr; + *out_ino = req.pr_entry_nr; *out_bh = req.pr_entry_bh; return 0; } @@ -88,7 +88,7 @@ int nilfs_ifile_create_inode(struct inode *ifile, ino_t *out_ino, * * %-ENOENT - Inode number unallocated. * * %-ENOMEM - Insufficient memory available. */ -int nilfs_ifile_delete_inode(struct inode *ifile, ino_t ino) +int nilfs_ifile_delete_inode(struct inode *ifile, u64 ino) { struct nilfs_palloc_req req = { .pr_entry_nr = ino, .pr_entry_bh = NULL @@ -123,21 +123,20 @@ int nilfs_ifile_delete_inode(struct inode *ifile, ino_t ino) return 0; } -int nilfs_ifile_get_inode_block(struct inode *ifile, ino_t ino, +int nilfs_ifile_get_inode_block(struct inode *ifile, u64 ino, struct buffer_head **out_bh) { struct super_block *sb = ifile->i_sb; int err; if (unlikely(!NILFS_VALID_INODE(sb, ino))) { - nilfs_error(sb, "bad inode number: %lu", (unsigned long)ino); + nilfs_error(sb, "bad inode number: %llu", ino); return -EINVAL; } err = nilfs_palloc_get_entry_block(ifile, ino, 0, out_bh); if (unlikely(err)) - nilfs_warn(sb, "error %d reading inode: ino=%lu", - err, (unsigned long)ino); + nilfs_warn(sb, "error %d reading inode: ino=%llu", err, ino); return err; } diff --git a/fs/nilfs2/ifile.h b/fs/nilfs2/ifile.h index 5d116a566d9e..d38a46f5ae41 100644 --- a/fs/nilfs2/ifile.h +++ b/fs/nilfs2/ifile.h @@ -19,7 +19,7 @@ static inline struct nilfs_inode * -nilfs_ifile_map_inode(struct inode *ifile, ino_t ino, struct buffer_head *ibh) +nilfs_ifile_map_inode(struct inode *ifile, u64 ino, struct buffer_head *ibh) { size_t __offset_in_folio = nilfs_palloc_entry_offset(ifile, ino, ibh); @@ -31,9 +31,11 @@ static inline void nilfs_ifile_unmap_inode(struct nilfs_inode *raw_inode) kunmap_local(raw_inode); } -int nilfs_ifile_create_inode(struct inode *, ino_t *, struct buffer_head **); -int nilfs_ifile_delete_inode(struct inode *, ino_t); -int nilfs_ifile_get_inode_block(struct inode *, ino_t, struct buffer_head **); +int nilfs_ifile_create_inode(struct inode *ifile, u64 *out_ino, + struct buffer_head **out_bh); +int nilfs_ifile_delete_inode(struct inode *ifile, u64 ino); +int nilfs_ifile_get_inode_block(struct inode *ifile, u64 ino, + struct buffer_head **out_bh); int nilfs_ifile_count_free_inodes(struct inode *, u64 *, u64 *); diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index 51f7e125a311..34e6096069ad 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -287,8 +287,7 @@ const struct address_space_operations nilfs_buffer_cache_aops = { }; static int nilfs_insert_inode_locked(struct inode *inode, - struct nilfs_root *root, - unsigned long ino) + struct nilfs_root *root, u64 ino) { struct nilfs_iget_args args = { .ino = ino, .root = root, .cno = 0, .type = NILFS_I_TYPE_NORMAL @@ -305,7 +304,7 @@ struct inode *nilfs_new_inode(struct inode *dir, umode_t mode) struct nilfs_root *root; struct buffer_head *bh; int err = -ENOMEM; - ino_t ino; + u64 ino; inode = new_inode(sb); if (unlikely(!inode)) @@ -443,7 +442,7 @@ int nilfs_read_inode_common(struct inode *inode, } static int __nilfs_read_inode(struct super_block *sb, - struct nilfs_root *root, unsigned long ino, + struct nilfs_root *root, u64 ino, struct inode *inode) { struct the_nilfs *nilfs = sb->s_fs_info; @@ -482,8 +481,8 @@ static int __nilfs_read_inode(struct super_block *sb, huge_decode_dev(le64_to_cpu(raw_inode->i_device_code))); } else { nilfs_error(sb, - "invalid file type bits in mode 0%o for inode %lu", - inode->i_mode, ino); + "invalid file type bits in mode 0%o for inode %llu", + inode->i_mode, ino); err = -EIO; goto failed_unmap; } @@ -533,7 +532,7 @@ static int nilfs_iget_set(struct inode *inode, void *opaque) } struct inode *nilfs_ilookup(struct super_block *sb, struct nilfs_root *root, - unsigned long ino) + u64 ino) { struct nilfs_iget_args args = { .ino = ino, .root = root, .cno = 0, .type = NILFS_I_TYPE_NORMAL @@ -542,8 +541,8 @@ struct inode *nilfs_ilookup(struct super_block *sb, struct nilfs_root *root, return ilookup5(sb, ino, nilfs_iget_test, &args); } -struct inode *nilfs_iget_locked(struct super_block *sb, struct nilfs_root *root, - unsigned long ino) +struct inode *nilfs_iget_locked(struct super_block *sb, + struct nilfs_root *root, u64 ino) { struct nilfs_iget_args args = { .ino = ino, .root = root, .cno = 0, .type = NILFS_I_TYPE_NORMAL @@ -553,7 +552,7 @@ struct inode *nilfs_iget_locked(struct super_block *sb, struct nilfs_root *root, } struct inode *nilfs_iget(struct super_block *sb, struct nilfs_root *root, - unsigned long ino) + u64 ino) { struct inode *inode; int err; @@ -579,8 +578,7 @@ struct inode *nilfs_iget(struct super_block *sb, struct nilfs_root *root, return inode; } -struct inode *nilfs_iget_for_gc(struct super_block *sb, unsigned long ino, - __u64 cno) +struct inode *nilfs_iget_for_gc(struct super_block *sb, u64 ino, __u64 cno) { struct nilfs_iget_args args = { .ino = ino, .root = NULL, .cno = cno, .type = NILFS_I_TYPE_GC @@ -740,7 +738,7 @@ void nilfs_write_inode_common(struct inode *inode, void nilfs_update_inode(struct inode *inode, struct buffer_head *ibh, int flags) { - ino_t ino = inode->i_ino; + u64 ino = inode->i_ino; struct nilfs_inode_info *ii = NILFS_I(inode); struct inode *ifile = ii->i_root->ifile; struct nilfs_inode *raw_inode; diff --git a/fs/nilfs2/ioctl.c b/fs/nilfs2/ioctl.c index b73f2c5d10f0..01a04080ef70 100644 --- a/fs/nilfs2/ioctl.c +++ b/fs/nilfs2/ioctl.c @@ -527,6 +527,7 @@ static int nilfs_ioctl_get_bdescs(struct inode *inode, struct file *filp, * Return: 0 on success, or one of the following negative error codes on * failure: * * %-EEXIST - Block conflict detected. + * * %-EINVAL - Invalid virtual block descriptor. * * %-EIO - I/O error. * * %-ENOENT - Requested block doesn't exist. * * %-ENOMEM - Insufficient memory available. @@ -536,15 +537,30 @@ static int nilfs_ioctl_move_inode_block(struct inode *inode, struct list_head *buffers) { struct buffer_head *bh; + __u64 limit_blkidx = (__u64)inode->i_sb->s_maxbytes >> inode->i_blkbits; int ret; - if (vdesc->vd_flags == 0) + /* + * vblocknr 0 is reserved as an invalid pointer. Also, limit_blkidx + * ensures that the page index converted from vd_vblocknr never + * overflows the page cache limit and respects the architecture's bmap + * key width. + */ + if (unlikely(vdesc->vd_vblocknr == 0 || + vdesc->vd_vblocknr >= limit_blkidx)) + return -EINVAL; + + if (vdesc->vd_flags == 0) { + if (unlikely(vdesc->vd_offset >= limit_blkidx)) + return -EINVAL; + ret = nilfs_gccache_submit_read_data( inode, vdesc->vd_offset, vdesc->vd_blocknr, vdesc->vd_vblocknr, &bh); - else + } else { ret = nilfs_gccache_submit_read_node( inode, vdesc->vd_blocknr, vdesc->vd_vblocknr, &bh); + } if (unlikely(ret < 0)) { if (ret == -ENOENT) @@ -596,7 +612,7 @@ static int nilfs_ioctl_move_blocks(struct super_block *sb, struct nilfs_vdesc *vdesc; struct buffer_head *bh, *n; LIST_HEAD(buffers); - ino_t ino; + u64 ino; __u64 cno; int i, ret; diff --git a/fs/nilfs2/namei.c b/fs/nilfs2/namei.c index 77c5f7f74fbf..e037e0c6e31a 100644 --- a/fs/nilfs2/namei.c +++ b/fs/nilfs2/namei.c @@ -54,7 +54,7 @@ static struct dentry * nilfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) { struct inode *inode; - ino_t ino; + u64 ino; int res; if (dentry->d_name.len > NILFS_NAME_LEN) @@ -69,7 +69,7 @@ nilfs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) inode = nilfs_iget(dir->i_sb, NILFS_I(dir)->i_root, ino); if (inode == ERR_PTR(-ESTALE)) { nilfs_error(dir->i_sb, - "deleted inode referenced: %lu", ino); + "deleted inode referenced: %llu", ino); return ERR_PTR(-EIO); } } @@ -463,7 +463,7 @@ out: */ static struct dentry *nilfs_get_parent(struct dentry *child) { - ino_t ino; + u64 ino; int res; struct nilfs_root *root; diff --git a/fs/nilfs2/nilfs.h b/fs/nilfs2/nilfs.h index b7e3d91b6243..4fc42d3787a4 100644 --- a/fs/nilfs2/nilfs.h +++ b/fs/nilfs2/nilfs.h @@ -144,7 +144,7 @@ enum { ((ino) < NILFS_USER_INO && (NILFS_SYS_INO_BITS & BIT(ino)))) #define NILFS_PRIVATE_INODE(ino) ({ \ - ino_t __ino = (ino); \ + u64 __ino = (ino); \ ((__ino) < NILFS_USER_INO && (__ino) != NILFS_ROOT_INO && \ (__ino) != NILFS_SKETCH_INO); }) @@ -255,7 +255,7 @@ static inline __u32 nilfs_mask_flags(umode_t mode, __u32 flags) /* dir.c */ int nilfs_add_link(struct dentry *, struct inode *); -int nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr, ino_t *ino); +int nilfs_inode_by_name(struct inode *dir, const struct qstr *qstr, u64 *ino); int nilfs_make_empty(struct inode *, struct inode *); struct nilfs_dir_entry *nilfs_find_entry(struct inode *, const struct qstr *, struct folio **); @@ -287,13 +287,12 @@ extern int nilfs_read_inode_common(struct inode *, struct nilfs_inode *); void nilfs_write_inode_common(struct inode *inode, struct nilfs_inode *raw_inode); struct inode *nilfs_ilookup(struct super_block *sb, struct nilfs_root *root, - unsigned long ino); -struct inode *nilfs_iget_locked(struct super_block *sb, struct nilfs_root *root, - unsigned long ino); + u64 ino); +struct inode *nilfs_iget_locked(struct super_block *sb, + struct nilfs_root *root, u64 ino); struct inode *nilfs_iget(struct super_block *sb, struct nilfs_root *root, - unsigned long ino); -extern struct inode *nilfs_iget_for_gc(struct super_block *sb, - unsigned long ino, __u64 cno); + u64 ino); +struct inode *nilfs_iget_for_gc(struct super_block *sb, u64 ino, __u64 cno); int nilfs_attach_btree_node_cache(struct inode *inode); void nilfs_detach_btree_node_cache(struct inode *inode); struct inode *nilfs_iget_for_shadow(struct inode *inode); diff --git a/fs/nilfs2/page.c b/fs/nilfs2/page.c index a9d8aa65416f..cf4f1c6798f5 100644 --- a/fs/nilfs2/page.c +++ b/fs/nilfs2/page.c @@ -154,7 +154,7 @@ void nilfs_folio_bug(struct folio *folio) { struct buffer_head *bh, *head; struct address_space *m; - unsigned long ino; + u64 ino; if (unlikely(!folio)) { printk(KERN_CRIT "NILFS_FOLIO_BUG(NULL)\n"); @@ -164,10 +164,9 @@ void nilfs_folio_bug(struct folio *folio) m = folio->mapping; ino = m ? m->host->i_ino : 0; - printk(KERN_CRIT "NILFS_FOLIO_BUG(%p): cnt=%d index#=%llu flags=0x%lx " - "mapping=%p ino=%lu\n", - folio, folio_ref_count(folio), - (unsigned long long)folio->index, folio->flags.f, m, ino); + printk(KERN_CRIT "NILFS_FOLIO_BUG(%p): cnt=%d index#=%lu flags=0x%lx mapping=%p ino=%llu\n", + folio, folio_ref_count(folio), folio->index, folio->flags.f, + m, ino); head = folio_buffers(folio); if (head) { @@ -243,6 +242,7 @@ static void nilfs_copy_folio(struct folio *dst, struct folio *src, int nilfs_copy_dirty_pages(struct address_space *dmap, struct address_space *smap) { + struct inode *smap_inode = smap->host; struct folio_batch fbatch; unsigned int i; pgoff_t index = 0; @@ -258,8 +258,19 @@ repeat: struct folio *folio = fbatch.folios[i], *dfolio; folio_lock(folio); - if (unlikely(!folio_test_dirty(folio))) - NILFS_FOLIO_BUG(folio, "inconsistent dirty state"); + if (unlikely(!folio_test_dirty(folio))) { + if (WARN_ONCE(!sb_rdonly(smap_inode->i_sb), + "inconsistent dirty state\n")) + goto unlock_folio; + + /* + * If the filesystem has been forced to read-only + * due to metadata corruption. + */ + folio_unlock(folio); + err = -EROFS; + break; + } dfolio = filemap_grab_folio(dmap, folio->index); if (IS_ERR(dfolio)) { @@ -277,6 +288,7 @@ repeat: folio_unlock(dfolio); folio_put(dfolio); +unlock_folio: folio_unlock(folio); } folio_batch_release(&fbatch); diff --git a/fs/nilfs2/recovery.c b/fs/nilfs2/recovery.c index 4d5a6aa5214c..45fb37215669 100644 --- a/fs/nilfs2/recovery.c +++ b/fs/nilfs2/recovery.c @@ -34,7 +34,7 @@ enum { /* work structure for recovery */ struct nilfs_recovery_block { - ino_t ino; /* + u64 ino; /* * Inode number of the file that this block * belongs to */ @@ -333,7 +333,7 @@ static int nilfs_scan_dsync_log(struct the_nilfs *nilfs, sector_t start_blocknr, unsigned int offset; u32 nfinfo, sumbytes; sector_t blocknr; - ino_t ino; + u64 ino; int err = -EIO; nfinfo = le32_to_cpu(sum->ss_nfinfo); diff --git a/fs/nilfs2/segment.c b/fs/nilfs2/segment.c index 9332f5ac6083..829573cb6131 100644 --- a/fs/nilfs2/segment.c +++ b/fs/nilfs2/segment.c @@ -1433,7 +1433,7 @@ static int nilfs_segctor_extend_segments(struct nilfs_sc_info *sci, failed: list_for_each_entry(segbuf, &list, sb_list) { ret = nilfs_sufile_free(sufile, segbuf->sb_nextnum); - WARN_ON(ret); /* never fails */ + nilfs_sufile_warn_on_error(sufile, ret); } nilfs_destroy_logs(&list); return err; @@ -1449,7 +1449,7 @@ static void nilfs_free_incomplete_logs(struct list_head *logs, segbuf = NILFS_FIRST_SEGBUF(logs); if (nilfs->ns_nextnum != segbuf->sb_nextnum) { ret = nilfs_sufile_free(sufile, segbuf->sb_nextnum); - WARN_ON(ret); /* never fails */ + nilfs_sufile_warn_on_error(sufile, ret); } if (atomic_read(&segbuf->sb_err)) { /* Case 1: The first segment failed */ @@ -1468,7 +1468,7 @@ static void nilfs_free_incomplete_logs(struct list_head *logs, list_for_each_entry_continue(segbuf, logs, sb_list) { if (prev->sb_nextnum != segbuf->sb_nextnum) { ret = nilfs_sufile_free(sufile, segbuf->sb_nextnum); - WARN_ON(ret); /* never fails */ + nilfs_sufile_warn_on_error(sufile, ret); } if (atomic_read(&segbuf->sb_err) && segbuf->sb_segnum != nilfs->ns_nextnum) @@ -1491,7 +1491,7 @@ static void nilfs_segctor_update_segusage(struct nilfs_sc_info *sci, ret = nilfs_sufile_set_segment_usage(sufile, segbuf->sb_segnum, live_blocks, sci->sc_seg_ctime); - WARN_ON(ret); /* always succeed because the segusage is dirty */ + nilfs_sufile_warn_on_error(sufile, ret); } } @@ -1504,28 +1504,32 @@ static void nilfs_cancel_segusage(struct list_head *logs, struct inode *sufile) ret = nilfs_sufile_set_segment_usage(sufile, segbuf->sb_segnum, segbuf->sb_pseg_start - segbuf->sb_fseg_start, 0); - WARN_ON(ret); /* always succeed because the segusage is dirty */ + nilfs_sufile_warn_on_error(sufile, ret); list_for_each_entry_continue(segbuf, logs, sb_list) { ret = nilfs_sufile_set_segment_usage(sufile, segbuf->sb_segnum, 0, 0); - WARN_ON(ret); /* always succeed */ + nilfs_sufile_warn_on_error(sufile, ret); } } -static void nilfs_segctor_truncate_segments(struct nilfs_sc_info *sci, - struct nilfs_segment_buffer *last, - struct inode *sufile) +static int nilfs_segctor_truncate_segments(struct nilfs_sc_info *sci, + struct nilfs_segment_buffer *last, + struct inode *sufile) { struct nilfs_segment_buffer *segbuf = last; - int ret; + int ret, err = 0; list_for_each_entry_continue(segbuf, &sci->sc_segbufs, sb_list) { sci->sc_segbuf_nblocks -= segbuf->sb_rest_blocks; - ret = nilfs_sufile_free(sufile, segbuf->sb_nextnum); - WARN_ON(ret); + + ret = nilfs_sufile_warn_on_error( + sufile, nilfs_sufile_free(sufile, segbuf->sb_nextnum)); + if (unlikely(ret) && err != -EROFS) + err = ret; } nilfs_truncate_logs(&sci->sc_segbufs, last); + return err; } @@ -1564,7 +1568,7 @@ static int nilfs_segctor_collect(struct nilfs_sc_info *sci, sci->sc_freesegs, sci->sc_nfreesegs, NULL); - WARN_ON(err); /* do not happen */ + nilfs_sufile_warn_on_error(nilfs->ns_sufile, err); sci->sc_stage.flags &= ~NILFS_CF_SUFREED; } @@ -1576,8 +1580,8 @@ static int nilfs_segctor_collect(struct nilfs_sc_info *sci, sci->sc_stage = prev_stage; } nilfs_segctor_zeropad_segsum(sci); - nilfs_segctor_truncate_segments(sci, sci->sc_curseg, nilfs->ns_sufile); - return 0; + err = nilfs_segctor_truncate_segments(sci, sci->sc_curseg, + nilfs->ns_sufile); failed: return err; @@ -1606,7 +1610,7 @@ nilfs_segctor_update_payload_blocknr(struct nilfs_sc_info *sci, struct nilfs_finfo *finfo = NULL; union nilfs_binfo binfo; struct buffer_head *bh, *bh_org; - ino_t ino = 0; + u64 ino = 0; int err = 0; if (!nfinfo) @@ -1878,7 +1882,7 @@ static void nilfs_segctor_abort_construction(struct nilfs_sc_info *sci, sci->sc_freesegs, sci->sc_nfreesegs, NULL); - WARN_ON(ret); /* do not happen */ + nilfs_sufile_warn_on_error(nilfs->ns_sufile, ret); } nilfs_destroy_logs(&logs); @@ -2561,6 +2565,10 @@ int nilfs_clean_segments(struct super_block *sb, struct nilfs_argv *argv, break; nilfs_warn(sb, "error %d cleaning segments", err); + + if (unlikely(err == -EROFS)) + goto out_unlock; + set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(sci->sc_interval); } diff --git a/fs/nilfs2/sufile.h b/fs/nilfs2/sufile.h index cd6f28ab3521..5888ed479c8b 100644 --- a/fs/nilfs2/sufile.h +++ b/fs/nilfs2/sufile.h @@ -10,6 +10,7 @@ #ifndef _NILFS_SUFILE_H #define _NILFS_SUFILE_H +#include <linux/errno.h> #include <linux/fs.h> #include <linux/buffer_head.h> #include "mdt.h" @@ -55,6 +56,33 @@ int nilfs_sufile_read(struct super_block *sb, size_t susize, int nilfs_sufile_trim_fs(struct inode *sufile, struct fstrim_range *range); /** + * nilfs_sufile_warn_on_error - warn on unexpected sufile error + * @sufile: inode of segment usage file + * @err: status code returned by a sufile function + * + * Even if buffer heads of blocks containing segment usage entries have + * been dirtied in advance by calling functions such as + * nilfs_sufile_mark_dirty() or nilfs_sufile_{alloc,free}(), those buffers + * can be discarded from memory after the file system detects corruption and + * degrades to read-only mode, which may cause sufile operations, including + * cancel operations, to return errors. nilfs_sufile_warn_on_error() is used + * to detect unexpected errors other than during read-only degradation. + * + * Return: 0 if @err is 0, %-EROFS if in read-only degraded mode, and %-EIO + * otherwise. + */ +#define nilfs_sufile_warn_on_error(sufile, err) \ + ({ \ + int _err = (err); \ + \ + if (unlikely(_err)) \ + _err = WARN_ONCE(!sb_rdonly((sufile)->i_sb), \ + "unexpected sufile error %d\n", _err) ? \ + -EIO : -EROFS; \ + _err; \ + }) + +/** * nilfs_sufile_scrap - make a segment garbage * @sufile: inode of segment usage file * @segnum: segment number to be freed diff --git a/fs/nilfs2/the_nilfs.c b/fs/nilfs2/the_nilfs.c index 7b23e373a106..f3805e7aabeb 100644 --- a/fs/nilfs2/the_nilfs.c +++ b/fs/nilfs2/the_nilfs.c @@ -461,6 +461,12 @@ static int nilfs_store_disk_layout(struct the_nilfs *nilfs, nilfs->ns_inode_size); return -EINVAL; } + if (NILFS_SR_BYTES(nilfs->ns_inode_size) > nilfs->ns_blocksize) { + nilfs_err(nilfs->ns_sb, + "too large inode size for super root: %d bytes", + nilfs->ns_inode_size); + return -EINVAL; + } nilfs->ns_first_ino = le32_to_cpu(sbp->s_first_ino); if (nilfs->ns_first_ino < NILFS_USER_INO) { |
