Compare commits

...

2 Commits

Author SHA1 Message Date
Ryusuke Konishi
48b71515fc sbin/cleanerd: cap segments per clean by available free segments
If the file system is low on free segments, the number of segments
requested for cleaning ('nsegs_per_step') may exceed the number of
actual free segments ('ncleansegs').

Cap 'nsegs_per_step' to 'ncleansegs' in nilfs_cleanerd_select_segments().
This serves as an entry-level guard to prevent invalid requests and
helps prevent filesystem operations from getting stuck when free space
is running out.

Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
2026-01-24 22:13:32 +09:00
Ryusuke Konishi
4a733f7ff0 cleanerd: cap segments per clean to prevent size_t overflow
In nilfs_xreclaim_segment(), the total number of reclaimable blocks is
calculated as:

    (blocks_per_segment * nsegs) - ...

On 32-bit systems, where 'size_t' is 32-bit, this multiplication can
theoretically overflow if the total number of blocks exceeds
'SIZE_MAX'.

To prevent this, calculate a safe maximum number of segments per
cleaning step ('max_nsegs_per_step') at initialization based on the
volume's layout information (blocks per segment).  Apply this cap
whenever the cleaning speed is configured, ensuring the total block
count always fits within 'size_t'.

Also, add a sanity check for zero 'blocks_per_segment' during
initialization.

Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
2026-01-24 12:59:59 +09:00

View File

