/*
 * hdapsd.c - Read from the HDAPS (HardDrive Active Protection System)
 *            and protect the drive if motion over threshold...
 *
 *            Derived from pivot.c by Robert Love.
 *
 * Copyright (C) 2005-2006 Jon Escombe <lists@dresco.co.uk>
 *                         Robert Love <rml@novell.com>
 *
 * "Why does that kid keep dropping his laptop?"
 */

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/mman.h>

#define SYSFS_POSITION_FILE	"/sys/devices/platform/hdaps/position"

#define BUF_LEN			32

/*
 * read_position - read the (x,y) position pair from hdaps.
 *
 * We open and close the file on every invocation, which is lame but due to
 * several features of sysfs files:
 *
 *	(a) Sysfs files are seekable.
 *	(b) Seeking to zero and then rereading does not seem to work.
 *
 * If I were king--and I will be one day--I would have made sysfs files
 * nonseekable and only able to return full-size reads.
 */
static int read_position (int *x, int *y)
{
	char buf[BUF_LEN];
	int fd, ret;

	fd = open (SYSFS_POSITION_FILE, O_RDONLY);
	if (fd < 0) {
		perror ("open");
		return fd;
	}	

	ret = read (fd, buf, BUF_LEN);
	if (ret < 0) {
		perror ("read");
		goto out;
	} else if (ret == 0) {
		fprintf (stderr, "error: unexpectedly read zero!\n");
		ret = 1;
		goto out;
	}
	ret = 0;

	if (sscanf (buf, "(%d,%d)\n", x, y) != 2)
		ret = 1;

out:
	if (close (fd))
		perror ("close");

	return ret;
}

/*
 * sanity_check() - check the current protection status
 */
static int sanity_check (const char *path)
{
	int fd, ret;
	char buf[BUF_LEN];

	memset(buf, 0, sizeof(buf));
	fd = open (path, O_RDONLY);
	ret = read (fd, buf, BUF_LEN);
	close (fd);
	return atoi(buf);
}

/*
 * write_protect() - park/unpark
 */
static int write_protect (const char *path, int val)
{
	int fd, ret;
	char park[] = "5";
	char unpark[] = "0";

	fd = open (path, O_WRONLY);
	if (fd < 0) {
		perror ("open");
		return fd;
	}	

	if (val)
		ret = write (fd, &park, sizeof(park));
	else
		ret = write (fd, &unpark, sizeof(unpark));

	if (ret < 0) {
		perror ("write");
		goto out;
	}
	ret = 0;

out:
	if (close (fd))
		perror ("close");

	return ret;
}

/*
 * usage() - display usage instructions and exit 
 */
void usage()
{
	printf("usage: hdapsd -d <device> -s <sensitivity> [-b]\n");
	printf("       where <device> is likely to be hda or sda,\n");
	printf("       a suggested starting <sensitivity> is 15,\n");
	printf("       and -b will run the process in the background.\n");
	exit(1);
}

/*
 * main() - loop forever, reading the hdaps values and 
 *          parking/unparking as necessary
 */
int main (int argc, char** argv)
{
	int c, x, y, x_last, y_last, x_delta, y_delta, park_check;
	int fd, i, ret, threshold = 0, background = 0, verbose = 0, parked = 0;
	char protect_file[32] = "";
	time_t now;

	for (;;) {
		c = getopt(argc, argv, "d:s:b");
		if (c < 0)
			break;
		switch (c) {
			case 'd':
				sprintf(protect_file, "/sys/block/%s/queue/protect", optarg);
				break;
			case 's':
				threshold = atoi(optarg);
				break;
			case 'b':
				background = 1;
				break;
			default:
				usage();
				break;
		}
	}

	if (!threshold || !strlen(protect_file))
		usage(argv);

	if (background)
		daemon(0,0);

	mlockall(MCL_FUTURE);

	if (verbose) {
		printf("protect_file: %s\n", protect_file);
		printf("threshold: %i\n", threshold);
	}

	/* check the protect attribute exists */
	/* wait for it if it's not there (in case the attribute hasn't been created yet) */
	fd = open (protect_file, O_WRONLY);
	if (fd < 0) {
		if (background) {
			do {
				usleep (1000000);	/* 1 Hz */
				fd = open (protect_file, O_WRONLY);		
			} while (fd < 0);
		} else {
			perror ("open");
			return 1;
		}
	} else
		close (fd);

	/* get some base data for comparison*/
	/* wait for it if it's not there (in case the attribute hasn't been created yet) */
	ret = read_position (&x_last, &y_last);
	if (ret) {
		if (background) {
			do {
				usleep (1000000);	/* 1 Hz */
				ret = read_position (&x_last, &y_last);
			} while (ret);
		} else
			return 1;
	}

	while (1) {
		usleep (100000);	/* 10 Hz */

		ret = read_position (&x, &y);
		if (ret)
			continue;

		x_delta = abs(x - x_last);
		y_delta = abs(y - y_last);

		if (verbose) {
			printf("x_delta: %d\n", x_delta);
			printf("y_delta: %d\n", y_delta);

			if (x_delta > threshold)
				printf("accelerometer delta over x threshold: %d\n", x_delta);
			if (y_delta > threshold)
				printf("accelerometer delta over y threshold: %d\n", y_delta);
		}

		if (parked && !sanity_check(protect_file))
			printf("\nError! Not parked when we thought we were.. (paged out and timer expired?)\n");

		if ((x_delta > threshold) || (y_delta > threshold)) {
			if (!parked) {
				now = time((time_t *)NULL);
				printf("%.24s: parking\n", ctime(&now));
			}
			parked = 10;
			write_protect(protect_file, 1);
		}			

		if (parked) {
			parked--;
			if (!parked) {
				now = time((time_t *)NULL);
				printf("\n%.24s: un-parking\n", ctime(&now));
				write_protect(protect_file, 0);
			} else {
				printf(".");
				fflush(stdout);
			}
		}

		x_last = x;
		y_last = y;
	}

	munlockall();
	return ret;
}
