/*
 * cleanerd.c - NILFS cleaner daemon.
 *
 * Copyright (C) 2007-2012 Nippon Telegraph and Telephone Corporation.
 *
 * Licensed under GPLv2: the complete text of the GNU General Public
 * License can be found in COPYING file of the nilfs-utils package.
 *
 * Credits:
 *    Koji Sato,
 *    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_UNISTD_H
#include <unistd.h>
#endif	/* HAVE_UNISTD_H */

#if HAVE_ERR_H
#include <err.h>
#endif	/* HAVE_ERR_H */

#if HAVE_STRING_H
#include <string.h>
#endif	/* HAVE_STRING_H */

#if HAVE_LIMITS_H
#include <limits.h>
#endif	/* HAVE_LIMITS_H */

#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif	/* HAVE_SYS_TYPES_H */

#if HAVE_FCNTL_H
#include <fcntl.h>
#endif	/* HAVE_FCNTL_H */

#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif	/* HAVE_SYS_STAT_H */

#if HAVE_SYS_TIME_H
#include <sys/time.h>
#endif	/* HAVE_SYS_TIME */

#if HAVE_TIME_H
#include <time.h>
#endif	/* HAVE_TIME_H */

#if HAVE_SYSLOG_H
#include <syslog.h>
#endif	/* HAVE_SYSLOG_H */

#if HAVE_MQUEUE_H
#include <mqueue.h>
#endif	/* HAVE_MQUEUE_H */

#if HAVE_POLL_H
#include <poll.h>
#endif	/* HAVE_POLL_H */

#include <errno.h>
#include <signal.h>
#include <inttypes.h>
#include <stdbool.h>
#include <setjmp.h>
#include <assert.h>
#include <uuid.h>

/* compat.h must be included before on-disk definitions for sparse checks */
#include "compat.h"
#include <linux/nilfs2_ondisk.h>  /* NILFS_MIN_NRSVSEGS */

#include "nilfs.h"
#include "util.h"
#include "vector.h"
#include "nilfs_gc.h"
#include "nilfs_cleaner.h"
#include "cleaner_msg.h"
#include "cldconfig.h"
#include "cnormap.h"
#include "realpath.h"


#ifndef SYSCONFDIR
#define SYSCONFDIR		"/etc"
#endif	/* SYSCONFDIR */
#define NILFS_CLEANERD_CONFFILE	SYSCONFDIR "/nilfs_cleanerd.conf"

#define NILFS_CLEANERD_NSUINFO	512
#define NILFS_CLEANERD_NULLTIME INT64_MAX

#ifdef _GNU_SOURCE
#include <getopt.h>
static const struct option long_option[] = {
	{"conffile", required_argument, NULL, 'c'},
	{"help", no_argument, NULL, 'h'},
	/* nofork option is obsolete. It does nothing even if passed */
	{"nofork", no_argument, NULL, 'n'},
	{"protection-period", required_argument, NULL, 'p'},
	{"version", no_argument, NULL, 'V'},
	{NULL, 0, NULL, 0}
};
#define NILFS_CLEANERD_OPTIONS	\
	"  -c, --conffile\tspecify configuration file\n"	\
	"  -h, --help    \tdisplay this help and exit\n"	\
	"  -p, --protection-period\tspecify protection period\n" \
	"  -V, --version \tprint version and exit\n"
#else	/* !_GNU_SOURCE */
#define NILFS_CLEANERD_OPTIONS	\
	"  -c            \tspecify configuration file\n"	\
	"  -h            \tdisplay this help and exit\n"	\
	"  -p            \tspecify protection period\n"		\
	"  -V            \tprint version and exit\n"
#endif	/* _GNU_SOURCE */

/**
 * struct nilfs_cleanerd - nilfs cleaner daemon
 * @nilfs: nilfs object
 * @cnormap: checkpoint number reverse mapper
 * @config: config structure
 * @conffile: configuration file name
 * @running: running state
 * @fallback: fallback state
 * @retry_cleaning: retrying reclamation for protected segments
 * @no_timeout: the next timeout will be 0 seconds
 * @shutdown: shutdown request flag
 * @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
 * @target: target time for sleeping (monotonic time)
 * @timeout: timeout value for sleeping
 * @min_reclaimable_blocks: min. number of reclaimable blocks
 * @prev_nongc_ctime: previous nongc ctime
 * @recvq: receive queue
 * @recvq_name: receive queue name
 * @sendq: send queue
 * @client_uuid: uuid of the previous message received from a client
 * @pending_cmd: pending client command
 * @jobid: current job id
 * @mm_prev_state: previous status during suspending
 * @mm_nrestpasses: remaining number of passes
 * @mm_nrestsegs: remaining number of segment (1-pass)
 * @mm_nsegs_per_step: number of segments cleaned per cleaning step
 *                     (manual mode)
 * @mm_protection_period: protection period (manual mode)
 * @mm_cleaning_interval: cleaning interval (manual mode)
 * @mm_min_reclaimable_blocks: min. number of reclaimable blocks (manual mode)
 */
struct nilfs_cleanerd {
	struct nilfs *nilfs;
	struct nilfs_cnormap *cnormap;
	struct nilfs_cldconfig config;
	char *conffile;

	int running;

	/* Boolean bitfields */
	bool fallback : 1;
	bool retry_cleaning : 1;
	bool no_timeout : 1;
	bool shutdown : 1;

	unsigned int nsegs_per_step;
	unsigned int max_nsegs_per_step;
	struct timespec cleaning_interval;
	struct timespec target;
	struct timespec timeout;
	uint32_t min_reclaimable_blocks;
	uint64_t prev_nongc_ctime;
	mqd_t recvq;
	char *recvq_name;
	mqd_t sendq;
	uuid_t client_uuid;
	unsigned long jobid;
	int mm_prev_state;
	int mm_nrestpasses;
	uint64_t mm_nrestsegs;
	unsigned int mm_nsegs_per_step;
	struct timespec mm_protection_period;
	struct timespec mm_cleaning_interval;
	uint32_t mm_min_reclaimable_blocks;
};

/**
 * struct nilfs_segimp - segment importance
 * @si_segnum: segment number
 * @si_importance: importance of segment
 */
struct nilfs_segimp {
	uint64_t si_segnum;
	long long si_importance;
};

/* command line option value */
static unsigned long protection_period;

/* global variables */
static struct nilfs_cleanerd *nilfs_cleanerd;
static sigjmp_buf nilfs_cleanerd_env; /* for siglongjmp */
static volatile sig_atomic_t nilfs_cleanerd_reload_config; /* reload flag */
static volatile sig_atomic_t nilfs_cleanerd_dump_req; /* dump request */
static char nilfs_cleanerd_msgbuf[NILFS_CLEANER_MSG_MAX_REQSZ];

static const char *nilfs_cleaner_cmd_name[] = {
	"get-status", "run", "suspend", "resume", "tune", "reload", "wait",
	"stop", "shutdown"
};

static void nilfs_cleanerd_version(void)
{
	printf("%s (%s %s)\n", getprogname(), PACKAGE, PACKAGE_VERSION);
}

static void nilfs_cleanerd_usage(FILE *stream)
{
	fprintf(stream,
		"Usage: %s [option]... dev\n"
		"%s options:\n"
		NILFS_CLEANERD_OPTIONS,
		getprogname(), getprogname());
}

static void nilfs_cleanerd_set_log_priority(struct nilfs_cleanerd *cleanerd)
{
	setlogmask(LOG_UPTO(cleanerd->config.cf_log_priority));
}