@ -136,6 +136,8 @@ static const struct option long_option[] = {
* @no_timeout: the next timeout will be 0 seconds * @no_timeout: the next timeout will be 0 seconds
* @shutdown: shutdown request flag * @shutdown: shutdown request flag
* @nsegs_per_step: number of segments cleaned per cleaning step * @nsegs_per_step: number of segments cleaned per cleaning step
* @max_nsegs_per_step: upper limit of the number of segments cleaned per
* cleaning step
* @cleaning_interval: cleaning interval * @cleaning_interval: cleaning interval
* @target: target time for sleeping (monotonic time) * @target: target time for sleeping (monotonic time)
* @timeout: timeout value for sleeping * @timeout: timeout value for sleeping
@ -171,6 +173,7 @@ struct nilfs_cleanerd {
bool shutdown : 1; bool shutdown : 1;
unsigned int nsegs_per_step; unsigned int nsegs_per_step;
unsigned int max_nsegs_per_step;
struct timespec cleaning_interval; struct timespec cleaning_interval;
struct timespec target; struct timespec target;
struct timespec timeout; struct timespec timeout;
@ -259,6 +262,8 @@ skip_monotonic_clock:
syslog(LOG_DEBUG, "no_timeout: %d", cleanerd->no_timeout); syslog(LOG_DEBUG, "no_timeout: %d", cleanerd->no_timeout);
syslog(LOG_DEBUG, "shutdown: %d", cleanerd->shutdown); syslog(LOG_DEBUG, "shutdown: %d", cleanerd->shutdown);
syslog(LOG_DEBUG, "nsegs_per_step: %u", cleanerd->nsegs_per_step); syslog(LOG_DEBUG, "nsegs_per_step: %u", cleanerd->nsegs_per_step);
syslog(LOG_DEBUG, "max_nsegs_per_step: %u",
cleanerd->max_nsegs_per_step);
syslog(LOG_DEBUG, "cleaning_interval: %ld.%09ld", syslog(LOG_DEBUG, "cleaning_interval: %ld.%09ld",
cleanerd->cleaning_interval.tv_sec, cleanerd->cleaning_interval.tv_sec,
cleanerd->cleaning_interval.tv_nsec); cleanerd->cleaning_interval.tv_nsec);
@ -352,7 +357,9 @@ static int nilfs_cleanerd_reconfig(struct nilfs_cleanerd *cleanerd,
if (unlikely(ret < 0)) { if (unlikely(ret < 0)) {
syslog(LOG_ERR, "cannot configure: %m"); syslog(LOG_ERR, "cannot configure: %m");
} else { } else {
cleanerd->nsegs_per_step = config->cf_nsegments_per_clean; cleanerd->nsegs_per_step = min_t(
unsigned int, config->cf_nsegments_per_clean,
cleanerd->max_nsegs_per_step);
cleanerd->cleaning_interval = config->cf_cleaning_interval; cleanerd->cleaning_interval = config->cf_cleaning_interval;
cleanerd->min_reclaimable_blocks = cleanerd->min_reclaimable_blocks =
config->cf_min_reclaimable_blocks; config->cf_min_reclaimable_blocks;
@ -468,6 +475,7 @@ static struct nilfs_cleanerd *
nilfs_cleanerd_create(const char *dev, const char *dir, const char *conffile) nilfs_cleanerd_create(const char *dev, const char *dir, const char *conffile)
{ {
struct nilfs_cleanerd *cleanerd; struct nilfs_cleanerd *cleanerd;
uint32_t blocks_per_segment;
int ret; int ret;
cleanerd = malloc(sizeof(*cleanerd)); cleanerd = malloc(sizeof(*cleanerd));
@ -484,6 +492,17 @@ nilfs_cleanerd_create(const char *dev, const char *dir, const char *conffile)
goto out_cleanerd; goto out_cleanerd;
} }
blocks_per_segment = nilfs_get_blocks_per_segment(cleanerd->nilfs);
if (unlikely(blocks_per_segment == 0)) {
syslog(LOG_ERR, "%s: invalid segment block count: 0", dev);
goto out_nilfs;
}
/* Calculate limit on segments per GC step. */
cleanerd->max_nsegs_per_step = min_t(
size_t, SIZE_MAX / blocks_per_segment,
NILFS_CLDCONFIG_NSEGMENTS_PER_CLEAN_MAX);
cleanerd->cnormap = nilfs_cnormap_create(cleanerd->nilfs); cleanerd->cnormap = nilfs_cnormap_create(cleanerd->nilfs);
if (unlikely(cleanerd->cnormap == NULL)) { if (unlikely(cleanerd->cnormap == NULL)) {
syslog(LOG_ERR, syslog(LOG_ERR,
@ -667,6 +686,20 @@ nilfs_cleanerd_select_segments(struct nilfs_cleanerd *cleanerd,
int i; int i;
nsegs_per_step = nilfs_cleanerd_nsegs_per_step(cleanerd); nsegs_per_step = nilfs_cleanerd_nsegs_per_step(cleanerd);
if (nsegs_per_step > sustat->ss_ncleansegs) {
syslog(LOG_DEBUG,
"reducing segments per cleaning step from %u to %u "
"(ncleansegs)",
nsegs_per_step, (unsigned int)sustat->ss_ncleansegs);
nsegs_per_step = (unsigned int)sustat->ss_ncleansegs;
/*
* Even if nsegs_per_step becomes 0, fall through to update
* timestamps. This ensures nilfs_cleanerd_recalc_interval()
* sets a short retry interval.
*/
}
nilfs = cleanerd->nilfs; nilfs = cleanerd->nilfs;
smv = nilfs_vector_create(sizeof(struct nilfs_segimp)); smv = nilfs_vector_create(sizeof(struct nilfs_segimp));
@ -1161,7 +1194,9 @@ static int nilfs_cleanerd_cmd_run(struct nilfs_cleanerd *cleanerd,
req2->args.nsegments_per_clean > req2->args.nsegments_per_clean >
NILFS_CLDCONFIG_NSEGMENTS_PER_CLEAN_MAX) NILFS_CLDCONFIG_NSEGMENTS_PER_CLEAN_MAX)
goto error_inval; goto error_inval;
cleanerd->mm_nsegs_per_step = req2->args.nsegments_per_clean; cleanerd->mm_nsegs_per_step = min_t(
unsigned int, req2->args.nsegments_per_clean,
cleanerd->max_nsegs_per_step);
} else { } else {
cleanerd->mm_nsegs_per_step = cleanerd->nsegs_per_step; cleanerd->mm_nsegs_per_step = cleanerd->nsegs_per_step;
} }
@ -1685,7 +1720,9 @@ static int nilfs_cleanerd_clean_loop(struct nilfs_cleanerd *cleanerd)
if (unlikely(ret < 0)) if (unlikely(ret < 0))
return -1; return -1;
cleanerd->nsegs_per_step = cleanerd->config.cf_nsegments_per_clean; cleanerd->nsegs_per_step = min_t(
unsigned int, cleanerd->config.cf_nsegments_per_clean,
cleanerd->max_nsegs_per_step);
cleanerd->cleaning_interval = cleanerd->config.cf_cleaning_interval; cleanerd->cleaning_interval = cleanerd->config.cf_cleaning_interval;
cleanerd->min_reclaimable_blocks = cleanerd->min_reclaimable_blocks =
cleanerd->config.cf_min_reclaimable_blocks; cleanerd->config.cf_min_reclaimable_blocks;