/*
 *  elvtune.c - I/O elevator tuner
 *
 *  Copyright (C) 2000 Andrea Arcangeli <andrea@suse.de> SuSE
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *  This file may be redistributed under the terms of the GNU General
 *  Public License, version 2.
 */

#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <getopt.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include "nls.h"
#include "blkdev.h"
#include "linux_version.h"

/* this has to match with the kernel structure */
/* current version for ac19 and 2.2.16 */
typedef struct blkelv_ioctl_arg_s {
	int queue_ID;
	int read_latency;
	int write_latency;
	int max_bomb_segments;
} blkelv_ioctl_arg_t;

static void
usage(void) {
	fprintf(stderr, "elvtune (%s)\n", PACKAGE_STRING);
	fprintf(stderr, _("usage:\n"));
	fprintf(stderr, "\telvtune [-r r_lat] [-w w_lat] [-b b_lat]"
		        " /dev/blkdev1 [/dev/blkdev2...]\n");
	fprintf(stderr, "\telvtune -h\n");
	fprintf(stderr, "\telvtune -v\n");
	fprintf(stderr, _("\tNOTE: elvtune only works with 2.4 kernels\n"));
	/* (ioctls exist in 2.2.16 - 2.5.57) */
}

static void
version(void) {
	fprintf(stderr, "elvtune (%s)\n", PACKAGE_STRING);
}

int
main(int argc, char * argv[]) {
	int read_value = 0xbeefbeef, write_value = 0xbeefbeef, bomb_value = 0xbeefbeef;
	int read_set, write_set, bomb_set, set;
	char * devname;
	int fd;
	blkelv_ioctl_arg_t elevator;

	read_set = write_set = bomb_set = set = 0;

	setlocale(LC_MESSAGES, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);

	for (;;) {
		int opt;

		opt = getopt(argc, argv, "r:w:b:hv");
		if (opt == -1)
			break;
		switch (opt) {
		case 'r':
			read_value = atoi(optarg);
			read_set = set = 1;
			break;
		case 'w':
			write_value = atoi(optarg);
			write_set = set = 1;
			break;
		case 'b':
			bomb_value = atoi(optarg);
			bomb_set = set = 1;
			break;

		case 'h':
			usage(), exit(0);
		case 'v':
			version(), exit(0);

		case '?':
		default:
		case ':':
			fprintf(stderr, _("parse error\n"));
			exit(1);
		}
	}

	if (optind >= argc)
		fprintf(stderr, _("missing blockdevice, use -h for help\n")), exit(1);

	while (optind < argc) {
		devname = argv[optind++];

		fd = open(devname, O_RDONLY|O_NONBLOCK);
		if (fd < 0) {
			perror("open");
			break;
		}

		/* mmj: If we get EINVAL it's not a 2.4 kernel, so warn about
		   that and exit. It should return ENOTTY however, so check for
		   that as well in case it gets corrected in the future */

		if (ioctl(fd, BLKELVGET, &elevator) < 0) {
			int errsv = errno;
			perror("ioctl get");
			if ((errsv == EINVAL || errsv == ENOTTY) &&
			    get_linux_version() >= KERNEL_VERSION(2,5,58)) {
				fprintf(stderr,
					_("\nelvtune is only useful on older "
					"kernels;\nfor 2.6 use IO scheduler "
					"sysfs tunables instead..\n"));
			}
			break;
		}

		if (set) {
			if (read_set)
				elevator.read_latency = read_value;
			if (write_set)
				elevator.write_latency = write_value;
			if (bomb_set)
				elevator.max_bomb_segments = bomb_value;

			if (ioctl(fd, BLKELVSET, &elevator) < 0) {
				perror("ioctl set");
				break;
			}
			if (ioctl(fd, BLKELVGET, &elevator) < 0) {
				perror("ioctl reget");
				break;
			}
		}

		printf("\n%s elevator ID\t\t%d\n", devname, elevator.queue_ID);
		printf("\tread_latency:\t\t%d\n", elevator.read_latency);
		printf("\twrite_latency:\t\t%d\n", elevator.write_latency);
		printf("\tmax_bomb_segments:\t%d\n\n", elevator.max_bomb_segments);

		if (close(fd) < 0) {
			perror("close");
			break;
		}
	}

	return 0;
}