static void nilfs_cleanerd_dump(struct nilfs_cleanerd *cleanerd)
{
	struct timespec ts;
	int ret;

	syslog(LOG_DEBUG, "============== nilfs_cleanerd dump ==============");
	ret = clock_gettime(CLOCK_REALTIME, &ts);
	if (unlikely(ret < 0))
		goto skip_current_time;
	syslog(LOG_DEBUG, "current time: %ld.%09ld", ts.tv_sec, ts.tv_nsec);

skip_current_time:
	ret = clock_gettime(CLOCK_MONOTONIC, &ts);
	if (unlikely(ret < 0))
		goto skip_monotonic_clock;
	syslog(LOG_DEBUG, "monotonic clock: %ld.%09ld", ts.tv_sec, ts.tv_nsec);

skip_monotonic_clock:
	syslog(LOG_DEBUG, "---------------- cleanerd state -----------------");
	syslog(LOG_DEBUG, "running: %d", cleanerd->running);
	syslog(LOG_DEBUG, "fallback: %d", cleanerd->fallback);
	syslog(LOG_DEBUG, "retry_cleaning: %d", cleanerd->retry_cleaning);
	syslog(LOG_DEBUG, "no_timeout: %d", cleanerd->no_timeout);
	syslog(LOG_DEBUG, "shutdown: %d", cleanerd->shutdown);
	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",
	       cleanerd->cleaning_interval.tv_sec,
	       cleanerd->cleaning_interval.tv_nsec);
	syslog(LOG_DEBUG, "target: %ld.%09ld",
	       cleanerd->target.tv_sec, cleanerd->target.tv_nsec);
	syslog(LOG_DEBUG, "timeout: %ld.%09ld",
	       cleanerd->timeout.tv_sec, cleanerd->timeout.tv_nsec);
	syslog(LOG_DEBUG, "min_reclaimable_blocks: %" PRIu32,
	       cleanerd->min_reclaimable_blocks);
	syslog(LOG_DEBUG, "prev_nongc_ctime: %" PRIu64,
	       cleanerd->prev_nongc_ctime);
	syslog(LOG_DEBUG, "mm_prev_state: %d", cleanerd->mm_prev_state);
	syslog(LOG_DEBUG, "mm_nrestpasses: %d", cleanerd->mm_nrestpasses);
	syslog(LOG_DEBUG, "mm_nrestsegs: %" PRIu64, cleanerd->mm_nrestsegs);
	syslog(LOG_DEBUG, "mm_nsegs_per_step: %u",
	       cleanerd->mm_nsegs_per_step);
	syslog(LOG_DEBUG, "mm_protection_period: %ld.%09ld",
	       cleanerd->mm_protection_period.tv_sec,
	       cleanerd->mm_protection_period.tv_nsec);
	syslog(LOG_DEBUG, "mm_cleaning_interval: %ld.%09ld",
	       cleanerd->mm_cleaning_interval.tv_sec,
	       cleanerd->mm_cleaning_interval.tv_nsec);
	syslog(LOG_DEBUG, "mm_min_reclaimable_blocks: %" PRIu32,
	       cleanerd->mm_min_reclaimable_blocks);
	syslog(LOG_DEBUG, "=================================================");
}

/**
 * nilfs_cleanerd_config() - load configuration file
 * @cleanerd: cleanerd object
 * @conffile: path to the configuration file (optional)
 *
 * nilfs_cleanerd_config() reads the configuration file specified by
 * @conffile and initializes the cleanerd configuration state.  If
 * @conffile is NULL, the default configuration file is used.
 *
 * Return: 0 on success, or -1 on failure.
 */
static int nilfs_cleanerd_config(struct nilfs_cleanerd *cleanerd,
				 const char *conffile)
{
	struct nilfs_cldconfig *config = &cleanerd->config;
	int ret;

	ret = nilfs_cldconfig_read(config, conffile ? : cleanerd->conffile,
				   cleanerd->nilfs);
	if (unlikely(ret < 0))
		return -1;

#ifdef HAVE_MMAP
	if (config->cf_use_mmap)
		nilfs_opt_set_mmap(cleanerd->nilfs);
	else
		nilfs_opt_clear_mmap(cleanerd->nilfs);
#endif	/* HAVE_MMAP */

	if (config->cf_use_set_suinfo)
		nilfs_opt_set_set_suinfo(cleanerd->nilfs);
	else
		nilfs_opt_clear_set_suinfo(cleanerd->nilfs);

	nilfs_cleanerd_set_log_priority(cleanerd);

	if (protection_period != ULONG_MAX) {
		syslog(LOG_INFO, "override protection period to %lu",
		       protection_period);
		config->cf_protection_period.tv_sec = protection_period;
		config->cf_protection_period.tv_nsec = 0;
	}
	return 0;
}

/**
 * nilfs_cleanerd_reconfig() - reload configuration file
 * @cleanerd: cleanerd object
 * @conffile: path to the configuration file (optional)
 *
 * nilfs_cleanerd_reconfig() reads the configuration file specified by
 * @conffile and reflects the new settings to the internal parameters
 * of @cleanerd.
 *
 * Return: 0 on success, or -1 on failure.
 */
static int nilfs_cleanerd_reconfig(struct nilfs_cleanerd *cleanerd,
				   const char *conffile)
{
	struct nilfs_cldconfig *config = &cleanerd->config;
	int ret;

	ret = nilfs_cleanerd_config(cleanerd, conffile);
	if (unlikely(ret < 0)) {
		syslog(LOG_ERR, "cannot configure: %m");
	} else {
		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->min_reclaimable_blocks =
				config->cf_min_reclaimable_blocks;
		syslog(LOG_INFO, "configuration file reloaded");
	}
	return ret;
}

static int nilfs_cleanerd_open_queue(struct nilfs_cleanerd *cleanerd,
				     const char *device)
{
	char nambuf[NAME_MAX - 4];
	struct stat stbuf;
	struct mq_attr attr = {
		.mq_maxmsg = 6,
		.mq_msgsize = NILFS_CLEANER_MSG_MAX_REQSZ
	};
	int ret;

	cleanerd->recvq = -1;
	cleanerd->sendq = -1;
	cleanerd->jobid = 0;
	uuid_clear(cleanerd->client_uuid);

	/* receive queue */
	ret = stat(device, &stbuf);
	if (unlikely(ret < 0))
		goto failed;

	if (S_ISBLK(stbuf.st_mode)) {
		ret = snprintf(nambuf, sizeof(nambuf),
			       "/nilfs-cleanerq-%llu",
			       (unsigned long long)stbuf.st_rdev);
	} else if (S_ISREG(stbuf.st_mode) || S_ISDIR(stbuf.st_mode)) {
		ret = snprintf(nambuf, sizeof(nambuf),
			       "/nilfs-cleanerq-%llu-%llu",
			       (unsigned long long)stbuf.st_dev,
			       (unsigned long long)stbuf.st_ino);
	} else {
		errno = EINVAL;
		goto failed;
	}

	assert(ret < sizeof(nambuf));

	cleanerd->recvq_name = strdup(nambuf);
	if (unlikely(!cleanerd->recvq_name))
		goto failed;

	cleanerd->recvq = mq_open(nambuf, O_RDONLY | O_CREAT | O_NONBLOCK,
				  0600, &attr);
	if (unlikely(cleanerd->recvq < 0)) {
		free(cleanerd->recvq_name);
		goto failed;
	}

	assert(ret < sizeof(nambuf));

	return 0;

failed:
	syslog(LOG_ERR, "cannot create mqueue on %s: %m", device);
	return -1;
}

static void nilfs_cleanerd_close_queue(struct nilfs_cleanerd *cleanerd)
{
	if (cleanerd->recvq >= 0) {
		mq_close(cleanerd->recvq);
		mq_unlink(cleanerd->recvq_name);
		free(cleanerd->recvq_name);
		cleanerd->recvq = -1;
		cleanerd->recvq_name = NULL;
	}
	if (cleanerd->sendq >= 0) {
		mq_close(cleanerd->sendq);
		cleanerd->sendq = -1;
	}
}

#ifndef PATH_MAX
#define PATH_MAX	8192
#endif	/* PATH_MAX */

static __attribute__((noinline)) char *get_canonical_path(const char *path)
{
	char buf[PATH_MAX + 2], *canonical = NULL;

	if (path && myrealpath(path, buf, sizeof(buf)))
		canonical = strdup(buf);
	return canonical;
}

/**
 * nilfs_cleanerd_create() - create cleanerd object
 * @dev: path to the block device
 * @dir: mount point directory (optional)
 * @conffile: path to the configuration file (optional)
 *
 * nilfs_cleanerd_create() allocates a new cleanerd object, opens a handle
 * for NILFS library operations on the device @dev and @dir, and
 * initializes internal parameters by loading the configuration from
 * @conffile.
 *
 * It also performs initialization tasks such as setting up a checkpoint
 * number reverse mapper and creating a POSIX message queue for accepting
 * control commands.
 *
 * Return: A pointer to the newly allocated cleanerd object on success,
 * or NULL on failure.
 */
