/*
 * cd-manager.c -
 * simple media event poller for cdroms implementing the GET_EVENT
 * interface and extra crap to deal with umounts and ejects
 *
 * Copyright (C) 2001 Jens Axboe <axboe@suse.de>
 * Modified and subsequently
 *   Copyright (C) 2005 Peter Willis <psyphreak@phreaker.net>
 * Modified and subsequently
 *   Copyright (C) 2009 Brad Love <brad at b-rad.cc>
 *       contact me for current issues & support
 *       http://b-rad.cc/cd-manager
 *       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.
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mount.h>

#include <linux/cdrom.h>

char cd_device[256] = "/dev/sr0";
int debug = 0;

int wait_cmd(int fd, struct cdrom_generic_command *cgc, void *buffer,
	     int len, int ddir, int timeout)
{
	struct request_sense sense;
	int ret;
	memset(&sense, 0, sizeof(sense));

	cgc->timeout = timeout;
	cgc->buffer = buffer;
	cgc->buflen = len;
	cgc->data_direction = ddir;
	cgc->sense = &sense;
	cgc->quiet = 1;

	ret = ioctl(fd, CDROM_SEND_PACKET, cgc);
	if (ret == -1) {
		perror("ioctl");
		fprintf(stderr, "Error: could not get data from CDROM.\n");
	}
	return ret;
}

int get_media_event(int fd)
{
	struct cdrom_generic_command cgc;
	unsigned char buffer[8];
	int ret;

	memset((&cgc), 0, sizeof(struct cdrom_generic_command));
	memset(buffer, 0, sizeof(buffer));

	cgc.cmd[0] = GPCMD_GET_EVENT_STATUS_NOTIFICATION;
	cgc.cmd[1] = 1;
	cgc.cmd[4] = 16;
	cgc.cmd[8] = sizeof(buffer);

	ret = wait_cmd(fd, &cgc, buffer, sizeof(buffer), CGC_DATA_READ, 600);
	if (ret < 0) {
		perror("GET_EVENT");
		return ret;
	}
	return buffer[4] & 0xf;
}

int eject_request(int fd)
{
	char mountline[256];
	snprintf(mountline, sizeof(mountline)-1, "cd.eject %s", cd_device);
	if ( system(mountline) == -1 ) {
		perror("umount");
		fprintf(stderr, "Error: could not umount CDROM \"%s\".\n", cd_device);
        return(1);
	} 
	return(0);
}

int mount_cdrom(int fd)
{
	char mountline[256];
	snprintf(mountline, sizeof(mountline)-1, "cd.mount %s", cd_device);
	if ( system(mountline) == -1 ) {
		perror("mount");
		fprintf(stderr, "Error: could not mount %s.\n", cd_device);
		return(1);
	}
	return(0);
}

int poll_events(int fd)
{
	int event, quit, first;

	quit = 0;
	first = 1;
	do {
		event = get_media_event(fd);
		if (event < 0)
			break;

		switch (event) {
			case 0:
				if (first)
					if ( debug ) fprintf(stderr, "no media change\n");
				break;
			case 1:
				if ( debug ) fprintf(stderr, "eject request\n");
				eject_request(fd);
				break;
			case 2:
				if ( debug ) fprintf(stderr, "new media\n");
				mount_cdrom(fd);
				break;
			case 3:
				if ( debug ) fprintf(stderr, "media removal\n");
				break;
			case 4:
				if ( debug ) fprintf(stderr, "media change\n");
				mount_cdrom(fd);
				break;
			default:
				if ( debug ) fprintf(stderr, "unknown media event (%d)\n", event);
				break;
		}
		first = 0;

		if (event)
			continue;
		sleep(1);
	} while (!quit);
	return event;
}

void usage(char *arg)
{
	printf(
      "Usage: %s <device>\n\n"
      "%s continuously polls <device> for eject button\n"
      "requests or new media and mounts, umounts and ejects accordingly.\n",
      arg, arg
	);
	exit(1);
}

int main(int argc, char *argv[])
{
	int fd, i;
	argv[argc] = cd_device; /* default checked last */

	for ( i=1; i<=argc; i++ ) {
		if ( strncmp(argv[i], "-h", 2) == 0 || strncmp(argv[i], "--help", 6) == 0 )
			usage(argv[0]);
		strncpy(cd_device, argv[i], sizeof(cd_device)-1);

	    if ( (fd = open(cd_device, O_RDONLY | O_NONBLOCK)) != -1 )
		    break;
	}
	if (fd == -1) {
        printf("%s error, does symlink/device exist?", cd_device);
		perror("open");
		return 1;
	}
	return poll_events(fd);
}
