/*
 * small tool to read xa-files (form2 too, yea!) from CD's
 *
 *       (c) 1996 Gerd Knorr <kraxel@cs.tu-berlin.de>
 *
 * requires a ioctl CDROMREADMODE2 which allows to read
 * raw frames (2336 bytes). Should work with ide and scsi,
 * if your kernel new enouth (ide 2.0.x, scsi >= 2.1.34).
 *
 * Known Bugs / TODO-List
 *  - It can't handle interleaved files correctly.
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>

#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/cdrom.h>
#include <linux/fs.h>

#include <term.h>
#include <termbits.h>

static const char *device_list[] = {
    /* try most common first ... */
    "/dev/cdrom",
    "/dev/scd0",
    "/dev/sr0",
    "/dev/hdc",
    "/dev/hdd",

    "/dev/scd1",
    "/dev/sr1",
    "/dev/scd2",
    "/dev/sr2",
    "/dev/scd3",
    "/dev/sr3",
    "/dev/hda",
    "/dev/hdb",
    "/dev/hde",
    "/dev/hdf",
    "/dev/hdg",
    "/dev/hdh",
    NULL
};

int print_info = 0;
int dump_raw   = 0;

void
usage()
{
    fprintf(stderr,
	    "usage: readxa options\n"
	    "readxa needs either a file or a device and track number\n"
	    "  -f file  give filename\n"
	    "  -d dev   give device name\n"
	    "  -t trk   give track number\n"
	    "  -i       prints some informations about the file and quit\n"
	    "  -r       dumps the complete sector (raw, 2336 bytes) instead\n"
	    "           of the 2048 (form1) / 2324 (form2) data bytes\n");
    
}

int
read_raw_frame(int fd, int lba, unsigned char *buf)
{
    struct cdrom_msf *msf;
    int    rc;

    msf = (struct cdrom_msf*) buf;
    msf->cdmsf_min0   = (lba + CD_MSF_OFFSET) / CD_FRAMES / CD_SECS; 
    msf->cdmsf_sec0   = (lba + CD_MSF_OFFSET) / CD_FRAMES % CD_SECS;
    msf->cdmsf_frame0 = (lba + CD_MSF_OFFSET) % CD_FRAMES;
    rc = ioctl(fd,CDROMREADMODE2,buf);
    if (-1 == rc)
	perror("ioctl CDROMREADMODE2");
    return rc;
}

