/* -*- Mode: Linux-C -*-
 *
 * lfs/lfs_cleanerd.c
 * Copyright (c) 1997 Christopher R. Waterson
 *
 */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <linux/fs.h>
#include <linux/lfs_fs.h>
#include <linux/stat.h>


/*
 * Set to non-zero by `-v' command line parameter to print lots of
 * debugging info
 */
int verbose = 0;

/*
 * The maximum number of free blocks that a segment can have to be
 * cleaned. If a segment has more than nusedblocksmax blocks in use,
 * it will not be considered for cleaning.
 */
int nusedblocksmax = 800;

/*
 * The number of segments to clean
 */
int nsegments_to_clean = 1;

       
/*
 * Tells the user how to use this program
 */
static void
usage(char* bin)
{
	fprintf(stderr, "%s: [-n segments] [-x maxusedblk] [-v] mountpoint\n", bin);
	fprintf(stderr, "-n segments: specify # of segments to clean ("
		"default is %d)\n", nsegments_to_clean);
	fprintf(stderr, "-x maxusedblck: specify used-block threshold ("
		"defauls is %d)\n", nusedblocksmax);
	fprintf(stderr, "-v: verbose mode\n");
	fprintf(stderr, "mountpoint: the mountpoint of an LFS filesystem to clean\n");
}


/*
 * Compares two segments to order them for cleaning.
 */
static int
cleaning_policy(struct lfs_segment *left, struct lfs_segment *right)
{
	return left->s_used_blocks - right->s_used_blocks;
}


/*
 * Get the dirty segments. Returns the number of dirty segments
 * actually retrieved. The segments are sorted from emptiest to
 * fullest, the idea being that you'll want to clean the emptier
 * segments first.
 */
static int
get_dirty_segments(int fd, struct lfs_segment* segments, int nsegmentsmax)
{
	struct lfs_dirty_segment_info info;
	int nsegments;
	int error;
	int i;

	info.d_nsegments = nsegmentsmax;
	info.d_segments  = segments;

	if ((error = ioctl(fd, LFS_IOC_GETDIRTYSEGMENTS, &info)) < 0) {
		perror("lfs_get_dirty_segments");
		exit(1);
	}

	nsegments = info.d_nsegments;

	if (verbose) printf("Sorting %d dirty segments...\n", nsegments);
	qsort(segments, nsegments, sizeof(struct lfs_segment),
	      (__compar_fn_t) cleaning_policy);

	for (i = 0; i < nsegments; ++i) {
		if (verbose) {
			printf("Segment %d: %d used blocks, mtime %ld\n",
			       segments[i].s_segmentnr,
			       segments[i].s_used_blocks,
			       segments[i].s_mtime);
		}

		if (segments[i].s_used_blocks > nusedblocksmax) {
			if (verbose) {
				printf("This segment has more than %d blocks in use.\n",
				       nusedblocksmax);
			}

			/* Because we've sorted them in decreasing
                           order of free space, we know that any
                           following segments will only be fuller */

			/* XXX Is this a bogus assumption to make
                           wrt. different cleaning policies? */
			nsegments = i;
			break;
		}
	}

	return nsegments;
}


/*
 * Retrieves the inodes contained in a segment into the array pointed
 * to by `inodes'. Up to `ninodesmax' are retrieved. The return value
 * is the actual number of inodes retrieved. If an error occurs, the
 * program just bails.
 */
static int
get_segment_inodes(int fd, int segmentnr, ino_t *inodes, int ninodesmax)
{
	int error;
	struct lfs_segment_summary_info info;

	if (verbose) printf("Retrieving inodes for segment %d...", segmentnr);

	info.s_segmentnr = segmentnr;
	info.s_ninodes   = ninodesmax;
	info.s_inodes    = inodes;

	if (error = ioctl(fd, LFS_IOC_GETSEGMENTSUMMARY, &info)) {
		perror("ioctl LFS_IOC_GETSEGMENTSUMMARY failed");
		exit(1);
	}

	if (verbose) printf("%d inodes retrieved\n", info.s_ninodes);
	return info.s_ninodes;
}


/*
 * Sorts the inodes
 */
static int
inode_sorter(ino_t *left, ino_t *right)
{
	return *left - *right;
}


/*
 * Iterate through each segment, collecting the inodes that we'll need
 * to clean. The inodes are placed into the `inodes' array, which is
 * sorted from lowest inode to highest.
 */