static struct nilfs_cleanerd *
nilfs_cleanerd_create(const char *dev, const char *dir, const char *conffile)
{
	struct nilfs_cleanerd *cleanerd;
	uint32_t blocks_per_segment;
	int ret;

	cleanerd = malloc(sizeof(*cleanerd));
	if (unlikely(cleanerd == NULL))
		return NULL;

	memset(cleanerd, 0, sizeof(*cleanerd));

	cleanerd->nilfs = nilfs_open(dev, dir,
				       NILFS_OPEN_RAW | NILFS_OPEN_RDWR |
				       NILFS_OPEN_GCLK);
	if (unlikely(cleanerd->nilfs == NULL)) {
		syslog(LOG_ERR, "cannot open nilfs on %s: %m", dev);
		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);
	if (unlikely(cleanerd->cnormap == NULL)) {
		syslog(LOG_ERR,
		       "failed to create checkpoint number reverse mapper: %m");
		goto out_nilfs;
	}

	cleanerd->conffile = strdup(conffile ? : NILFS_CLEANERD_CONFFILE);
	if (unlikely(cleanerd->conffile == NULL))
		goto out_cnormap;

	ret = nilfs_cleanerd_config(cleanerd, NULL);
	if (unlikely(ret < 0))
		goto out_conffile;

	ret = nilfs_cleanerd_open_queue(cleanerd,
					nilfs_get_dev(cleanerd->nilfs));
	if (unlikely(ret < 0))
		goto out_conffile;

	/* success */
	return cleanerd;

	/* error */
out_conffile:
	free(cleanerd->conffile);
out_cnormap:
	nilfs_cnormap_destroy(cleanerd->cnormap);
out_nilfs:
	nilfs_close(cleanerd->nilfs);

out_cleanerd:
	free(cleanerd);
	return NULL;
}

static void nilfs_cleanerd_destroy(struct nilfs_cleanerd *cleanerd)
{
	nilfs_cleanerd_close_queue(cleanerd);
	free(cleanerd->conffile);
	nilfs_cnormap_destroy(cleanerd->cnormap);
	nilfs_close(cleanerd->nilfs);
	free(cleanerd);
}

static int nilfs_comp_segimp(const void *elem1, const void *elem2)
{
	const struct nilfs_segimp *segimp1 = elem1, *segimp2 = elem2;

	if (segimp1->si_importance < segimp2->si_importance)
		return -1;
	else if (segimp1->si_importance > segimp2->si_importance)
		return 1;

	return (segimp1->si_segnum < segimp2->si_segnum) ? -1 : 1;
}

static int nilfs_cleanerd_automatic_suspend(struct nilfs_cleanerd *cleanerd)
{
	return cleanerd->config.cf_min_clean_segments > 0;
}

static unsigned int
nilfs_cleanerd_nsegs_per_step(struct nilfs_cleanerd *cleanerd)
{
	return cleanerd->running == 2 ?
		cleanerd->mm_nsegs_per_step : cleanerd->nsegs_per_step;
}

static struct timespec *
nilfs_cleanerd_cleaning_interval(struct nilfs_cleanerd *cleanerd)
{
	return cleanerd->running == 2 ?
		&cleanerd->mm_cleaning_interval :
		&cleanerd->cleaning_interval;
}

static struct timespec *
nilfs_cleanerd_protection_period(struct nilfs_cleanerd *cleanerd)
{
	return cleanerd->running == 2 ?
		&cleanerd->mm_protection_period :
		&cleanerd->config.cf_protection_period;
}

static uint32_t
nilfs_cleanerd_min_reclaimable_blocks(struct nilfs_cleanerd *cleanerd)
{
	return cleanerd->running == 2 ?
		cleanerd->mm_min_reclaimable_blocks :
		cleanerd->min_reclaimable_blocks;
}

static void
nilfs_cleanerd_reduce_nsegs_per_step(struct nilfs_cleanerd *cleanerd)
{
	if (cleanerd->running == 2) {
		if (cleanerd->mm_nsegs_per_step > 1)
			cleanerd->mm_nsegs_per_step >>= 1;
	} else  {
		if (cleanerd->nsegs_per_step > 1)
			cleanerd->nsegs_per_step >>= 1;
	}
}

/**
 * nilfs_segments_still_reclaimable - examine if segments are still reclaimable
 * @nilfs: nilfs object
 * @segnumv: array of segment numbers
 * @nsegs: number of segment numbers stored in @segnumv
 * @protseq: lower limit of sequence numbers of protected segments
 */
static int nilfs_segments_still_reclaimable(struct nilfs *nilfs,
					    uint64_t *segnumv,
					    unsigned long nsegs,
					    uint64_t protseq)
{
	struct nilfs_suinfo si;
	int i, ret;

	for (i = 0; i < nsegs; i++) {
		if (nilfs_get_suinfo(nilfs, segnumv[i], &si, 1) == 1) {
			if (!nilfs_suinfo_reclaimable(&si))
				continue;
			if (nilfs_suinfo_empty(&si))
				return 1;  /* Found a scrapped segment */
		}

		ret = nilfs_segment_is_protected(nilfs, segnumv[i], protseq);
		if (ret > 0)
			return 1;
	}
	return 0;
}

/**
 * nilfs_shrink_protected_region - shrink region of protected segments
 * @nilfs: nilfs object
 *
 * This tries to update log cursor written in superblocks to make
 * protected segments reclaimable.
 */
static int nilfs_shrink_protected_region(struct nilfs *nilfs)
{
	nilfs_cno_t cno;
	int ret = -1;

	nilfs_sync(nilfs, &cno);

	if (nilfs_freeze(nilfs) == 0 && nilfs_thaw(nilfs) == 0)
		ret = 0;

	return ret;
}

/**
 * nilfs_cleanerd_select_segments - select segments to be reclaimed
 * @cleanerd: cleanerd object
 * @sustat: status information on segments
 * @segnums: array of segment numbers to store selected segments
 * @prottimep: place to store lower limit of protected period
 * @oldestp: place to store the oldest mod-time
 */
static ssize_t
nilfs_cleanerd_select_segments(struct nilfs_cleanerd *cleanerd,
			       struct nilfs_sustat *sustat, uint64_t *segnums,
			       int64_t *prottimep, int64_t *oldestp)
{
	struct nilfs *nilfs;
	struct nilfs_vector *smv;
	struct nilfs_segimp *sm;
	struct nilfs_suinfo si[NILFS_CLEANERD_NSUINFO];
	struct timespec ts, ts2;
	int64_t prottime, oldest, lastmod, now;
	uint64_t segnum;
	size_t count;
	ssize_t nssegs, n;
	long long imp, thr;
	unsigned int nsegs_per_step;
	int ret;
	int i;

	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;

	smv = nilfs_vector_create(sizeof(struct nilfs_segimp));
	if (unlikely(!smv))
		return -1;

	/*
	 * The segments that were more recently written to disk than
	 * prottime are not selected.
	 */
	ret = clock_gettime(CLOCK_REALTIME, &ts);
	if (unlikely(ret < 0)) {
		nssegs = -1;
		goto out;
	}
	timespecsub(&ts, nilfs_cleanerd_protection_period(cleanerd), &ts2);
	now = ts.tv_sec;
	prottime = ts2.tv_sec;
	oldest = NILFS_CLEANERD_NULLTIME;

	/*
	 * The segments that have larger importance than thr are not
	 * selected.
	 */
	thr = sustat->ss_nongc_ctime;

	for (segnum = 0; segnum < sustat->ss_nsegs; segnum += n) {
		count = min_t(uint64_t, sustat->ss_nsegs - segnum,
			      NILFS_CLEANERD_NSUINFO);
		n = nilfs_get_suinfo(nilfs, segnum, si, count);
		if (unlikely(n < 0)) {
			nssegs = n;
			goto out;
		}
		for (i = 0; i < n; i++) {
			if (!nilfs_suinfo_reclaimable(&si[i]))
				continue;

			/*
			 * Use local variable 'lastmod' to treat the
			 * segment timestamp as a signed type value.
			 */
			lastmod = si[i].sui_lastmod;

			/*
			 * Timestamp policy.  The importance value is
			 * adjusted to include segments with a future
			 * timestamp.
			 */
			imp = lastmod <= now ? lastmod : thr - 1;

			if (imp < thr) {
				if (lastmod < oldest)
					oldest = lastmod;
				if (lastmod < prottime || lastmod > now) {
					sm = nilfs_vector_get_new_element(smv);
					if (unlikely(sm == NULL)) {
						nssegs = -1;
						goto out;
					}
					sm->si_segnum = segnum + i;
					sm->si_importance = imp;
				}
			}
		}
		if (unlikely(n == 0)) {
			syslog(LOG_WARNING,
			       "inconsistent number of segments: %zu (nsegs=%"
			       PRIu64 ")",
			       nilfs_vector_get_size(smv),
			       (uint64_t)sustat->ss_nsegs);
			break;
		}
	}
	nilfs_vector_sort(smv, nilfs_comp_segimp);

	nssegs = min_t(size_t, nilfs_vector_get_size(smv), nsegs_per_step);
	for (i = 0; i < nssegs; i++) {
		sm = nilfs_vector_get_element(smv, i);
		assert(sm != NULL);
		segnums[i] = sm->si_segnum;
	}
	*prottimep = prottime;
	*oldestp = oldest;

 out:
	nilfs_vector_destroy(smv);
	return nssegs;
}

