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
This commit is contained in:
Linus Torvalds
2026-08-17 18:34:43 -07:00
18 changed files with 206 additions and 89 deletions

View File

@@ -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

View File

@@ -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);
}
/**

View File

@@ -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 *);

View File

@@ -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);

View File

@@ -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;

View File

@@ -169,17 +169,16 @@ static bool nilfs_check_folio(struct folio *folio, char *kaddr)
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 @@ struct nilfs_dir_entry *nilfs_dotdot(struct inode *dir, struct folio **foliop)
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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 *);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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 @@ static int nilfs_rename(struct mnt_idmap *idmap,
*/
static struct dentry *nilfs_get_parent(struct dentry *child)
{
ino_t ino;
u64 ino;
int res;
struct nilfs_root *root;

View File

@@ -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);

View File

@@ -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 @@ int nilfs_copy_dirty_pages(struct address_space *dmap,
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 @@ int nilfs_copy_dirty_pages(struct address_space *dmap,
folio_unlock(dfolio);
folio_put(dfolio);
unlock_folio:
folio_unlock(folio);
}
folio_batch_release(&fbatch);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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"
@@ -54,6 +55,33 @@ int nilfs_sufile_read(struct super_block *sb, size_t susize,
struct nilfs_inode *raw_inode, struct inode **inodep);
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

View File

@@ -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) {