static int
collect_inodes(int fd, struct lfs_segment* segments, int nsegments,
	       ino_t* inodes, int ninodesmax)
{
	int ninodes = 0;
	int i;

	for (i = 0; i < nsegments; ++i) {
		int ninode;

		ninode = get_segment_inodes(fd, segments[i].s_segmentnr,
					    &inodes[ninodes], ninodesmax);

		ninodes += ninode;
		ninodesmax -= ninode;

		if (ninodesmax < 0) {
			fprintf(stderr, "out of memory in collect_inodes\n");
			exit(1);
		}
	}

	if (verbose) printf("Sorting %d inodes...\n", ninodes);
	qsort(inodes, ninodes, sizeof(ino_t), (__compar_fn_t) inode_sorter);

	return ninodes;
}


/*
 * Clean each inode
 */
static void
clean_inodes(int fd, ino_t* inodes, int ninodes, lfs_segmentnr_t* segmentnrs, int nsegmentnrs)
{
	struct lfs_clean_inode_info info;
	int i;

	info.c_nsegmentnrs = nsegmentnrs;
	info.c_segmentnrs  = segmentnrs;

	for (i = 0; i < ninodes; ++i) {
		int error;

		/* We may have some duplicates... */
		if (i > 0 && inodes[i] == inodes[i-1])
			continue;

		if (verbose) printf("Cleaning inode %ld...", inodes[i]);

		info.c_inode = inodes[i];
		if (ioctl(fd, LFS_IOC_CLEANINODE, &info) == 0) {
			if (verbose) printf("done.\n");
			continue;
		}

		switch (errno) {
		case ENOENT:
			/* The inode has since been cleaned or removed */
			if (verbose) printf("deleted.\n");
			break;

		default:
			printf("\n");
			perror("ioctl LFS_IOC_CLEANINODE failed");
			exit(1);
		}
	}
}

/*
 * The main program body
 */
int
main(int argc, char* argv[])
{
	struct lfs_segment segments[LFS_CLEANER_MAX_SEGMENTS];
	lfs_segmentnr_t segmentnrs[LFS_CLEANER_MAX_SEGMENTS];
	int nsegments;

	ino_t inodes[LFS_MAX_INODES_PER_SEGMENT];
	int ninodes;

	/* Filesystem mountpoint */
	char fsmountpoint[128];

	int fd;

	strcpy(fsmountpoint, ".");

	/* Parse the command line */
	{
		char c;
		while ((c = getopt(argc, argv, "n:vx:?")) != -1) {
			switch (c) {
			case 'n':
				nsegments_to_clean = atoi(optarg);
				break;

			case 'v':
				verbose = 1;
				break;

			case 'x':
				nusedblocksmax = atoi(optarg);
				break;

			case '?':
				usage(argv[0]);
				exit(1);

			}
		}
	}

	strcpy(fsmountpoint, argv[argc - 1]);

	if (verbose) {
		printf("Cleaning %d segments in \"%s\"...\n",
		       nsegments_to_clean,
		       fsmountpoint);
	}

	/* Get a handle to the filesystem */
	if ((fd = open(fsmountpoint)) < 0) {
		char buf[64];
		sprintf(buf, "unable to open \"%s\"", fsmountpoint);
		perror(buf);
		exit(1);
	}

	/* Get the dirty segments and sort them */
	if (! (nsegments = get_dirty_segments(fd, segments, LFS_CLEANER_MAX_SEGMENTS))) {
		if (verbose) printf("No dirty segments.\n");
		return 0;
	}

	if (nsegments < nsegments_to_clean)
		nsegments_to_clean = nsegments;

	if (! nsegments_to_clean) {
		if (verbose) printf("No segments to clean.\n");
		return 0;
	}

	/* Collect and sort the inodes that remain in the segments
	   that are to be cleaned. */
	ninodes = collect_inodes(fd, segments, nsegments_to_clean, inodes,
				 LFS_MAX_INODES_PER_SEGMENT);

	/* Copy the segment numbers into an array for use during
	   cleaning */
	{
		int i;
		for (i = 0; i < nsegments_to_clean; ++i)
			segmentnrs[i] = segments[i].s_segmentnr;
	}

	clean_inodes(fd, inodes, ninodes, segmentnrs, nsegments_to_clean);

	/* Return the clean segments to the freelist */
	{
		struct lfs_free_segments_info info;
		info.f_nsegmentnrs = nsegments_to_clean;
		info.f_segmentnrs  = segmentnrs;

		if (ioctl(fd, LFS_IOC_FREESEGMENTS, &info) < 0) {
			int i;

			perror("lfs_free_segment");

			fprintf(stderr, "busy segment(s): ");
			for (i = 0; i < nsegments_to_clean; ++i) {
				if (segmentnrs[i] != 0)
					fprintf(stderr, "%d ", segmentnrs[i]);
			}
			fprintf(stderr, "\n");

			exit(1);
		}
	}

	return 0;
};