static int oom_adjust(void)
{
	int fd, err;
	const char *path, *score;
	struct stat st;

	/* Avoid oom-killer */
	path = "/proc/self/oom_score_adj";
	score = "-1000\n";

	if (stat(path, &st)) {
		/* oom_score_adj cannot be used, try oom_adj */
		path = "/proc/self/oom_adj";
		score = "-17\n";
	}

	fd = open(path, O_WRONLY);
	if (unlikely(fd < 0)) {
		syslog(LOG_WARNING,
		       "can't adjust oom-killer's pardon %s: %m", path);
		return -1;
	}

	err = write(fd, score, strlen(score));
	if (unlikely(err < 0)) {
		syslog(LOG_WARNING,
		       "can't adjust oom-killer's pardon %s: %m", path);
		close(fd);
		return -1;
	}
	close(fd);
	return 0;
}

#define DEVNULL	"/dev/null"
#define ROOTDIR	"/"

static int daemonize(int nochdir, int noclose)
{
	pid_t pid;

	pid = fork();
	if (unlikely(pid < 0))
		return -1;
	else if (pid != 0)
		/* parent */
		_exit(EXIT_SUCCESS);

	/* child */
	if (unlikely(setsid() < 0))
		return -1;

	/* umask(0); */

	/* for ensuring I'm not a session leader */
	pid = fork();
	if (unlikely(pid < 0))
		return -1;
	else if (pid != 0)
		/* parent */
		_exit(EXIT_SUCCESS);

	if (unlikely(!nochdir && chdir(ROOTDIR) < 0))
		return -1;

	printf("NILFS_CLEANERD_PID=%lu\n", (unsigned long)getpid());
	fflush(stdout);

	if (!noclose) {
		close(0);
		close(1);
		close(2);
		if (unlikely(open(DEVNULL, O_RDONLY) < 0))
			return -1;
		if (unlikely(open(DEVNULL, O_WRONLY) < 0))
			return -1;
		if (unlikely(open(DEVNULL, O_WRONLY) < 0))
			return -1;
	}
	return 0;
}

static void handle_sigterm(int signum)
{
	siglongjmp(nilfs_cleanerd_env, 1);
}

static void handle_sighup(int signum)
{
	nilfs_cleanerd_reload_config = 1;
}

static void handle_sigusr1(int signum)
{
	nilfs_cleanerd_dump_req = 1;
}

static int set_signal_handler(int signum, void (*handler)(int))
{
	struct sigaction act;

	memset(&act, 0, sizeof(act));
	act.sa_handler = handler;
	sigfillset(&act.sa_mask);
	return sigaction(signum, &act, NULL);
}

static int ignore_signal(int signum)
{
	struct sigaction act;

	memset(&act, 0, sizeof(act));
	act.sa_handler = SIG_IGN;
	sigfillset(&act.sa_mask);
	return sigaction(signum, &act, NULL);
}

static int nilfs_cleanerd_init_signal_handlers(struct nilfs_cleanerd *cleanerd,
					       sigset_t *sigmask)
{
	int ret;

	ret = set_signal_handler(SIGTERM, handle_sigterm);
	if (unlikely(ret < 0)) {
		syslog(LOG_ERR, "cannot set SIGTERM signal handler: %m");
		return -1;
	}

	ret = set_signal_handler(SIGINT, handle_sigterm);
	if (unlikely(ret < 0)) {
		syslog(LOG_ERR, "cannot set SIGINT signal handler: %m");
		return -1;
	}

	ret = set_signal_handler(SIGHUP, handle_sighup);
	if (unlikely(ret < 0)) {
		syslog(LOG_ERR, "cannot set SIGHUP signal handler: %m");
		return -1;
	}

	ret = set_signal_handler(SIGUSR1, handle_sigusr1);
	if (unlikely(ret < 0)) {
		syslog(LOG_ERR, "cannot set SIGUSR1 signal handler: %m");
		return -1;
	}

	ret = ignore_signal(SIGUSR2); /* Reserved for future use */
	if (unlikely(ret < 0)) {
		syslog(LOG_ERR, "cannot ignore SIGUSR2 signal: %m");
		return -1;
	}

	sigaddset(sigmask, SIGHUP);
	sigaddset(sigmask, SIGUSR1);
	return 0;
}

static void nilfs_cleanerd_handle_signals(struct nilfs_cleanerd *cleanerd)
{
	if (nilfs_cleanerd_reload_config) {
		nilfs_cleanerd_reconfig(cleanerd, NULL);
		nilfs_cleanerd_reload_config = 0;
	}

	if (nilfs_cleanerd_dump_req) {
		if (cleanerd->config.cf_log_priority == LOG_DEBUG)
			nilfs_cleanerd_dump(cleanerd);
		nilfs_cleanerd_dump_req = 0;
	}
}

static void nilfs_cleanerd_clean_check_pause(struct nilfs_cleanerd *cleanerd)
{
	cleanerd->running = 0;
	cleanerd->timeout = cleanerd->config.cf_clean_check_interval;
	syslog(LOG_INFO, "pause (clean check)");
}

static void nilfs_cleanerd_clean_check_resume(struct nilfs_cleanerd *cleanerd)
{
	cleanerd->running = 1;
	syslog(LOG_INFO, "resume (clean check)");
}

static void nilfs_cleanerd_manual_suspend(struct nilfs_cleanerd *cleanerd)
{
	cleanerd->mm_prev_state = cleanerd->running;
	cleanerd->running = -1;
	cleanerd->timeout = cleanerd->config.cf_clean_check_interval;
	syslog(LOG_INFO, "suspend (manual)");
}

static void nilfs_cleanerd_manual_resume(struct nilfs_cleanerd *cleanerd)
{
	/* cleanerd->running == -1 */
	cleanerd->mm_prev_state = 0;
	cleanerd->running = cleanerd->mm_prev_state;
	syslog(LOG_INFO, "resume (manual)");
}

static void nilfs_cleanerd_manual_run(struct nilfs_cleanerd *cleanerd)
{
	cleanerd->running = 2;
	syslog(LOG_INFO, "run (manual)");
}

static void nilfs_cleanerd_manual_end(struct nilfs_cleanerd *cleanerd)
{
	cleanerd->running = 0;
	cleanerd->timeout = cleanerd->config.cf_clean_check_interval;
	syslog(LOG_INFO, "manual run completed");
}

static void nilfs_cleanerd_manual_stop(struct nilfs_cleanerd *cleanerd)
{
	cleanerd->running = 0;
	cleanerd->timeout = cleanerd->config.cf_clean_check_interval;
	syslog(LOG_INFO, "manual run aborted");
}

