/* hdled (C)opyright 2007 Nico Golde <nico@ngolde.de>
 * License: MIT/X Consortium License
 * Last modified: Mi Feb 28 04:37:02 CET 2007
 *
 * switches on your CAPS-Lock led on the keyboard according
 * to disk activity.
 * You have to be root to use it
 * Depends: Linux kernel >=2.6.0
 *
 * gcc hled.c -o hled
 * hled <device> e.g. hled hda
 * * * */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <limits.h>
#include <linux/kd.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>

int fd = -1;
char leds;

void cleanup(char *reason, char *ptr){
	perror(reason);
	free(ptr);
	exit(EXIT_FAILURE);
}

void sighandler(int signum){
	if (-1 == (ioctl(fd, KDSETLED, leds))) {
		perror("ioctl");
		close(fd);
	}
	exit(EXIT_FAILURE);
}

int main(int argc, char **argv){
	FILE *diskstats = NULL;
	unsigned long long reads, writes, tmp_read, tmp_write;
	char *device = NULL;

	if(argc != 2){
		fprintf(stderr, "you have to specify a device as argument\n");
		return EXIT_FAILURE;
	}

	if(NULL == (device=malloc(_POSIX_PATH_MAX)))
		cleanup("malloc", device);

	if(-1 == (fd=open("/dev/console", O_NOCTTY)))
		cleanup("open", device);

	if (ioctl(fd, KDGETLED, &leds) == -1)
		 cleanup("ioctl", device);

	snprintf(device, _POSIX_PATH_MAX, "/sys/block/%s/stat", argv[1]);

	if(NULL == (diskstats = fopen(device, "r")))
		cleanup("fopen", device);

	if(2 != fscanf(diskstats,"%*d %*d %*s %llu %*u %*u %*u %llu", &reads, &writes))
		cleanup("fscanf", device);

	signal(SIGINT,  sighandler);
	signal(SIGTERM, sighandler);
	signal(SIGQUIT, sighandler);
	signal(SIGTSTP, sighandler);

	tmp_read = reads;
	tmp_write = writes;
	
	printf("To exit hit Control-C.\n");
	
	while(1){
		fclose(diskstats);
		if( NULL == (diskstats = fopen(device, "r"))) /* rewind and fseek doesn't seem to work in sysfs :( */
			cleanup("fopen", device);

		usleep(100);
		ioctl(fd, KDSETLED, leds);

		if(2 != fscanf(diskstats,"%*d %*d %*s %llu %*u %*u %*u %llu",&reads, &writes))
			cleanup("fscanf", device);

		if(reads > tmp_read || writes > tmp_write){
			if(-1 == ioctl(fd, KDSETLED, leds | LED_CAP))
				cleanup("ioctl", device);
		} else {
			if(-1 == ioctl(fd, KDSETLED, leds &~ LED_CAP ))
				cleanup("ioctl", device);
		}

		tmp_read = reads;
		tmp_write = writes;
	}

	fclose(diskstats);
	close(fd);

	free(device);
	return 0;
}
