* [PATCH 1/5] nilfs2: treat missing sufile header block as metadata corruption
2024-08-21 15:46 [PATCH 0/5] nilfs2: prevent unexpected ENOENT propagation Ryusuke Konishi
@ 2024-08-21 15:46 ` Ryusuke Konishi
2024-08-21 15:46 ` [PATCH 2/5] nilfs2: treat missing cpfile " Ryusuke Konishi
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Ryusuke Konishi @ 2024-08-21 15:46 UTC (permalink / raw)
To: Andrew Morton; +Cc: linux-nilfs, linux-kernel
The sufile, a metadata file that holds metadata for segment management,
has statistical information in its first block, but if reading this
block fails, it receives the internal code -ENOENT and returns it
unchanged to the callers.
To prevent this -ENOENT from being propagated to system calls, if
reading the header block fails, return -EIO (or -EINVAL depending on
the context) instead.
Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
---
fs/nilfs2/sufile.c | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/fs/nilfs2/sufile.c b/fs/nilfs2/sufile.c
index 7bfc0860acee..f071eba48163 100644
--- a/fs/nilfs2/sufile.c
+++ b/fs/nilfs2/sufile.c
@@ -79,10 +79,17 @@ nilfs_sufile_block_get_segment_usage(const struct inode *sufile, __u64 segnum,
NILFS_MDT(sufile)->mi_entry_size;
}
-static inline int nilfs_sufile_get_header_block(struct inode *sufile,
- struct buffer_head **bhp)
+static int nilfs_sufile_get_header_block(struct inode *sufile,
+ struct buffer_head **bhp)
{
- return nilfs_mdt_get_block(sufile, 0, 0, NULL, bhp);
+ int err = nilfs_mdt_get_block(sufile, 0, 0, NULL, bhp);
+
+ if (unlikely(err == -ENOENT)) {
+ nilfs_error(sufile->i_sb,
+ "missing header block in segment usage metadata");
+ err = -EIO;
+ }
+ return err;
}
static inline int
@@ -1237,9 +1244,15 @@ int nilfs_sufile_read(struct super_block *sb, size_t susize,
if (err)
goto failed;
- err = nilfs_sufile_get_header_block(sufile, &header_bh);
- if (err)
+ err = nilfs_mdt_get_block(sufile, 0, 0, NULL, &header_bh);
+ if (unlikely(err)) {
+ if (err == -ENOENT) {
+ nilfs_err(sb,
+ "missing header block in segment usage metadata");
+ err = -EINVAL;
+ }
goto failed;
+ }
sui = NILFS_SUI(sufile);
kaddr = kmap_local_page(header_bh->b_page);
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 2/5] nilfs2: treat missing cpfile header block as metadata corruption
2024-08-21 15:46 [PATCH 0/5] nilfs2: prevent unexpected ENOENT propagation Ryusuke Konishi
2024-08-21 15:46 ` [PATCH 1/5] nilfs2: treat missing sufile header block as metadata corruption Ryusuke Konishi
@ 2024-08-21 15:46 ` Ryusuke Konishi
2024-08-21 15:46 ` [PATCH 3/5] nilfs2: do not propagate ENOENT error from sufile during recovery Ryusuke Konishi
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Ryusuke Konishi @ 2024-08-21 15:46 UTC (permalink / raw)
To: Andrew Morton; +Cc: linux-nilfs, linux-kernel
The cpfile, a metadata file that holds metadata for checkpoint management,
also has statistical information in its first block, and if reading this
block fails, it receives the internal code -ENOENT and returns that code
to the callers.
As with sufile, to prevent this -ENOENT from being propagated to system
calls, return -EIO instead when reading the header block fails.
Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
---
fs/nilfs2/cpfile.c | 22 ++++++++++++----------
1 file changed, 12 insertions(+), 10 deletions(-)
diff --git a/fs/nilfs2/cpfile.c b/fs/nilfs2/cpfile.c
index 9c8d531cffa7..f0ce37552446 100644
--- a/fs/nilfs2/cpfile.c
+++ b/fs/nilfs2/cpfile.c
@@ -125,10 +125,17 @@ static void nilfs_cpfile_block_init(struct inode *cpfile,
}
}
-static inline int nilfs_cpfile_get_header_block(struct inode *cpfile,
- struct buffer_head **bhp)
+static int nilfs_cpfile_get_header_block(struct inode *cpfile,
+ struct buffer_head **bhp)
{
- return nilfs_mdt_get_block(cpfile, 0, 0, NULL, bhp);
+ int err = nilfs_mdt_get_block(cpfile, 0, 0, NULL, bhp);
+
+ if (unlikely(err == -ENOENT)) {
+ nilfs_error(cpfile->i_sb,
+ "missing header block in checkpoint metadata");
+ err = -EIO;
+ }
+ return err;
}
static inline int nilfs_cpfile_get_checkpoint_block(struct inode *cpfile,
@@ -283,14 +290,9 @@ int nilfs_cpfile_create_checkpoint(struct inode *cpfile, __u64 cno)
down_write(&NILFS_MDT(cpfile)->mi_sem);
ret = nilfs_cpfile_get_header_block(cpfile, &header_bh);
- if (unlikely(ret < 0)) {
- if (ret == -ENOENT) {
- nilfs_error(cpfile->i_sb,
- "checkpoint creation failed due to metadata corruption.");
- ret = -EIO;
- }
+ if (unlikely(ret < 0))
goto out_sem;
- }
+
ret = nilfs_cpfile_get_checkpoint_block(cpfile, cno, 1, &cp_bh);
if (unlikely(ret < 0))
goto out_header;
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 3/5] nilfs2: do not propagate ENOENT error from sufile during recovery
2024-08-21 15:46 [PATCH 0/5] nilfs2: prevent unexpected ENOENT propagation Ryusuke Konishi
2024-08-21 15:46 ` [PATCH 1/5] nilfs2: treat missing sufile header block as metadata corruption Ryusuke Konishi
2024-08-21 15:46 ` [PATCH 2/5] nilfs2: treat missing cpfile " Ryusuke Konishi
@ 2024-08-21 15:46 ` Ryusuke Konishi
2024-08-21 15:46 ` [PATCH 4/5] nilfs2: do not propagate ENOENT error from sufile during GC Ryusuke Konishi
2024-08-21 15:46 ` [PATCH 5/5] nilfs2: do not propagate ENOENT error from nilfs_sufile_mark_dirty() Ryusuke Konishi
4 siblings, 0 replies; 6+ messages in thread
From: Ryusuke Konishi @ 2024-08-21 15:46 UTC (permalink / raw)
To: Andrew Morton; +Cc: linux-nilfs, linux-kernel
nilfs_sufile_free() returns the error code -ENOENT when the block
where the segment usage should be placed does not exist (hole block
case), but this error should not be propagated upwards to the mount
system call.
In nilfs_prepare_segment_for_recovery(), one of the recovery steps
during mount, nilfs_sufile_free() is used and may return -ENOENT as
is, so in that case return -EINVAL instead.
Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
---
fs/nilfs2/recovery.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/fs/nilfs2/recovery.c b/fs/nilfs2/recovery.c
index 61e25a980f73..cd364cd26a97 100644
--- a/fs/nilfs2/recovery.c
+++ b/fs/nilfs2/recovery.c
@@ -433,8 +433,17 @@ static int nilfs_prepare_segment_for_recovery(struct the_nilfs *nilfs,
* The next segment is invalidated by this recovery.
*/
err = nilfs_sufile_free(sufile, segnum[1]);
- if (unlikely(err))
+ if (unlikely(err)) {
+ if (err == -ENOENT) {
+ nilfs_err(sb,
+ "checkpoint log inconsistency at block %llu (segment %llu): next segment %llu is unallocated",
+ (unsigned long long)nilfs->ns_last_pseg,
+ (unsigned long long)nilfs->ns_segnum,
+ (unsigned long long)segnum[1]);
+ err = -EINVAL;
+ }
goto failed;
+ }
for (i = 1; i < 4; i++) {
err = nilfs_segment_list_add(head, segnum[i]);
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 4/5] nilfs2: do not propagate ENOENT error from sufile during GC
2024-08-21 15:46 [PATCH 0/5] nilfs2: prevent unexpected ENOENT propagation Ryusuke Konishi
` (2 preceding siblings ...)
2024-08-21 15:46 ` [PATCH 3/5] nilfs2: do not propagate ENOENT error from sufile during recovery Ryusuke Konishi
@ 2024-08-21 15:46 ` Ryusuke Konishi
2024-08-21 15:46 ` [PATCH 5/5] nilfs2: do not propagate ENOENT error from nilfs_sufile_mark_dirty() Ryusuke Konishi
4 siblings, 0 replies; 6+ messages in thread
From: Ryusuke Konishi @ 2024-08-21 15:46 UTC (permalink / raw)
To: Andrew Morton; +Cc: linux-nilfs, linux-kernel
nilfs_sufile_freev(), which is used to free segments in GC, aborts with
-ENOENT if the target segment usage is on a hole block.
This error only occurs if one of the segment numbers to be freed passed
by the GC ioctl is invalid, so return -EINVAL instead.
To avoid impairing readability, introduce a wrapper function that
encapsulates error handling including the error code conversion (and
error message output).
Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
---
fs/nilfs2/segment.c | 64 +++++++++++++++++++++++++++++++++++++++------
1 file changed, 56 insertions(+), 8 deletions(-)
diff --git a/fs/nilfs2/segment.c b/fs/nilfs2/segment.c
index 871ec35ea8e8..765d55333a13 100644
--- a/fs/nilfs2/segment.c
+++ b/fs/nilfs2/segment.c
@@ -1102,12 +1102,64 @@ static int nilfs_segctor_scan_file_dsync(struct nilfs_sc_info *sci,
return err;
}
+/**
+ * nilfs_free_segments - free the segments given by an array of segment numbers
+ * @nilfs: nilfs object
+ * @segnumv: array of segment numbers to be freed
+ * @nsegs: number of segments to be freed in @segnumv
+ *
+ * nilfs_free_segments() wraps nilfs_sufile_freev() and
+ * nilfs_sufile_cancel_freev(), and edits the segment usage metadata file
+ * (sufile) to free all segments given by @segnumv and @nsegs at once. If
+ * it fails midway, it cancels the changes so that none of the segments are
+ * freed. If @nsegs is 0, this function does nothing.
+ *
+ * The freeing of segments is not finalized until the writing of a log with
+ * a super root block containing this sufile change is complete, and it can
+ * be canceled with nilfs_sufile_cancel_freev() until then.
+ *
+ * Return: 0 on success, or the following negative error code on failure.
+ * * %-EINVAL - Invalid segment number.
+ * * %-EIO - I/O error (including metadata corruption).
+ * * %-ENOMEM - Insufficient memory available.
+ */
+static int nilfs_free_segments(struct the_nilfs *nilfs, __u64 *segnumv,
+ size_t nsegs)
+{
+ size_t ndone;
+ int ret;
+
+ if (!nsegs)
+ return 0;
+
+ ret = nilfs_sufile_freev(nilfs->ns_sufile, segnumv, nsegs, &ndone);
+ if (unlikely(ret)) {
+ nilfs_sufile_cancel_freev(nilfs->ns_sufile, segnumv, ndone,
+ NULL);
+ /*
+ * If a segment usage of the segments to be freed is in a
+ * hole block, nilfs_sufile_freev() will return -ENOENT.
+ * In this case, -EINVAL should be returned to the caller
+ * since there is something wrong with the given segment
+ * number array. This error can only occur during GC, so
+ * there is no need to worry about it propagating to other
+ * callers (such as fsync).
+ */
+ if (ret == -ENOENT) {
+ nilfs_err(nilfs->ns_sb,
+ "The segment usage entry %llu to be freed is invalid (in a hole)",
+ (unsigned long long)segnumv[ndone]);
+ ret = -EINVAL;
+ }
+ }
+ return ret;
+}
+
static int nilfs_segctor_collect_blocks(struct nilfs_sc_info *sci, int mode)
{
struct the_nilfs *nilfs = sci->sc_super->s_fs_info;
struct list_head *head;
struct nilfs_inode_info *ii;
- size_t ndone;
int err = 0;
switch (nilfs_sc_cstage_get(sci)) {
@@ -1201,14 +1253,10 @@ static int nilfs_segctor_collect_blocks(struct nilfs_sc_info *sci, int mode)
nilfs_sc_cstage_inc(sci);
fallthrough;
case NILFS_ST_SUFILE:
- err = nilfs_sufile_freev(nilfs->ns_sufile, sci->sc_freesegs,
- sci->sc_nfreesegs, &ndone);
- if (unlikely(err)) {
- nilfs_sufile_cancel_freev(nilfs->ns_sufile,
- sci->sc_freesegs, ndone,
- NULL);
+ err = nilfs_free_segments(nilfs, sci->sc_freesegs,
+ sci->sc_nfreesegs);
+ if (unlikely(err))
break;
- }
sci->sc_stage.flags |= NILFS_CF_SUFREED;
err = nilfs_segctor_scan_file(sci, nilfs->ns_sufile,
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 5/5] nilfs2: do not propagate ENOENT error from nilfs_sufile_mark_dirty()
2024-08-21 15:46 [PATCH 0/5] nilfs2: prevent unexpected ENOENT propagation Ryusuke Konishi
` (3 preceding siblings ...)
2024-08-21 15:46 ` [PATCH 4/5] nilfs2: do not propagate ENOENT error from sufile during GC Ryusuke Konishi
@ 2024-08-21 15:46 ` Ryusuke Konishi
4 siblings, 0 replies; 6+ messages in thread
From: Ryusuke Konishi @ 2024-08-21 15:46 UTC (permalink / raw)
To: Andrew Morton; +Cc: linux-nilfs, linux-kernel
nilfs_sufile_mark_dirty(), which marks a block in the sufile metadata
file as dirty in preparation for log writing, returns -ENOENT to the
caller if the block containing the segment usage of the specified
segment is missing.
This internal code can propagate through the log writer to system
calls such as fsync. To prevent this, treat this case as a filesystem
error and return -EIO instead.
Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
---
fs/nilfs2/sufile.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/fs/nilfs2/sufile.c b/fs/nilfs2/sufile.c
index f071eba48163..eea5a6a12f7b 100644
--- a/fs/nilfs2/sufile.c
+++ b/fs/nilfs2/sufile.c
@@ -513,8 +513,15 @@ int nilfs_sufile_mark_dirty(struct inode *sufile, __u64 segnum)
down_write(&NILFS_MDT(sufile)->mi_sem);
ret = nilfs_sufile_get_segment_usage_block(sufile, segnum, 0, &bh);
- if (ret)
+ if (unlikely(ret)) {
+ if (ret == -ENOENT) {
+ nilfs_error(sufile->i_sb,
+ "segment usage for segment %llu is unreadable due to a hole block",
+ (unsigned long long)segnum);
+ ret = -EIO;
+ }
goto out_sem;
+ }
kaddr = kmap_local_page(bh->b_page);
su = nilfs_sufile_block_get_segment_usage(sufile, segnum, bh, kaddr);
--
2.34.1
^ permalink raw reply related [flat|nested] 6+ messages in thread