static int nilfs_cleanerd_init_interval(struct nilfs_cleanerd *cleanerd)
{
	int ret;

	ret = clock_gettime(CLOCK_MONOTONIC, &cleanerd->target);
	if (unlikely(ret < 0)) {
		syslog(LOG_ERR, "cannot get monotonic time: %m");
		return -1;
	}
	timespecadd(&cleanerd->target, &cleanerd->config.cf_cleaning_interval,
		    &cleanerd->target);
	return 0;
}

static int nilfs_cleanerd_recalc_interval(struct nilfs_cleanerd *cleanerd,
					  int nchosen, int ndone,
					  int64_t prottime, int64_t oldest)
{
	struct timespec curr, *interval;
	int ret;

	if (nchosen == 0 ||
	    (!cleanerd->fallback && ndone == 0 && !cleanerd->retry_cleaning)) {
		struct timespec pt;

		/* no segment were cleaned */
		if (cleanerd->running == 2) {
			/* Quit in the case of manual run */
			nilfs_cleanerd_manual_end(cleanerd);
			return 0;
		} else if (oldest == NILFS_CLEANERD_NULLTIME) {
			/* no reclaimable segments */
			if (cleanerd->running)
				cleanerd->running = 0;

			pt = *(nilfs_cleanerd_protection_period(cleanerd));
		} else {
			int64_t tgt;

			ret = clock_gettime(CLOCK_REALTIME, &curr);
			if (unlikely(ret < 0)) {
				syslog(LOG_ERR, "cannot get current time: %m");
				return -1;
			}
			tgt = min_t(int64_t, oldest, curr.tv_sec);
			pt.tv_sec = tgt > prottime ? tgt - prottime + 1 : 1;
			pt.tv_nsec = 0;
		}
		if (timespeccmp(&pt,
				&cleanerd->config.cf_clean_check_interval, <))
			cleanerd->timeout =
				cleanerd->config.cf_clean_check_interval;
		else
			cleanerd->timeout = pt;
		return 0;
	}

	ret = clock_gettime(CLOCK_MONOTONIC, &curr);
	if (unlikely(ret < 0)) {
		syslog(LOG_ERR, "cannot get monotonic time: %m");
		return -1;
	}

	if (cleanerd->fallback) {
		timespecadd(&curr, &cleanerd->config.cf_retry_interval,
			    &cleanerd->target);
		timespecsub(&cleanerd->target, &curr, &cleanerd->timeout);
		syslog(LOG_DEBUG, "retry interval");
		return 0;
	}

	interval = nilfs_cleanerd_cleaning_interval(cleanerd);
	/* timespeccmp() does not work for '>=' or '<='. */
	/* curr >= target */
	if (!timespeccmp(&curr, &cleanerd->target, <) || cleanerd->no_timeout) {
		timespecclear(&cleanerd->timeout);
		timespecadd(&curr, interval, &cleanerd->target);
		syslog(LOG_DEBUG, "adjust interval");
		return 0;
	}
	timespecsub(&cleanerd->target, &curr, &cleanerd->timeout);
	timespecadd(&cleanerd->target, interval, &cleanerd->target);
	return 0;
}

static int nilfs_cleanerd_respond(struct nilfs_cleanerd *cleanerd,
				  struct nilfs_cleaner_request *req,
				  const struct nilfs_cleaner_response *res)
{
	int ret;

	if (cleanerd->sendq < 0 ||
	    uuid_compare(cleanerd->client_uuid, req->client_uuid) != 0) {
		char nambuf[NAME_MAX - 4];
		char uuidbuf[36 + 1];

		uuid_unparse_lower(req->client_uuid, uuidbuf);
		ret = snprintf(nambuf, sizeof(nambuf),
			       "/nilfs-cleanerq-%s", uuidbuf);
		if (unlikely(ret < 0))
			goto out;

		if (cleanerd->sendq >= 0)
			mq_close(cleanerd->sendq);
		ret = -1;
		cleanerd->sendq = mq_open(nambuf, O_WRONLY | O_NONBLOCK);
		if (unlikely(cleanerd->sendq < 0)) {
			syslog(LOG_ERR, "cannot open queue to client: %m");
			goto out;
		}
		uuid_copy(cleanerd->client_uuid, req->client_uuid);
	}
	ret = mq_send(cleanerd->sendq, (char *)res, sizeof(*res),
		      NILFS_CLEANER_PRIO_HIGH);
	if (unlikely(ret < 0)) {
		syslog(LOG_ERR, "cannot respond to client: %m");
	} else {
		if (req->cmd >= 0 &&
		    req->cmd < ARRAY_SIZE(nilfs_cleaner_cmd_name))
			syslog(LOG_DEBUG, "command %s %s",
			       nilfs_cleaner_cmd_name[req->cmd],
			       res->result ? "nacked" : "acked");
	}
out:
	return ret;
}

static int nilfs_cleanerd_nak(struct nilfs_cleanerd *cleanerd,
			      struct nilfs_cleaner_request *req,
			      int errcode)
{
	struct nilfs_cleaner_response res = {
		.result = NILFS_CLEANER_RSP_NACK,  .err = errcode
	};
	return nilfs_cleanerd_respond(cleanerd, req, &res);
}

static int nilfs_cleanerd_cmd_getstat(struct nilfs_cleanerd *cleanerd,
				      struct nilfs_cleaner_request *req,
				      size_t argsize)
{
	struct nilfs_cleaner_response res = {0};

	if (cleanerd->running == 0)
		res.status = NILFS_CLEANER_STATUS_IDLE;
	else if (cleanerd->running > 0)
		res.status = NILFS_CLEANER_STATUS_RUNNING;
	else
		res.status = NILFS_CLEANER_STATUS_SUSPENDED;

	res.result = NILFS_CLEANER_RSP_ACK;
	return nilfs_cleanerd_respond(cleanerd, req, &res);
}

static int nilfs_cleanerd_cmd_run(struct nilfs_cleanerd *cleanerd,
				  struct nilfs_cleaner_request *req,
				  size_t argsize)
{
	struct nilfs_cleaner_request_with_args *req2;
	struct nilfs_cleaner_response res = {0};

	if (argsize < sizeof(req2->args))
		goto error_inval;

	req2 = (struct nilfs_cleaner_request_with_args *)req;

	/* protection period */
	if (req2->args.valid & NILFS_CLEANER_ARG_PROTECTION_PERIOD) {
		if (req2->args.protection_period > ULONG_MAX)
			goto error_inval;
		cleanerd->mm_protection_period.tv_sec =
			req2->args.protection_period;
		cleanerd->mm_protection_period.tv_nsec = 0;
	} else {
		cleanerd->mm_protection_period =
			cleanerd->config.cf_protection_period;
	}

	/* gc speed (nsegments per clean) */
	if (req2->args.valid & NILFS_CLEANER_ARG_NSEGMENTS_PER_CLEAN) {
		if (req2->args.nsegments_per_clean == 0 ||
		    req2->args.nsegments_per_clean >
		    NILFS_CLDCONFIG_NSEGMENTS_PER_CLEAN_MAX)
			goto error_inval;
		cleanerd->mm_nsegs_per_step = min_t(
			unsigned int, req2->args.nsegments_per_clean,
			cleanerd->max_nsegs_per_step);
	} else {
		cleanerd->mm_nsegs_per_step = cleanerd->nsegs_per_step;
	}
	/* gc speed (cleaning interval) */
	if (req2->args.valid & NILFS_CLEANER_ARG_CLEANING_INTERVAL) {
		cleanerd->mm_cleaning_interval.tv_sec =
			req2->args.cleaning_interval;
		cleanerd->mm_cleaning_interval.tv_nsec =
			req2->args.cleaning_interval_nsec;
	} else {
		cleanerd->mm_cleaning_interval =
			cleanerd->cleaning_interval;
	}
	/* minimal reclaimable blocks */
	if (req2->args.valid & NILFS_CLEANER_ARG_MIN_RECLAIMABLE_BLOCKS) {
		switch (req2->args.min_reclaimable_blocks_unit) {
		case NILFS_CLEANER_ARG_UNIT_NONE:
			if (req2->args.min_reclaimable_blocks >
				nilfs_get_blocks_per_segment(cleanerd->nilfs))
				goto error_inval;

			cleanerd->mm_min_reclaimable_blocks =
				req2->args.min_reclaimable_blocks;
			break;
		case NILFS_CLEANER_ARG_UNIT_PERCENT:
			if (req2->args.min_reclaimable_blocks > 100)
				goto error_inval;

			cleanerd->mm_min_reclaimable_blocks =
				(req2->args.min_reclaimable_blocks *
				nilfs_get_blocks_per_segment(cleanerd->nilfs) +
				99) / 100;
			break;
		default:
			goto error_inval;
		}
	} else {
		cleanerd->mm_min_reclaimable_blocks =
			cleanerd->min_reclaimable_blocks;
	}
	/* number of passes */
	if (req2->args.valid & NILFS_CLEANER_ARG_NPASSES) {
		if (!req2->args.npasses)
			goto error_inval;
		cleanerd->mm_nrestpasses = req2->args.npasses;
		cleanerd->mm_nrestsegs = 0;
	} else {
		cleanerd->mm_nrestpasses = 1;
		cleanerd->mm_nrestsegs = 0;
	}
	nilfs_cleanerd_manual_run(cleanerd);
	res.jobid = ++cleanerd->jobid;
	res.result = NILFS_CLEANER_RSP_ACK;
out:
	return nilfs_cleanerd_respond(cleanerd, req, &res);

error_inval:
	res.result = NILFS_CLEANER_RSP_NACK;
	res.err = EINVAL;
	goto out;
}