int
main(int argc, char *argv[])
{
    int            file, cdrom, rc, c, size, count = 0;
    long           start;
    long           block = 0;
    unsigned char  *buf;
    struct stat    st, dev_st;
    int            device;
    struct cdrom_tocentry toc,toc2;
    struct termios      saved_attributes,tattr;

    char *filename = NULL, *devname = NULL;
    int tracknr = 0;

    /* parse options */
    for (;;) {
	c = getopt(argc, argv, "icrf:d:t:");
	if (c == -1)
	    break;
	switch (c) {
	case 'i':
	    print_info = 1;
	    break;
	case 'r':
	    dump_raw = 1;
	    break;
	case 'f':
	    filename = optarg;
	    break;
	case 'd':
	    devname = optarg;
	    break;
	case 't':
	    tracknr = atoi(optarg);
	    break;
	case 'c':
	    count = 1;
	    break;
	default:
	    usage();
	    exit(1);
	}
    }

    if (filename) {
	/* have a file -- get start sector */
	if (-1 == (file = open(filename,O_RDONLY))) {
	    fprintf(stderr,"open %s: %s\n",filename,sys_errlist[errno]);
	    exit(1);
	}
	fstat(file,&st);
	size = st.st_size;
	start = 0;
	if (-1 == ioctl(file,FIBMAP,&start)) {
	    perror("ioctl FIBMAP");
	    exit(1);
	}
	if (print_info) {
	    fprintf(stderr,"first sector: %lu, size: %d\n",start,size);
	}
	close(file);
	/* get block device */
	for (device = 0; device_list[device] != NULL; device++) {
	    if (-1 == stat(device_list[device],&dev_st))
		continue;
	    if (!S_ISBLK(dev_st.st_mode))
		continue;
	    if (dev_st.st_rdev == st.st_dev)
		break;
	}
	if (device_list[device] == NULL) {
	    fprintf(stderr,"can't find special file for %02x:%02x\n",
		    MAJOR(st.st_dev),MINOR(st.st_dev));
	    exit(1);
	}
	if (print_info) {
	    fprintf(stderr,"block device: %s\n",device_list[device]);
	}
    } else if (devname && tracknr) {
	/* have a device + track -- read toc */
	if (-1 == (file = open(devname,O_RDONLY))) {
	    fprintf(stderr,"open %s: %s\n",devname,sys_errlist[errno]);
	    exit(1);
	}
	toc.cdte_track  = tracknr;
	toc.cdte_format = CDROM_LBA;
	if (-1 == ioctl(file,CDROMREADTOCENTRY,&toc)) {
	    perror("ioctl CDROMREADTOCENTRY");
	    exit(1);
	}
	toc2.cdte_track  = tracknr+1;
	toc2.cdte_format = CDROM_LBA;
	if (-1 == ioctl(file,CDROMREADTOCENTRY,&toc2)) {
	    toc2.cdte_track  = CDROM_LEADOUT;
	    toc2.cdte_format = CDROM_LBA;
	    if (-1 == ioctl(file,CDROMREADTOCENTRY,&toc2)) {
		perror("ioctl CDROMREADTOCENTRY");
		exit(1);
	    }
	}
	start = toc.cdte_addr.lba;
	size  = (toc2.cdte_addr.lba - start) * 2048;
	if (print_info) {
	    fprintf(stderr,"first sector of track %d: %lu\n",tracknr,start);
	    fprintf(stderr,"first sector of %s: %d, size <= %d\n",
		    (toc2.cdte_track != CDROM_LEADOUT) ?
		    "next track" : "leadout",
		    toc2.cdte_addr.lba,size);
	}
	close(file);
    } else {
	usage();
	exit(1);
    }

    /* read sector (raw) */
    if (-1 == (cdrom = open(devname ? devname : device_list[device],O_RDONLY))) {
	fprintf(stderr,"open %s: %s\n",devname ? devname : device_list[device],sys_errlist[errno]);
	exit(1);
    }
    if (NULL == (buf = malloc(CD_FRAMESIZE_RAW0))) {
	fprintf(stderr,"Out of memory\n");
	exit(1);
    }
	
    if (0 != (rc = read_raw_frame(cdrom,start,buf))) {
	perror("read error");
	exit(1);
    }

    if (print_info) {
	if (buf[0] == buf[4] &&
	    buf[1] == buf[5] &&
	    buf[2] == buf[6] &&
	    buf[3] == buf[7] &&
	    buf[2] != 0 && 
	    buf[0] < 8 &&
	    buf[1] < 8) {
	    fprintf(stderr,"XA sectors - ");
	    fprintf(stderr,"form %s - ",buf[2] & 0x20 ? "2" : "1");
	    if (buf[2] & 0x08) fprintf(stderr,"data\n");
	    if (buf[2] & 0x04) fprintf(stderr,"audio\n");
	    if (buf[2] & 0x02) fprintf(stderr,"video\n");
	    if (buf[0])
		fprintf(stderr, "fileno %d\n", buf[4]);
	    if (buf[1])
		fprintf(stderr, "channel %d\n", buf[5]);
	} else {
	    fprintf(stderr,"normal sectors\n");
	}
    } else {
	fcntl(0,F_SETFL,O_NONBLOCK);
	tcgetattr (0, &saved_attributes);
	tcgetattr (0, &tattr);
	tattr.c_lflag &= ~(ICANON|ECHO|ISIG);
	tattr.c_cc[VMIN] = 1;
	tattr.c_cc[VTIME] = 0;
	tcsetattr (0, TCSAFLUSH, &tattr);
	for (;;) {
	    int   offset, len, rc, i;
	    char  keys[100];

	    if (buf[0] == buf[4] &&
		buf[1] == buf[5] &&
		buf[2] == buf[6] &&
		buf[3] == buf[7] &&
		buf[2] != 0 && 
		buf[0] < 8 &&
		buf[1] < 8) {
		offset = 8;
		len    = (buf[2] & 0x20) ? 2324 : 2048;
	    } else {
		offset = 0;
		len    = 2048;
	    }
	    if (dump_raw)
		write(1,buf,CD_FRAMESIZE_RAW0);
	    else
		write(1,buf+offset, (size < len) ? size : len);

	    if (count)
		fprintf(stderr,"%10ld\r",block);
	    rc = read(0,keys,100);
	    if (rc > 0) {
		if (keys[0] == 3 || keys[0] == 'Q' || keys[0] == 'q') {
		    fprintf(stderr,"quit\n");
		    break;
		}
		fprintf(stderr,"%10ld => ",block);
		for (i = 0; i < rc; i++)
		    switch (keys[i]) {
		    case '3': block +=   500; break;
		    case '6': block +=  5000; break;
		    case '1': block -=   500; break;
		    case '4': block -=  5000; break;
		    }
		if (block < 0) block = 0;
		fprintf(stderr,"%10ld%c",block,count?'\n':'\r');
	    }
	    
	    block++;
	    if (2048*block >= size)
		break;
	    if (0 != (rc = read_raw_frame(cdrom,start+block,buf))) {
		perror("read error");
		exit(1);
	    }
	}
	tcsetattr (0, TCSANOW, &saved_attributes);
    }
    close(cdrom);
    free(buf);
    exit(0);
}

