/*
 * small tool to read xa-files (form2 too, yea!) from CD's
 *
 *       (c) 1996,1997 Gerd Knorr <kraxel@cs.tu-berlin.de>
 *
 * uses the CDROMREADMODE2 ioctl which allows to read
 * raw frames (2336 bytes)
 *
 * Works with IDE (2.0.29 is ok) and SCSI (requires >= 2.1.34)
 *
 * compile: 'make readxa'
 * usage: try 'readxa -h'
 *
 * 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>

extern char *sys_errlist[];

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_BLOCK_OFFSET) / CD_FRAMES / CD_SECS; 
    msf->cdmsf_sec0   = (lba + CD_BLOCK_OFFSET) / CD_FRAMES % CD_SECS;
    msf->cdmsf_frame0 = (lba + CD_BLOCK_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;
    unsigned long block;
    unsigned char *buf;
    struct stat st, dev_st;
    int device;
    struct cdrom_tocentry toc,toc2;

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

    /* parse options */
    for (;;) {
	c = getopt(argc, argv, "ihrf: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 'h':
	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;
	block = 0;
	if (-1 == ioctl(file,FIBMAP,&block)) {
	    perror("ioctl FIBMAP");
	    exit(1);
	}
	if (print_info) {
	    fprintf(stderr,"first sector: %lu, size: %d\n",block,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);
	    }
	}
	block = toc.cdte_addr.lba;
	size  = (toc2.cdte_addr.lba - block) * 2048;
	if (print_info) {
	    fprintf(stderr,"first sector of track %d: %lu\n",tracknr,block);
	    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,block,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 {
	for (;;) {
	    int offset, len;
			
	    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);

	    size -= 2048 /* len */;
	    block++;
	    if (size <= 0)
		break;
	    if (0 != (rc = read_raw_frame(cdrom,block,buf))) {
		perror("read error");
		exit(1);
	    }
	}
    }
    close(cdrom);
    free(buf);
    exit(0);
}