static int nilfs_cleanerd_cmd_suspend(struct nilfs_cleanerd *cleanerd,
				      struct nilfs_cleaner_request *req,
				      size_t argsize)
{
	struct nilfs_cleaner_response res = {0};

	if (cleanerd->running != -1)
		nilfs_cleanerd_manual_suspend(cleanerd);
	res.result = NILFS_CLEANER_RSP_ACK;
	return nilfs_cleanerd_respond(cleanerd, req, &res);
}

static int nilfs_cleanerd_cmd_resume(struct nilfs_cleanerd *cleanerd,
				     struct nilfs_cleaner_request *req,
				     size_t argsize)
{
	struct nilfs_cleaner_response res = {0};

	if (cleanerd->running == -1)
		nilfs_cleanerd_manual_resume(cleanerd);
	res.result = NILFS_CLEANER_RSP_ACK;
	return nilfs_cleanerd_respond(cleanerd, req, &res);
}

static int nilfs_cleanerd_cmd_tune(struct nilfs_cleanerd *cleanerd,
				   struct nilfs_cleaner_request *req,
				   size_t argsize)
{
	/* Not yet implemented */
	return nilfs_cleanerd_nak(cleanerd, req, EOPNOTSUPP);
}

static int nilfs_cleanerd_cmd_reload(struct nilfs_cleanerd *cleanerd,
				     struct nilfs_cleaner_request *req,
				     size_t argsize)
{
	struct nilfs_cleaner_request_with_path *req2;
	struct nilfs_cleaner_response res = {0};
	char *conffile;

	req2 = (struct nilfs_cleaner_request_with_path *)req;
	if (argsize == 0 || (argsize == 1 && req2->pathname[0] == '\0')) {
		conffile = NULL;
	} else if (strnlen(req2->pathname, argsize) < argsize) {
		conffile = req2->pathname;
	} else  {
		res.result = NILFS_CLEANER_RSP_NACK;
		res.err = EINVAL;
		goto out_send;
	}
	if (nilfs_cleanerd_reconfig(cleanerd, conffile) < 0) {
		res.result = NILFS_CLEANER_RSP_NACK;
		res.err = errno;
	} else {
		res.result = NILFS_CLEANER_RSP_ACK;
	}
out_send:
	return nilfs_cleanerd_respond(cleanerd, req, &res);
}

static int nilfs_cleanerd_cmd_wait(struct nilfs_cleanerd *cleanerd,
				   struct nilfs_cleaner_request *req,
				   size_t argsize)
{
	/* Not yet implemented */
	return nilfs_cleanerd_nak(cleanerd, req, EOPNOTSUPP);
}

static int nilfs_cleanerd_cmd_stop(struct nilfs_cleanerd *cleanerd,
				   struct nilfs_cleaner_request *req,
				   size_t argsize)
{
	struct nilfs_cleaner_response res = {0};

	if (cleanerd->running == 2 || cleanerd->running == -1)
		nilfs_cleanerd_manual_stop(cleanerd);
	res.result = NILFS_CLEANER_RSP_ACK;
	return nilfs_cleanerd_respond(cleanerd, req, &res);
}

static int nilfs_cleanerd_cmd_shutdown(struct nilfs_cleanerd *cleanerd,
				       struct nilfs_cleaner_request *req,
				       size_t argsize)
{
	struct nilfs_cleaner_response res = {0};

	cleanerd->shutdown = true;
	res.result = NILFS_CLEANER_RSP_ACK;
	return nilfs_cleanerd_respond(cleanerd, req, &res);
}

static int nilfs_cleanerd_handle_message(struct nilfs_cleanerd *cleanerd,
					 void *msgbuf, size_t bytes)
{
	struct nilfs_cleaner_request *req = msgbuf;
	size_t argsize;
	int ret = -1;

	if (bytes < sizeof(*req) || bytes < sizeof(*req) + req->argsize) {
		syslog(LOG_NOTICE, "too short message");
		goto out;
	}
	argsize = bytes - sizeof(*req);

	if (req->cmd >= 0 && req->cmd < ARRAY_SIZE(nilfs_cleaner_cmd_name))
		syslog(LOG_DEBUG, "received %s command",
		       nilfs_cleaner_cmd_name[req->cmd]);

	switch (req->cmd) {
	case NILFS_CLEANER_CMD_GET_STATUS:
		ret = nilfs_cleanerd_cmd_getstat(cleanerd, req, argsize);
		break;
	case NILFS_CLEANER_CMD_RUN:
		ret = nilfs_cleanerd_cmd_run(cleanerd, req, argsize);
		break;
	case NILFS_CLEANER_CMD_SUSPEND:
		ret = nilfs_cleanerd_cmd_suspend(cleanerd, req, argsize);
		break;
	case NILFS_CLEANER_CMD_RESUME:
		ret = nilfs_cleanerd_cmd_resume(cleanerd, req, argsize);
		break;
	case NILFS_CLEANER_CMD_TUNE:
		ret = nilfs_cleanerd_cmd_tune(cleanerd, req, argsize);
		break;
	case NILFS_CLEANER_CMD_RELOAD:
		ret = nilfs_cleanerd_cmd_reload(cleanerd, req, argsize);
		break;
	case NILFS_CLEANER_CMD_WAIT:
		ret = nilfs_cleanerd_cmd_wait(cleanerd, req, argsize);
		break;
	case NILFS_CLEANER_CMD_STOP:
		ret = nilfs_cleanerd_cmd_stop(cleanerd, req, argsize);
		break;
	case NILFS_CLEANER_CMD_SHUTDOWN:
		ret = nilfs_cleanerd_cmd_shutdown(cleanerd, req, argsize);
		break;
	default:
		syslog(LOG_DEBUG, "received unknown command: %d", req->cmd);
		return nilfs_cleanerd_nak(cleanerd, req, EINVAL);
	}
out:
	return ret;
}

