/* IDE support */
#define _GNU_SOURCE 1
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <linux/hdreg.h>
#include "oopsd.h"

/* We need to know that start of the partition, to correct offsets */
static unsigned long get_start(int part_fd)
{
	struct hd_geometry hdgeo;
	struct hd_big_geometry hdgeo_big;

	if (ioctl(part_fd, HDIO_GETGEO_BIG, &hdgeo_big) < 0) {
		if (ioctl(part_fd, HDIO_GETGEO, &hdgeo) < 0) {
			perror("Failure reading partition geomtry");
			exit(1);
		}
		return hdgeo.start;
	}
	return hdgeo_big.start;
}

/* Create a LINUX_OOPSER_SET command for this device. */
void *ide_create_set_command(int devfd,
			     size_t *len,
			     const unsigned int offsets[])
{
	struct hd_driveid hdid;
	struct stat statbuf;
	struct linux_oopser_ide *ide;
	unsigned long part_start;
	unsigned int i;

	ide = malloc(sizeof(*ide));
	ide->command = LINUX_OOPSER_SET;
	ide->type = LINUX_OOPSER_TYPE_IDE;

	part_start = get_start(devfd);

	/* Seem to need full device, not partition, for GET_IDENTITY */
	fstat(devfd, &statbuf);
	if (minor(statbuf.st_rdev) >= 64) {
		ide->master = 0;
		devfd = open_device(major(statbuf.st_rdev), 64);
	} else {
		ide->master = 1;
		devfd = open_device(major(statbuf.st_rdev), 0);
	}

	if (ioctl(devfd, HDIO_GET_IDENTITY, &hdid) < 0) {
		perror("Getting identity of drive");
		exit(1);
	}
	if (!(hdid.capability & (1 << 1))) {
		fprintf(stderr, "Drive does not support LBA\n");
		exit(1);
	}
	close(devfd);

	/* Set offsets to absolute disk offsets */
	for (i = 0; i < LINUX_OOPSER_BLOCKS; i++)
		ide->lba[i] = part_start + offsets[i];

	*len = sizeof(*ide);
	return ide;
}

/* Get device name, eg. /dev/hda3 */
char *ide_get_device_name(int major, int minor)
{
	const char *base;
	char ext[32];
	char *ret;

	if (minor >= 64)
		base = "/dev/hdb";
	else
		base = "/dev/hda";

	if (minor % 64 == 0)
		strcpy(ext, "");
	else
		sprintf(ext, "%u", minor % 64);

	asprintf(&ret, "%s%s", base, ext);
	return ret;
}

/* Get devfs-style name. eg. /dev/ide/host0/bus0/target0/lun0/disc */
char *ide_get_devfs_name(int major, int minor)
{
	const char *base;
	char ext[32];
	char *ret;

	if (minor >= 64)
		base = "/dev/ide/host0/bus0/target1/lun0/";
	else
		base = "/dev/ide/host0/bus0/target0/lun0/";

	if (minor % 64 == 0)
		strcpy(ext, "disc");
	else
		sprintf(ext, "%u", minor % 64);

	asprintf(&ret, "%s%s", base, ext);
	return ret;
}
