nilfs-utils/sbin/nilfs-clean.c
Ryusuke Konishi ae0fc7c09b nilfs-clean: allow specifying filesystem node instead of device
Update nilfs-clean to accept a regular file or directory path on the
target filesystem in addition to a block device node.

This change is implemented within the private library libcleaner.
The functions nilfs_cleaner_launch() and nilfs_cleaner_open() in
lib/cleaner_ctl.c are modified to automatically resolve the backing
block device using the internal helper nilfs_lookup_device() if a
filesystem node is passed.

Signed-off-by: Ryusuke Konishi <konishi.ryusuke@gmail.com>
2026-01-21 20:53:35 +09:00

604 lines
14 KiB
C

/*
* nilfs-clean.c - run garbage collector for nilfs2 volume
*
* Licensed under GPLv2: the complete text of the GNU General Public
* License can be found in COPYING file of the nilfs-utils package.
*
* Copyright (C) 2011-2012 Nippon Telegraph and Telephone Corporation.
*
* Credits:
* Ryusuke Konishi <konishi.ryusuke@gmail.com>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdio.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif /* HAVE_STDLIB_H */
#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif /* HAVE_SYS_TYPES_H */
#if HAVE_LINUX_TYPES_H
#include <linux/types.h>
#endif /* HAVE_LINUX_TYPES_H */
#if HAVE_ERR_H
#include <err.h>
#endif /* HAVE_ERR_H */
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif /* HAVE_FCNTL_H */
#if HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */
#if HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif /* HAVE_SYS_IOCTL_H */
#if HAVE_LIMITS_H
#include <limits.h>
#endif /* HAVE_LIMITS_H */
#if HAVE_STRING_H
#include <string.h>
#endif /* HAVE_SYS_STRING_H */
#if HAVE_SYSLOG_H
#include <syslog.h> /* LOG_ERR, and so forth */
#endif /* HAVE_SYSLOG_H */
#if HAVE_TIME_H
#include <time.h> /* timespec, nanosleep() */
#endif /* HAVE_TIME_H */
#include <sys/stat.h>
#include <setjmp.h>
#include <assert.h>
#include <stdarg.h> /* va_start, va_end, vfprintf */
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include "nls.h"
#include "nilfs.h"
#include "compat.h" /* getprogname() */
#include "nilfs_cleaner.h"
#include "parser.h"
#include "util.h"
#ifdef _GNU_SOURCE
#include <getopt.h>
static const struct option long_option[] = {
{"break", no_argument, NULL, 'b'},
{"reload", optional_argument, NULL, 'c'},
{"help", no_argument, NULL, 'h'},
{"status", no_argument, NULL, 'l'},
{"protection-period", required_argument, NULL, 'p'},
{"quit", no_argument, NULL, 'q'},
{"resume", no_argument, NULL, 'r'},
{"stop", no_argument, NULL, 'b'},
{"suspend", no_argument, NULL, 's'},
{"speed", required_argument, NULL, 'S'},
{"min-reclaimable-blocks", required_argument, NULL, 'm'},
{"verbose", no_argument, NULL, 'v'},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0}
};
#define NILFS_CLEAN_USAGE \
"Usage: %s [options] [device|node]\n" \
" -b, --break,--stop\tstop running cleaner\n" \
" -c, --reload[=CONFFILE]\n" \
" \t\treload config\n" \
" -h, --help\t\tdisplay this help and exit\n" \
" -l, --status\t\tdisplay cleaner status\n" \
" -p, --protection-period=SECONDS\n" \
" \t\tspecify protection period\n" \
" -m, --min-reclaimable-blocks=COUNT[%%]\n" \
" \t\tset minimum number of reclaimable blocks\n" \
" \t\tbefore a segment can be cleaned\n" \
" -q, --quit\t\tshutdown cleaner\n" \
" -r, --resume\t\tresume cleaner\n" \
" -s, --suspend\t\tsuspend cleaner\n" \
" -S, --speed=COUNT[/SECONDS]\n" \
" \t\tset GC speed\n" \
" -v, --verbose\t\tverbose mode\n" \
" -V, --version\t\tdisplay version and exit\n"
#else
#define NILFS_CLEAN_USAGE \
"Usage: %s [-b] [-c [conffile]] [-h] [-l] [-m blocks]\n" \
" [-p protection-period] [-q] [-r] [-s] [-S gc-speed]\n" \
" [-v] [-V] [device|node]\n"
#endif /* _GNU_SOURCE */
enum {
NILFS_CLEAN_CMD_RUN,
NILFS_CLEAN_CMD_INFO,
NILFS_CLEAN_CMD_SUSPEND,
NILFS_CLEAN_CMD_RESUME,
NILFS_CLEAN_CMD_RELOAD,
NILFS_CLEAN_CMD_STOP,
NILFS_CLEAN_CMD_SHUTDOWN,
};
/* GC speed parameters */
#define NILFS_CLEAN_DEFAULT_GC_SPEED (1280ULL << 20) /* 1.28 GiB/s */
#define NILFS_CLEAN_MAX_SEGMENTS_PER_CALL 32
#define NILFS_CLEAN_MIN_SEGMENTS_PER_CALL 1
#define NILFS_CLEAN_DEFAULT_NSEGMENTS_PER_CALL 16
/* options */
static int show_version_only;
static int verbose;
static int clean_cmd = NILFS_CLEAN_CMD_RUN;
static const char *conffile;
static unsigned long protection_period = ULONG_MAX;
static unsigned int nsegments_per_clean = 0; /* to be adjusted */
static struct timespec cleaning_interval = { 0, 100000000 }; /* 100 msec */
static unsigned long min_reclaimable_blocks = ULONG_MAX;
static unsigned char min_reclaimable_blocks_unit = NILFS_CLEANER_ARG_UNIT_NONE;
static sigjmp_buf nilfs_clean_env;
static struct nilfs_cleaner *nilfs_cleaner;
/* cleaner parameter */
static void nilfs_clean_logger(int priority, const char *fmt, ...)
{
va_list args;
if ((verbose && priority > LOG_INFO) || priority >= LOG_INFO)
return;
va_start(args, fmt);
vwarnx(fmt, args);
va_end(args);
}
static void nilfs_clean_escape(int signum)
{
siglongjmp(nilfs_clean_env, 1);
}
/**
* nilfs_clean_adjust_speed - adjust the GC speed instructed to cleanerd
* @cleaner: nilfs_cleaner struct
*
* nilfs_clean_adjust_speed() attempts to adjust the GC speed based on
* filesystem layout information, etc. Even if adjustment is not possible due
* to overflow or other reasons, it will not fail and set a default value.
*/
static void nilfs_clean_adjust_speed(struct nilfs_cleaner *cleaner)
{
const char *device = nilfs_cleaner_device(cleaner);
struct nilfs *nilfs;
uint64_t blocks_per_segment;
uint64_t interval_ms, nbytes, nsegs;
size_t block_size;
nilfs = nilfs_open(device, NULL, NILFS_OPEN_RDONLY | NILFS_OPEN_RAW);
if (unlikely(!nilfs))
goto fallback;
block_size = nilfs_get_block_size(nilfs);
blocks_per_segment = nilfs_get_blocks_per_segment(nilfs);
nilfs_close(nilfs);
interval_ms = (cleaning_interval.tv_sec * 1000 +
cleaning_interval.tv_nsec / 1000000);
if (unlikely(!block_size || !blocks_per_segment || !interval_ms))
goto fallback;
if (unlikely(blocks_per_segment > UINT64_MAX / block_size))
goto fallback;
nbytes = blocks_per_segment * block_size;
/* = Number of bytes per segment */
if (unlikely(nbytes > UINT64_MAX / 1000))
goto fallback;
nbytes = (nbytes * 1000) / interval_ms;
/* = Number of bytes to clean per second */
if (unlikely(!nbytes))
goto fallback;
nsegs = NILFS_CLEAN_DEFAULT_GC_SPEED / nbytes;
/* = Number of segments to clean per call */
nsegments_per_clean = max_t(unsigned int,
min_t(uint64_t, nsegs,
NILFS_CLEAN_MAX_SEGMENTS_PER_CALL),
NILFS_CLEAN_MIN_SEGMENTS_PER_CALL);
return;
fallback:
errno = 0;
nsegments_per_clean = NILFS_CLEAN_DEFAULT_NSEGMENTS_PER_CALL;
}
static int nilfs_clean_do_run(struct nilfs_cleaner *cleaner)
{
struct nilfs_cleaner_args args;
int ret;
if (!nsegments_per_clean)
nilfs_clean_adjust_speed(cleaner);
args.npasses = 1;
args.nsegments_per_clean = nsegments_per_clean;
args.cleaning_interval = cleaning_interval.tv_sec;
args.cleaning_interval_nsec = cleaning_interval.tv_nsec;
args.valid = (NILFS_CLEANER_ARG_NPASSES |
NILFS_CLEANER_ARG_CLEANING_INTERVAL |
NILFS_CLEANER_ARG_NSEGMENTS_PER_CLEAN);
if (protection_period != ULONG_MAX) {
args.protection_period = protection_period;
args.valid |= NILFS_CLEANER_ARG_PROTECTION_PERIOD;
}
if (min_reclaimable_blocks != ULONG_MAX) {
args.min_reclaimable_blocks = min_reclaimable_blocks;
args.min_reclaimable_blocks_unit = min_reclaimable_blocks_unit;
args.valid |= NILFS_CLEANER_ARG_MIN_RECLAIMABLE_BLOCKS;
}
ret = nilfs_cleaner_run(cleaner, &args, NULL);
if (unlikely(ret < 0)) {
warn(_("cannot run cleaner"));
return -1;
}
return 0;
}
static int nilfs_clean_do_getinfo(struct nilfs_cleaner *cleaner)
{
int cleaner_status;
int ret;
ret = nilfs_cleaner_get_status(cleaner, &cleaner_status);
if (ret < 0) {
warn(_("cannot get cleaner status"));
return -1;
}
switch (cleaner_status) {
case NILFS_CLEANER_STATUS_IDLE:
puts(_("idle"));
break;
case NILFS_CLEANER_STATUS_RUNNING:
puts(_("running"));
break;
case NILFS_CLEANER_STATUS_SUSPENDED:
puts(_("suspended"));
break;
default:
printf(_("%d (unknown)\n"), cleaner_status);
}
return 0;
}
static int nilfs_clean_do_suspend(struct nilfs_cleaner *cleaner)
{
int ret;
ret = nilfs_cleaner_suspend(cleaner);
if (unlikely(ret < 0)) {
warn(_("suspend failed"));
return -1;
}
return 0;
}
static int nilfs_clean_do_resume(struct nilfs_cleaner *cleaner)
{
int ret;
ret = nilfs_cleaner_resume(cleaner);
if (unlikely(ret < 0)) {
warn(_("resume failed"));
return -1;
}
return 0;
}
static int nilfs_clean_do_reload(struct nilfs_cleaner *cleaner)
{
int ret;
ret = nilfs_cleaner_reload(cleaner, conffile);
if (unlikely(ret < 0)) {
warn(_("reload failed"));
return -1;
}
return 0;
}
static int nilfs_clean_do_stop(struct nilfs_cleaner *cleaner)
{
int ret;
ret = nilfs_cleaner_stop(cleaner);
if (unlikely(ret < 0)) {
warn(_("stop failed"));
return -1;
}
return 0;
}
static int nilfs_clean_do_shutdown(struct nilfs_cleaner *cleaner)
{
int ret;
ret = nilfs_cleaner_shutdown(cleaner);
if (unlikely(ret < 0)) {
warn(_("shutdown failed"));
return -1;
}
return 0;
}
static int nilfs_clean_request(struct nilfs_cleaner *cleaner)
{
int status = EXIT_FAILURE;
int ret;
switch (clean_cmd) {
case NILFS_CLEAN_CMD_RUN:
ret = nilfs_clean_do_run(cleaner);
break;
case NILFS_CLEAN_CMD_INFO:
ret = nilfs_clean_do_getinfo(cleaner);
break;
case NILFS_CLEAN_CMD_SUSPEND:
ret = nilfs_clean_do_suspend(cleaner);
break;
case NILFS_CLEAN_CMD_RESUME:
ret = nilfs_clean_do_resume(cleaner);
break;
case NILFS_CLEAN_CMD_RELOAD:
ret = nilfs_clean_do_reload(cleaner);
break;
case NILFS_CLEAN_CMD_STOP:
ret = nilfs_clean_do_stop(cleaner);
break;
case NILFS_CLEAN_CMD_SHUTDOWN:
ret = nilfs_clean_do_shutdown(cleaner);
break;
default:
goto out;
}
if (likely(!ret))
status = EXIT_SUCCESS;
out:
return status;
}
static int nilfs_do_clean(const char *device)
{
struct sigaction act, oldact[3];
int status = EXIT_FAILURE;
nilfs_cleaner = nilfs_cleaner_open(device, NULL,
NILFS_CLEANER_OPEN_QUEUE);
if (unlikely(!nilfs_cleaner))
goto out;
if (!sigsetjmp(nilfs_clean_env, 1)) {
memset(&act, 0, sizeof(act));
sigfillset(&act.sa_mask);
act.sa_handler = nilfs_clean_escape;
act.sa_flags = 0;
if (unlikely(sigaction(SIGINT, &act, &oldact[0]) < 0 ||
sigaction(SIGTERM, &act, &oldact[1]) < 0 ||
sigaction(SIGHUP, &act, &oldact[2]) < 0))
siglongjmp(nilfs_clean_env, 1);
status = nilfs_clean_request(nilfs_cleaner);
sigaction(SIGINT, &oldact[0], NULL);
sigaction(SIGTERM, &oldact[1], NULL);
sigaction(SIGHUP, &oldact[2], NULL);
}
nilfs_cleaner_close(nilfs_cleaner);
out:
return status;
}
static void nilfs_clean_usage(FILE *stream)
{
fprintf(stream, NILFS_CLEAN_USAGE, getprogname());
}
static int nilfs_clean_parse_gcspeed(const char *arg)
{
unsigned long nsegs;
unsigned long interval;
double interval2;
char *endptr, *endptr2;
nsegs = strtoul(arg, &endptr, 10);
if (endptr == arg || (endptr[0] != '/' && endptr[0] != '\0'))
goto failed;
if (nsegs == ULONG_MAX)
goto failed_too_large;
if (endptr[0] == '/') {
interval = strtoul(&endptr[1], &endptr2, 10);
if (endptr2 == &endptr[1])
goto failed;
if (endptr2[0] == '.') {
errno = 0;
interval2 = strtod(&endptr[1], &endptr2);
if (endptr2 == &endptr[1] || endptr2[0] != '\0')
goto failed;
if (errno == ERANGE)
goto failed_too_large;
cleaning_interval.tv_sec = interval;
cleaning_interval.tv_nsec =
(interval2 - interval) * 1000000000;
} else if (endptr2[0] != '\0') {
goto failed;
} else {
if (interval == ULONG_MAX)
goto failed_too_large;
cleaning_interval.tv_sec = interval;
cleaning_interval.tv_nsec = 0;
}
} else {
cleaning_interval.tv_sec = 1;
cleaning_interval.tv_nsec = 0;
}
nsegments_per_clean = nsegs;
return 0;
failed:
warnx(_("invalid gc speed: %s"), arg);
return -1;
failed_too_large:
warnx(_("value too large: %s"), arg);
return -1;
}
static int nilfs_clean_parse_min_reclaimable(const char *arg)
{
unsigned long blocks;
char *endptr;
blocks = strtoul(arg, &endptr, 10);
if (endptr == arg || (endptr[0] != '\0' && endptr[0] != '%')) {
warnx(_("invalid reclaimable blocks: %s"), arg);
return -1;
}
if (blocks == ULONG_MAX) {
warnx(_("value too large: %s"), arg);
return -1;
}
if (endptr[0] == '%') {
if (blocks > 100) {
warnx(_("percent value can't be > 100: %s"), arg);
return -1;
}
min_reclaimable_blocks_unit = NILFS_CLEANER_ARG_UNIT_PERCENT;
} else {
min_reclaimable_blocks_unit = NILFS_CLEANER_ARG_UNIT_NONE;
}
min_reclaimable_blocks = blocks;
return 0;
}
static void nilfs_clean_parse_options(int argc, char *argv[])
{
#ifdef _GNU_SOURCE
int option_index;
#endif /* _GNU_SOURCE */
int c, ret;
#ifdef _GNU_SOURCE
while ((c = getopt_long(argc, argv, "bc::hlm:p:qrsS:vV",
long_option, &option_index)) >= 0) {
#else
while ((c = getopt(argc, argv, "bc::hlm:p:qrsS:vV")) >= 0) {
#endif /* _GNU_SOURCE */
switch (c) {
case 'b':
clean_cmd = NILFS_CLEAN_CMD_STOP;
break;
case 'c':
if (optarg != NULL)
conffile = optarg;
clean_cmd = NILFS_CLEAN_CMD_RELOAD;
break;
case 'h':
nilfs_clean_usage(stdout);
exit(EXIT_SUCCESS);
break;
case 'l':
clean_cmd = NILFS_CLEAN_CMD_INFO;
break;
case 'm':
if (nilfs_clean_parse_min_reclaimable(optarg) < 0)
exit(EXIT_FAILURE);
break;
case 'p':
ret = nilfs_parse_protection_period(
optarg, &protection_period);
if (!ret)
break;
if (errno == ERANGE)
errx(EXIT_FAILURE,
_("too large period: %s"), optarg);
else
errx(EXIT_FAILURE,
_("invalid protection period: %s"),
optarg);
case 'q':
clean_cmd = NILFS_CLEAN_CMD_SHUTDOWN;
break;
case 'r':
clean_cmd = NILFS_CLEAN_CMD_RESUME;
break;
case 's':
clean_cmd = NILFS_CLEAN_CMD_SUSPEND;
break;
case 'S':
if (nilfs_clean_parse_gcspeed(optarg) < 0)
exit(EXIT_FAILURE);
break;
case 'v':
verbose = 1;
break;
case 'V':
show_version_only = 1;
break;
default:
nilfs_clean_usage(stderr);
exit(EXIT_FAILURE);
}
}
}
int main(int argc, char *argv[])
{
struct stat statbuf;
char *device = NULL;
int status;
nilfs_cleaner_logger = nilfs_clean_logger;
nilfs_clean_parse_options(argc, argv);
if (show_version_only) {
printf(_("%s version %s\n"), getprogname(), PACKAGE_VERSION);
status = EXIT_SUCCESS;
goto out;
}
if (optind < argc) {
device = argv[optind++];
if (stat(device, &statbuf) < 0)
err(EXIT_FAILURE, _("cannot find '%s'"), device);
}
if (optind < argc)
errx(EXIT_FAILURE, _("too many arguments."));
status = nilfs_do_clean(device);
out:
exit(status);
}