static int nilfs_cleanerd_wait(struct nilfs_cleanerd *cleanerd)
{
	struct pollfd pfd;
	ssize_t bytes;
	int ret;

	syslog(LOG_DEBUG, "wait %ld.%09ld",
	       cleanerd->timeout.tv_sec, cleanerd->timeout.tv_nsec);

	memset(&pfd, 0, sizeof(pfd));
	pfd.fd = cleanerd->recvq;
	pfd.events = POLLIN;

	ret = ppoll(&pfd, 1, &cleanerd->timeout, NULL);
	if (unlikely(ret < 0)) {
		if (errno == EINTR) {
			syslog(LOG_INFO, "wake up (interrupted)");
			goto out;
		}
		syslog(LOG_ERR, "ppoll failed: %m");
		return -1;
	}

	if (!(pfd.revents & POLLIN)) {
		syslog(LOG_DEBUG, "wake up (timed out)");
		goto out;
	}
	syslog(LOG_DEBUG, "wake up to handle message");

	bytes = mq_receive(cleanerd->recvq, nilfs_cleanerd_msgbuf,
			   sizeof(nilfs_cleanerd_msgbuf), NULL);
	if (unlikely(bytes < 0)) {
		if (errno == EINTR || errno == EAGAIN) {
			syslog(LOG_INFO, "mq_receive aborted: %s",
			       errno == EINTR ?
			       "interrupted" : "no message found");
		} else {
			syslog(LOG_ERR, "mq_receive failed: %m");
			return -1;
		}
	} else {
		nilfs_cleanerd_handle_message(cleanerd, nilfs_cleanerd_msgbuf,
					      bytes);
	}
out:
	return 0;
}

static uint64_t nilfs_get_reserved_segments(const struct nilfs *nilfs,
					    uint64_t nsegs)
{
	uint32_t ratio = nilfs_get_reserved_segments_ratio(nilfs);

	return max_t(uint64_t, (nsegs * ratio + 99) / 100, NILFS_MIN_NRSVSEGS);
}

static int nilfs_cleanerd_handle_clean_check(struct nilfs_cleanerd *cleanerd,
					     struct nilfs_sustat *sustat)
{
	struct nilfs_cldconfig *config = &cleanerd->config;
	uint64_t r_segments = nilfs_get_reserved_segments(cleanerd->nilfs,
						       sustat->ss_nsegs);

	if (cleanerd->running == 1) {
		/* running (automatic suspend mode) */
		if (sustat->ss_ncleansegs >
		    config->cf_max_clean_segments + r_segments) {
			nilfs_cleanerd_clean_check_pause(cleanerd);
			return 1; /* immediately sleep */
		}
	} else if (cleanerd->running == 0) {
		/* idle */
		if (sustat->ss_ncleansegs <
		    config->cf_min_clean_segments + r_segments)
			nilfs_cleanerd_clean_check_resume(cleanerd);
		else
			return 1; /* immediately sleep */
	}

	if (sustat->ss_ncleansegs <
	    config->cf_min_clean_segments + r_segments) {
		/* disk space is close to limit -- accelerate cleaning */
		cleanerd->nsegs_per_step = config->cf_mc_nsegments_per_clean;
		cleanerd->cleaning_interval = config->cf_mc_cleaning_interval;
		cleanerd->min_reclaimable_blocks =
				config->cf_mc_min_reclaimable_blocks;
	} else {
		/* continue to run */
		cleanerd->nsegs_per_step = config->cf_nsegments_per_clean;
		cleanerd->cleaning_interval = config->cf_cleaning_interval;
		cleanerd->min_reclaimable_blocks =
				config->cf_min_reclaimable_blocks;
	}

	return 0; /* do gc */
}

static int
nilfs_cleanerd_count_reclaimable_segments(struct nilfs_cleanerd *cleanerd,
					  struct nilfs_sustat *sustat,
					  uint64_t *count)
{
	struct nilfs_suinfo si[NILFS_CLEANERD_NSUINFO];
	uint64_t segnum, rest, nfound;

	segnum = 0;
	nfound = 0;
	rest = sustat->ss_nsegs;
	while (rest > 0 && segnum < sustat->ss_nsegs) {
		size_t nitems = min_t(uint64_t, rest, NILFS_CLEANERD_NSUINFO);
		ssize_t nsi, i;

		nsi = nilfs_get_suinfo(cleanerd->nilfs, segnum, si, nitems);
		if (unlikely(nsi < 0)) {
			syslog(LOG_ERR, "cannot get segment usage info: %m");
			return -1;
		}
		for (i = 0; i < nsi; i++, segnum++) {
			if (nilfs_suinfo_reclaimable(&si[i])) {
				nfound++;
				rest--;
			}
		}
	}
	*count = nfound;
	return 0;
}

static int nilfs_cleanerd_handle_manual_mode(struct nilfs_cleanerd *cleanerd,
					     struct nilfs_sustat *sustat)
{
	if (cleanerd->mm_nrestsegs == 0 && cleanerd->mm_nrestpasses > 0) {
		uint64_t count;
		int ret;

		ret = nilfs_cleanerd_count_reclaimable_segments(
			cleanerd, sustat, &count);
		if (unlikely(ret < 0)) {
			cleanerd->mm_nrestpasses = 0;
		} else {
			cleanerd->mm_nrestpasses--;
			cleanerd->mm_nrestsegs = count;
		}
	}
	if (cleanerd->mm_nrestpasses == 0 && cleanerd->mm_nrestsegs == 0)
		nilfs_cleanerd_manual_end(cleanerd);

	return 0;
}

static int nilfs_cleanerd_check_state(struct nilfs_cleanerd *cleanerd,
				      struct nilfs_sustat *sustat)
{
	int ret;

	if (cleanerd->running < 0)
		return 1; /* suspending -> sleep */

	if (cleanerd->running == 2)
		nilfs_cleanerd_handle_manual_mode(cleanerd, sustat);

	if (cleanerd->running < 2 &&
	    nilfs_cleanerd_automatic_suspend(cleanerd)) {
		ret = nilfs_cleanerd_handle_clean_check(cleanerd, sustat);
		if (ret)
			return 1; /* pausing (clean check) -> sleep */
	}

	if (cleanerd->running == 0) {
		/* idle */
		if (sustat->ss_nongc_ctime != cleanerd->prev_nongc_ctime) {
			cleanerd->running = 1;
			cleanerd->prev_nongc_ctime = sustat->ss_nongc_ctime;
		} else {
			return 1; /* fs not updated -> sleep */
		}
	}
	return 0; /* do gc */
}

static void nilfs_cleanerd_progress(struct nilfs_cleanerd *cleanerd, int nsegs)
{
	if (cleanerd->running == 2) {
		/* decrease remaining number of segments */
		cleanerd->mm_nrestsegs =
			(cleanerd->mm_nrestsegs >= nsegs ?
			 cleanerd->mm_nrestsegs - nsegs : 0);
	}
}

static int nilfs_cleanerd_clean_segments(struct nilfs_cleanerd *cleanerd,
					 uint64_t *segnums, size_t nsegs,
					 uint64_t protseq, size_t *ndone)
{
	struct nilfs_reclaim_params params;
	struct nilfs_reclaim_stat stat;
	struct timespec *pt;
	int ret, i, sumsegs;

	params.flags = NILFS_RECLAIM_PARAM_PROTSEQ |
		       NILFS_RECLAIM_PARAM_PROTCNO |
		       NILFS_RECLAIM_PARAM_MIN_RECLAIMABLE_BLKS;
	params.min_reclaimable_blks =
			nilfs_cleanerd_min_reclaimable_blocks(cleanerd);
	params.protseq = protseq;

	pt = nilfs_cleanerd_protection_period(cleanerd);

	ret = nilfs_cnormap_track_back(cleanerd->cnormap, pt->tv_sec,
				       &params.protcno);
	if (unlikely(ret < 0)) {
		syslog(LOG_ERR,
		       "cannot get checkpoint number from protection period (%llu): %m",
		       (unsigned long long)pt->tv_sec);
		goto out;
	}
	syslog(LOG_DEBUG, "got cno %" PRIcno " from protection period %lu",
	       params.protcno, (unsigned long)pt->tv_sec);

	memset(&stat, 0, sizeof(stat));
	ret = nilfs_xreclaim_segment(cleanerd->nilfs, segnums, nsegs, 0,
				     &params, &stat);
	if (unlikely(ret < 0)) {
		if (errno == ENOMEM) {
			nilfs_cleanerd_reduce_nsegs_per_step(cleanerd);
			cleanerd->fallback = true;
			*ndone = 0;
			ret = 0;
		}
		goto out;
	}

	*ndone = 0;

	if (stat.cleaned_segs > 0) {
		for (i = 0; i < stat.cleaned_segs; i++)
			syslog(LOG_DEBUG, "segment %" PRIu64 " cleaned",
			       segnums[i]);

		nilfs_cleanerd_progress(cleanerd, stat.cleaned_segs);
		cleanerd->fallback = false;
		cleanerd->retry_cleaning = false;

		*ndone += stat.cleaned_segs;
	}

	if (stat.deferred_segs > 0) {
		sumsegs = stat.cleaned_segs + stat.deferred_segs;
		for (i = stat.cleaned_segs; i < sumsegs; i++)
			syslog(LOG_DEBUG, "segment %" PRIu64 " deferred",
			       segnums[i]);

		nilfs_cleanerd_progress(cleanerd, stat.deferred_segs);
		cleanerd->fallback = false;
		cleanerd->retry_cleaning = false;

		if (!stat.cleaned_segs)
			cleanerd->no_timeout = true;

		*ndone += stat.deferred_segs;
	}

	if (*ndone == 0) {
		syslog(LOG_DEBUG, "no segments cleaned");

		if (!cleanerd->retry_cleaning &&
		    nilfs_segments_still_reclaimable(
			    cleanerd->nilfs, segnums, nsegs, protseq) &&
		    nilfs_shrink_protected_region(cleanerd->nilfs) == 0) {

			syslog(LOG_DEBUG, "retrying protected region");
			cleanerd->retry_cleaning = true;
		} else {
			cleanerd->retry_cleaning = false;
		}
	}

out:
	return ret;
}

