nilfs2: do not propagate ENOENT error from sufile during GC
authorRyusuke Konishi <konishi.ryusuke@gmail.com>
Wed, 21 Aug 2024 15:46:26 +0000 (00:46 +0900)
committerAndrew Morton <akpm@linux-foundation.org>
Mon, 2 Sep 2024 03:43:39 +0000 (20:43 -0700)
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).

Link: https://lkml.kernel.org/r/20240821154627.11848-5-konishi.ryusuke@gmail.com
Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
fs/nilfs2/segment.c

index 0ca3110d638689a0b6bc37211792fd0342550554..2a771e222d86b23ece817aaa733235d7781bb560 100644 (file)
@@ -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,