Commit 0b9aad46 authored by Ryusuke Konishi's avatar Ryusuke Konishi Committed by Andrew Morton
Browse files

nilfs2: do not propagate ENOENT error from sufile during GC

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: default avatarRyusuke Konishi <konishi.ryusuke@gmail.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
parent 5b527d38
Loading
Loading
Loading
Loading
+56 −8
Original line number Diff line number Diff line
@@ -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,