/**
 * nilfs_cleanerd_clean_loop - main loop of the cleaner daemon
 * @cleanerd: cleanerd object
 */
static int nilfs_cleanerd_clean_loop(struct nilfs_cleanerd *cleanerd)
{
	struct nilfs_sustat sustat;
	int64_t prottime = 0, oldest = 0;
	uint64_t segnums[NILFS_CLDCONFIG_NSEGMENTS_PER_CLEAN_MAX];
	sigset_t sigset;
	size_t ndone;
	int ns, ret;

	sigemptyset(&sigset);
	ret = sigprocmask(SIG_SETMASK, &sigset, NULL);
	if (unlikely(ret < 0)) {
		syslog(LOG_ERR, "cannot set signal mask: %m");
		return -1;
	}

	ret = nilfs_cleanerd_init_signal_handlers(cleanerd, &sigset);
	if (unlikely(ret < 0))
		return -1;

	nilfs_cleanerd_reload_config = 0;
	nilfs_cleanerd_dump_req = 0;

	cleanerd->running = 1;
	cleanerd->fallback = false;
	cleanerd->retry_cleaning = false;
	nilfs_gc_logger = syslog;

	ret = nilfs_cleanerd_init_interval(cleanerd);
	if (unlikely(ret < 0))
		return -1;

	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->min_reclaimable_blocks =
			cleanerd->config.cf_min_reclaimable_blocks;


	if (nilfs_cleanerd_automatic_suspend(cleanerd))
		nilfs_cleanerd_clean_check_pause(cleanerd);

	while (!cleanerd->shutdown) {
		cleanerd->no_timeout = false;

		ret = sigprocmask(SIG_BLOCK, &sigset, NULL);
		if (unlikely(ret < 0)) {
			syslog(LOG_ERR, "cannot set signal mask: %m");
			return -1;
		}

		nilfs_cleanerd_handle_signals(cleanerd);

		ret = nilfs_get_sustat(cleanerd->nilfs, &sustat);
		if (unlikely(ret < 0)) {
			syslog(LOG_ERR, "cannot get segment usage stat: %m");
			return -1;
		}

		if (nilfs_cleanerd_check_state(cleanerd, &sustat))
			goto sleep;

		/* starts garbage collection */
		syslog(LOG_DEBUG, "ncleansegs = %" PRIu64,
		       (uint64_t)sustat.ss_ncleansegs);

		ns = nilfs_cleanerd_select_segments(
			cleanerd, &sustat, segnums, &prottime, &oldest);
		if (unlikely(ns < 0)) {
			syslog(LOG_ERR, "cannot select segments: %m");
			return -1;
		}
		syslog(LOG_DEBUG, "%d segment%s selected to be cleaned",
		       ns, (ns <= 1) ? "" : "s");
		ndone = 0;
		if (ns > 0) {
			ret = nilfs_cleanerd_clean_segments(
				cleanerd, segnums, ns, sustat.ss_prot_seq,
				&ndone);
			if (unlikely(ret < 0))
				return -1;
		} else {
			cleanerd->retry_cleaning = false;
		}
		/* done */

		ret = nilfs_cleanerd_recalc_interval(
			cleanerd, ns, ndone, prottime, oldest);
		if (unlikely(ret < 0))
			return -1;

sleep:
		ret = sigprocmask(SIG_UNBLOCK, &sigset, NULL);
		if (unlikely(ret < 0)) {
			syslog(LOG_ERR, "cannot set signal mask: %m");
			return -1;
		}

		ret = nilfs_cleanerd_wait(cleanerd);
		if (unlikely(ret < 0))
			return -1;
	}
	return 0;
}

int main(int argc, char *argv[])
{
	char *conffile;
	char *dev, *dir;
	char *endptr;
	int status, c, ret;
#ifdef _GNU_SOURCE
	int option_index;
#endif	/* _GNU_SOURCE */

	conffile = NILFS_CLEANERD_CONFFILE;
	status = EXIT_SUCCESS;
	protection_period = ULONG_MAX;
	dev = NULL;
	dir = NULL;

#ifdef _GNU_SOURCE
	while ((c = getopt_long(argc, argv, "c:hnp:V",
				long_option, &option_index)) >= 0) {
#else	/* !_GNU_SOURCE */
	while ((c = getopt(argc, argv, "c:hnp:V")) >= 0) {
#endif	/* _GNU_SOURCE */

		switch (c) {
		case 'c':
			conffile = optarg;
			break;
		case 'h':
			nilfs_cleanerd_usage(stdout);
			exit(EXIT_SUCCESS);
		case 'n':
			/* ignore nofork option, do nothing */
			break;
		case 'p':
			protection_period = strtoul(optarg, &endptr, 10);
			if (endptr == optarg || *endptr != '\0') {
				errx(EXIT_FAILURE,
				     "invalid protection period: %s", optarg);
			} else if (protection_period == ULONG_MAX &&
				   errno == ERANGE) {
				errx(EXIT_FAILURE, "too large period: %s",
				     optarg);
			}
			break;
		case 'V':
			nilfs_cleanerd_version();
			exit(EXIT_SUCCESS);
		default:
			nilfs_cleanerd_usage(stderr);
			exit(EXIT_FAILURE);
		}
	}

	if (optind < argc) {
		const char *path = argv[optind++];

		dev = get_canonical_path(path);
		if (path && !dev)
			err(EXIT_FAILURE,
			    "failed to canonicalize device path %s", path);
	}

	if (optind < argc) {
		const char *path = argv[optind++];

		dir = get_canonical_path(path);
		if (unlikely(path && !dir)) {
			warn("failed to canonicalize directory path %s", path);
			status = EXIT_FAILURE;
			goto out_free;
		}
	}

	ret = daemonize(0, 0);
	if (unlikely(ret < 0)) {
		warn(NULL);
		status = EXIT_FAILURE;
		goto out_free;
	}

	openlog(getprogname(), LOG_PID, LOG_DAEMON);
	syslog(LOG_INFO, "start");

	ret = oom_adjust();
	if (unlikely(ret < 0))
		syslog(LOG_WARNING,
		       "adjusting the OOM killer failed: %m");

	nilfs_cleanerd = nilfs_cleanerd_create(dev, dir, conffile);
	if (unlikely(nilfs_cleanerd == NULL)) {
		syslog(LOG_ERR, "cannot create cleanerd on %s: %m", dev);
		status = EXIT_FAILURE;
		goto out_close_log;
	}

	if (!sigsetjmp(nilfs_cleanerd_env, 1)) {
		ret = nilfs_cleanerd_clean_loop(nilfs_cleanerd);
		if (unlikely(ret < 0))
			status = EXIT_FAILURE;
	}

	nilfs_cleanerd_destroy(nilfs_cleanerd);

out_close_log:
	syslog(LOG_INFO, "shutdown");
	closelog();

out_free:
	free(dir);	/* free(NULL) is just ignored */
	free(dev);

	exit(status);
}
