/*
  dvdtape: A program for generating master images of
  Digital Versatile Discs (DVD) to Digital Linear Tapes (DLT),
  including Disc Desciption Protocol information.

  Copyright 1999, 2000 Yggdrasil Computing, Incorporated
  Written by Adam J. Richter (adam@yggdrasil.com)

  This program may be freely redistributed under the terms and
  conditions of version 2 of the GNU General Public License, as
  published by the Free Software Foundations (Cambridge, MA, USA).
  A copy of these terms and conditions is in the file "COPYING",
  which you should have received along with this file.
*/

#define _FILE_OFFSET_BITS 64
#include "config.h"
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#ifdef HAVE_SYS_MTIO_H
# include <sys/mtio.h>
#endif


#define _GNU_SOURCE
#include <getopt.h>

#define TAPE_BLOCK_SIZE	32768ULL	/* This is NOT a tunable paramter */
#define DEFAULT_CONTROL_SIZE	32768

#define DVD_SINGLE_SIZE	(143432ULL * TAPE_BLOCK_SIZE) /* 4,699,979,776 bytes */
#define DVD_LAYER1_SIZE	(130310ULL * TAPE_BLOCK_SIZE) /* 4,269,998,080 bytes */
/* A daul layer DVD has 8,539,996,160 bytes. */
#define DVD_SECTOR_SIZE	2048

#define CONTROL_START	0x2f200
#define IMAGE_START	0x30000

#define CONTROL_FILE	"CONTROL.DAT"
#define IMAGE_FILE	"MAIN.DAT"

extern unsigned long long isosize(int infile);

static int layer = 0;
static int layers = 1;
static int layers_specified = 0;
static int side = 0;
static int sides = 1;
static int sides_specified = 0;
static unsigned long long offset = 0;
static int offset_specified = 0;
static unsigned long long combined_length; /* sum of lengths of both layers
					      in 2 layer case */
static int combined_length_specified = 0;
static unsigned long long layer_length = 0;
static int length_specified = 0;
static char *owner = "";
static char *usertext = "";
static char *input_filename = NULL;
static char *control_filename = NULL;
static char *control_output_filename = NULL;
static char *output_filename = "/dev/st0";
static char time_string[7];
static char track_path;		/* Direction of translation for second layer.
				   I = inward, parallel to first layer.
				   O = outward, opposite of first layer. */
static int track_path_specified = 0;
static char diameter = 'B';	/* "A" = 8cm, "B" = 12cm. */
int readout_speed = 2;		/* Minimum readout speed.  Used only in the
				   control file.
				    2 =  2,520,000 bits/second
				    5 =  5,040,000 bits/second
				   10 = 10,080,000 bits/second
				*/
char master_id[49] = "\0";


#define need_control	(layer == 0 || track_path == 'I')

static struct option options[] = {
	{"combined-length", required_argument, NULL, 'C'},
	{"controlfile", required_argument, NULL, 'c'}, /* for control data */
	{"controlfile-output", required_argument, NULL, 'N'},
	{"diameter", required_argument, NULL, 'd'},
	{"inputfile", required_argument, NULL, 'i'},
	{"layer", required_argument, NULL, 'l'},
	{"layers", required_argument, NULL, 'L'},
	{"length", required_argument, NULL, 'b'},
	{"master-id", required_argument, NULL, 'm'},
	{"offset", required_argument, NULL, 'o'},
	{"outputfile", required_argument, NULL, 'f'},
	{"owner", required_argument, NULL, 'O'},
	{"readout-speed", required_argument, NULL, 'r'},
	{"side", required_argument, NULL, 's'},
	{"sides", required_argument, NULL, 'S'},
	{"track-path", required_argument, NULL, 't'},
	{"usertext", required_argument, NULL, 'u'},
	{0, 0, NULL, 0}
};

static void
usage(void) {
	int i;
  	fprintf (stderr, "Usage: dvdtape [options]\n"
		 "Options:\n");
	for (i = 0; options[i].name != NULL; i++) {
	  	fprintf(stderr, "\t--%s", options[i].name);
		switch (options[i].has_arg) {
			case no_argument:
				fprintf(stderr, "\n");
				break;
			case required_argument:
				fprintf(stderr, "=%s\n", options[i].name);
				break;
			case optional_argument:
				fprintf(stderr, "[=%s]\n", options[i].name);
				break;
		}
	}
	exit(1);
}

static void
parse_options (int argc, char **argv) {
	int opt;

	while ((opt = getopt_long(argc, argv, "l:L:s:S:o:O:u:l:",
				  options, NULL)) != -1) {
		switch (opt) {
			case 'C':
				if (sscanf(optarg,"%Ld",&combined_length)!=1) {
					fprintf (stderr,
						 "%s: not an integer\n",
						 optarg);
					exit(1);
				}
				combined_length_specified = 1;
			  	break;
			case 'c': control_filename = optarg; break;
			case 'd':
			  	if (strcmp(optarg, "8cm") == 0)
				  	diameter = 'A';
				else if (strcmp(optarg, "12cm") == 0)
				        diameter = 'B';
				else {
				  	fprintf (stderr,
						 "Disc diameter must be \"8cm\" or \"12cm\".\n");
					exit(1);
				}
				break;
			case 'l': layer = atoi(optarg); break;
			case 'L':
				layers = atoi(optarg);
				layers_specified = 1;
				break;
			case 'm':
			  	strncpy(master_id,optarg,sizeof(master_id)-1);
				master_id[sizeof(master_id)-1] = '\0';
				break;
			case 'N': control_output_filename = optarg; break;
			case 'o':
				if (sscanf(optarg, "%Ld", &offset) != 1) {
					fprintf (stderr,
						 "%s: not an integer\n",
						 optarg);
					exit(1);
				}
				offset_specified = 1;
				break;
			case 'O': owner = optarg; break;
			case 'r':
			  	readout_speed = atoi(optarg);
				if (readout_speed != 2 &&
				    readout_speed != 5 &&
				    readout_speed != 10) {
				  	fprintf (stderr,
						 "Read out speed must be 2, 5 or 10 for 2.52, 5.04, or 10.08 megabits/second.\n");
					exit(1);
				}
				break;
			case 's': side = atoi(optarg); break;
			case 'S':
			  	sides = atoi(optarg);
				sides_specified = 1;
				break;
			case 't':
			  	if (strcmp(optarg, "opposite") == 0 ||
				    strcmp(optarg, "out") == 0 ||
				    strcmp(optarg, "outward") == 0) {
				  	track_path = 'O';
			  	} else if (strcmp(optarg, "parallel") == 0 ||
				    strcmp(optarg, "in") == 0 ||
				    strcmp(optarg, "inward") == 0) {
				  	track_path = 'I';
				} else {
				     fprintf (stderr, "Track path must be one of opposite, out, outward (which are synonymous)\n"
					      "or parallel, in or inward (which are synonymous).\n");
				     exit(1);
				}
				track_path_specified = 1;
				break;
			case 'u': usertext = optarg; break;
			case 'b':
				if (sscanf(optarg, "%Ld", &layer_length) != 1) {
					fprintf (stderr,
						 "%s: not an integer\n",
						 optarg);
					exit(1);
				}
				length_specified = 1;
				break;
			case 'i': input_filename = optarg; break;
			case 'f': output_filename = optarg; break;
			case '?':
			  	fprintf (stderr, "Unknown option.\n");
				usage();
				/*NOTREACHED*/
			case ':':
			  	fprintf (stderr, "Missing option.\n");
				usage();
				/*NOTREACHED*/
		}
	}
}

static void
check_options(void) {
  	int must_abort = 0;
	if (side == 0 && layer == 0 && offset != 0) {
	  	fprintf (stderr, "Warning: You have specified a nonzero input offset, even though you are making the first layer of the first side.\n");
	}
	if (!sides_specified) sides = side+1;
	if (!layers_specified) layers = layer+1;
	if (sides > 2) {
	  	fprintf (stderr, "Disc cannot have more than two sides.\n");
		must_abort = 1;
	}
	if (layers > 2) {
	  	fprintf (stderr, "Disc cannot have more than two layers.\n");
		must_abort = 1;
	}
	if (side >= sides) {
	  	fprintf(stderr,
			"Side number (%d) is greater than or equal to"
			"number of sides(%d).\n",
			side, sides);
		must_abort = 1;
	}
	if (layer >= layers) {
	  	fprintf(stderr,
			"Layer number (%d) is greater than or equal to"
			"number of layers(%d).\n",
			layer, layers);
		must_abort = 1;
	}
	if (!input_filename) {
	  	fprintf(stderr, "Input file name not specified.\n");
		must_abort = 1;
	}
	if (must_abort) {
		usage();
	}
}

static void
full_read(int infd, char *buf, int len) {
	while (len > 0) {
	  	int this_len = read(infd, buf, len);
		if (this_len < 0) {
			if (errno == EINTR)
				continue;
			else {
			  	perror("read");
				exit(1);
			}
		}
		if (this_len == 0) {
		  	fprintf (stderr, "Premature end of file.\n");
			exit(1);
		}
		buf += this_len;
		len -= this_len;
	}
}

static void
full_write(int outfd, const char *buf, int len) {
	while (len > 0) {
	  	int this_len = write(outfd, buf, len);
		if (this_len < 0) {
			if (errno == EINTR)
				continue;
			else {
			  	perror("write");
				exit(1);
			}
		}
		buf += this_len;
		len -= this_len;
	}
}

static void
copy_bytes(int outfd, int infd, int blocksize, unsigned long long length) {
  	int blkcount = 0;
	int nblocks = length / blocksize;
	while (length > 0) {
	  	char buf[blocksize];
		if (length >= blocksize) {
			full_read(infd, buf, blocksize);
			full_write(outfd, buf, blocksize);
			length -= blocksize;
		} else {
			/* Pad to block size. */
		  	full_read(infd, buf, length);
			memset(buf+length, 0, blocksize - length);
			full_write(outfd, buf, blocksize);
			return;
		}
		blkcount++;
		if (blkcount % 10000 == 0) {
		  	printf ("copy_bytes: %d %d byte blocks written (%d%% complete).\n",
				blkcount, blocksize, blkcount * 100 / nblocks);
			fflush(stdout);
		}
	}
}

static void
write_vol_label(int outfd) {
  	char buf[81];
  	sprintf(buf,
		"VOL1"		/* Label ID */
		"REEL%-2d"	/* Reel ID */
		" "		/* Accessibility */
		"                          " /* reserved (26 bytes) */
		"%-14.14s"	/* Owner */
		"                            " /* reserved (28 bytes */
		"4",		/* Label Standard Version ("4" = ANSI label) */
		(side*layers)+layer+1, /* Reel ID# */
		owner);
	full_write(outfd, buf, sizeof(buf)-1);
}

static void
write_hdr1(int outfd, char *labelid, char *fileid, int block_count) {
  	char buf[81];
  	sprintf(buf,
		"%-4.4s"	/* Label ID */
		"%-17.17s"	/* File ID */
		"DVD   "	/* File set ID, 6 bytes */
		"0001"		/* File Section Number */
		"0001"		/* File Sequence Number */
		"0001"		/* File Generation Number */
		"01"		/* File Generation Version Number */
		"%6.6s"		/* Creation date */
		"000000"	/* Expiration date not specified */
		" "		/* Accessibility */
		"%06d"		/* Block count */
		"             "	/* system code, 13 bytes */
		"       ",	/* Reserved, 7 bytes */
		labelid, fileid, time_string, block_count);
	full_write(outfd, buf, sizeof(buf)-1);
}

static void
write_eof_mark(int outfd)
{
#ifdef HAVE_SYS_MTIO_H
	struct mtop mtop;
	mtop.mt_op = MTWEOF;
	mtop.mt_count = 1;
	if (ioctl(outfd, MTIOCTOP, &mtop) < 0) {
		perror ("writing end-of-file mark in tape stream");
	}
#else
	fsync(outfd);
	while (write(outfd, buf, 0) < 0) {
		if (errno != EINTR) {
			perror ("writing end-of-file mark in tape stream");
			exit(1);
		}
	}
	fsync(outfd);
#endif
}

static void
write_hdr2(int outfd, char *labelid, int blocksize, int recordsize) {
  	char buf[81];
  	sprintf(buf,
		"%-4.4s"	/* Label ID */
		"F"		/* Record format, "F" = fixed */
		"%05d"		/* block size */
		"%05d"		/* record size */
		"                                   " /* reserved, 35 bytes */
		"00"		/* buffer offset length */
		"                            ", /* reserved, 28 bytes */
		labelid, blocksize, recordsize
		);
	full_write(outfd, buf, sizeof(buf)-1);
	write_eof_mark(outfd);
}

static void
write_ddpid(int outfd) {
  	char buf[129];
	char txtsize[3];
	if (strlen(usertext) == 0)
	    strcpy(txtsize, "  ");
	else
	    sprintf (txtsize, "%02d", strlen(usertext));

  	sprintf(buf,
		"DDP 2.00"	/* DDP level */
		"             "	/* UPC, reserved, 13 bytes */
		"        "	/* MSS, Map Stream Start, 8 bytes */
		"        "	/* MSL, reserved, 8 bytes */
		" "		/* Media Number */
		"%-48.48s"	/* Master ID (user supplied string) */
		" "		/* BK, reserved */
		"DV"		/* TYPE, Type of Disc ("DV" = DVD) */
		"%01d"		/* NSIDE, # of sides on final disc */
		"%01d"		/* SIDE */
		"%01d"		/* NLAYER */
		"%01d"		/* LAYER */
		"%c"		/* DIR.  Direction of translation for
				   second layer.
				   "I" = inner to outer,
				   "O" = outer to inner (opposite path). */
		"%c"		/* Disc size "B" = 12cm, "A" = 8cm */
		"0"		/* SSCRST. Security Scrambling Status
				   "0" = No encryption. */
		"0"		/* SSCRMD. Security Scrambling Mode.
				   "0" = DVD. */
		"%s"		/* SIZ. Size of user text */
		"%-29.29s",	/* TXT. User text  */
		master_id,
		sides,
		side,
		layers,
		layer,
		track_path,
		diameter,
		txtsize,
		usertext);
	full_write(outfd, buf, sizeof(buf)-1);
}

static void
write_ddpms(int outfd, char *streamtype, int start, int len, char *filename) {
  	char buf[129];
  	sprintf(buf,
		"VVVM"		/* MPV Map Packet Valid */
		"%-2.2s"	/* DST Data Stream Type
				   "D2" = control
				   "D0" = main DVD */
		"        "	/* DSP Data Stream Pointer, empty for tapes */
		"%08d"		/* DSL Data Stream Length */
		"%08d"		/* DSS Data Stream Start */
		"        "	/* SUB Subcode descriptor */
		"DV"		/* CDM DVD Disc mode. */
		"0"		/* SSM Source Storage Mode */
		"0"		/* SCR source materials scrambled */
		"    "		/* PRE1 Reserved. */
		"    "		/* PRE2 Reserved. */
		"    "		/* PST Reserved */
		" "		/* MED Number for multiple input media */
		"  "		/* TRK Reserved. */
		"  "		/* IDX Reserved. */
		"            "	/* ISRC Reserved */
		"017"		/* SIZ Size of data stream identifier */
		"%-17.17s"	/* DSI Data stream identifier
				   ("CONTROL.DAT" or "IMAGE.DAT") */
		" "		/* NEW Reserved */
		"    "		/* PRE1NXT Reserved */
		"        "	/* PAUSEADD Reserved. */
		"         "	/* OFS Starting offset */
		"               ", /* PAD  Padding, 15 bytes. */
		streamtype,
		len,
		start,
		filename);
	full_write(outfd, buf, sizeof(buf)-1);
}

static void
write_ddp_file(int outfd) {
	const int num_tape_blocks = (need_control ? 3 : 2);
	const unsigned long image_blocks =
		(layer_length + TAPE_BLOCK_SIZE - 1) / TAPE_BLOCK_SIZE;
	const unsigned long image_sectors =
		image_blocks * (TAPE_BLOCK_SIZE / DVD_SECTOR_SIZE);
	unsigned long image_start;
	write_hdr1(outfd, "HDR1", "DDPID", num_tape_blocks);
	write_hdr2(outfd, "HDR2", 128, 128);
	write_ddpid(outfd);
	if (need_control) {
		image_start = IMAGE_START;
		write_ddpms(outfd, "D2", CONTROL_START, 16, CONTROL_FILE);
	} else {
		image_start = IMAGE_START + (offset/DVD_SECTOR_SIZE) - 1;
	}
	write_ddpms(outfd, "D0", image_start, image_sectors, IMAGE_FILE);
	write_eof_mark(outfd);	/* FIXME: Delete this?  The sample tape from
				   Marin Digital had an EOF mark here, but
				   I don't see anything in the standard
				   about it. -Adam Richter 2000.01.09 */
	write_hdr1(outfd, "EOF1", "DDPID", num_tape_blocks);
	write_hdr2(outfd, "EOF2", 128, 128);

}

static void
write_big_endian(unsigned char *out, unsigned long val) {
  	out[0] = val >> 24;
  	out[1] = val >> 16;
  	out[2] = val >> 8;
  	out[3] = val;
}

static void
make_control(unsigned char control[DEFAULT_CONTROL_SIZE]) {
	unsigned long sectors_this_layer, sectors_other_layer;
  	memset(control, 0, DEFAULT_CONTROL_SIZE);
	control[0] = 1;
	switch(readout_speed) {
		case 5:  control[1] = 1; break;
		case 10: control[1] = 2; break;
	}

	if (diameter == 'A')
	  	control[1] |= 0x10;

	control[2] = ((layers-1) << 5) | 1;
	if (track_path == 'O')
	  	control[2] |= 0x10;
	control[3] = (layers-1) << 4;
	control[5] = 0x03;
	sectors_this_layer = IMAGE_START + (layer_length/DVD_SECTOR_SIZE);
	if (track_path == 'O' && layers > 1) {
	  	/* ASSERT(layer==0); */
		sectors_other_layer =
			(combined_length - layer_length)/DVD_SECTOR_SIZE;
		write_big_endian(control+8,
		       (~sectors_this_layer & 0xFFFFFF) + sectors_other_layer);
		write_big_endian(control+12, sectors_this_layer - 1);
	} else {
		write_big_endian(control+8, sectors_this_layer - 1);
	}

	if (control_output_filename != NULL) {
		int fd;
		if ((fd = creat(control_output_filename, 0666)) < 0 ) {
			perror(control_output_filename);
		} else {
			full_write(fd, control, DEFAULT_CONTROL_SIZE);
			close(fd);
		}
	}
}

static void
write_control_file(int outfd) {
	int control_size;
	int control_fd = -1;
	char *control;

	if (control_filename == NULL) {
		control_size = DEFAULT_CONTROL_SIZE;
	} else {
		struct stat statbuf;
		if (stat(control_filename, &statbuf) < 0 ||
		    (control_fd = open(control_filename, O_RDONLY)) < 0) {
			perror(control_filename);
			exit(1);
		}
		control_size = statbuf.st_size;
	}

	/* Returns OK */
	write_hdr1(outfd, "HDR1", CONTROL_FILE, 1);
	/* Gets seg fault */
	write_hdr2(outfd, "HDR2", control_size, control_size / 16);
	control = alloca(control_size);
	if (control == NULL) {
		fprintf (stderr,
			 "Could not allocate %d bytes of memory for"
			 "control control data.\n", control_size);
		exit(1);
	}
	if (control_fd >= 0) {
		full_read(control_fd, control, control_size);
	} else {
	  	make_control(control);
	}
	full_write(outfd, control, control_size);
	if (control_fd >= 0)
		close(control_fd);
	write_eof_mark(outfd);	/* FIXME: Delete this?  The sample tape from
				   Marin Digital had an EOF mark here, but
				   I don't see anything in the standard
				   about it. -Adam Richter 2000.01.09 */
	write_hdr1(outfd, "EOF1", CONTROL_FILE, 1);
	write_hdr2(outfd, "EOF2", control_size, control_size/16);
}

static void
write_dvd_file(int outfd, int infd) {
  	const int blocksize = TAPE_BLOCK_SIZE;
	const int block_count = (layer_length+blocksize-1)/blocksize;
	write_hdr1(outfd, "HDR1", IMAGE_FILE, block_count);
	write_hdr2(outfd, "HDR2", blocksize, 2048);
	copy_bytes(outfd, infd, blocksize, layer_length);
	write_eof_mark(outfd);	/* FIXME: Delete this?  The sample tape from
				   Marin Digital *may have* had an EOF mark
				   here too, but I don't see anything in the
				   standard about it.
				   -Adam Richter 2000.01.09 */
	write_hdr1(outfd, "EOF1", IMAGE_FILE, block_count);
	write_hdr2(outfd, "EOF2", blocksize, 2048);
}

static void
guess_length_and_offset(int infd) {
	if (!combined_length_specified) {
	  	if (layers_specified && layers == 1 && length_specified)
		  	combined_length = layer_length;
		else
		  	combined_length = isosize(infd);
	}
	if (layer > 0 && !offset_specified) {
	  	offset = DVD_LAYER1_SIZE;
	}
	if (!length_specified) {
	  	layer_length = combined_length - offset;
		if (layer == 0 && layer_length > DVD_SINGLE_SIZE)
		  	layer_length = DVD_LAYER1_SIZE;
	}
	if (!layers_specified) {
	  	if (combined_length > DVD_SINGLE_SIZE) layers = 2;
	}
	if (!track_path_specified) {
		track_path = ((layers == 1) ? 'I' : 'O');
	}
}

static void
init_time(void) {
  	time_t now;
	struct tm *tm;
	time(&now);
	tm = localtime(&now);
	sprintf (time_string, "%c%02d%03d",
		 tm->tm_year >= 100 ? '0' : ' ',
		 tm->tm_year % 100,
		 tm->tm_yday + 1 /* E.g., Write Jan 1 as day 1, not day 0.*/
		 );
}

int
main(int argc, char **argv) {
	int outfd; 
	int infd;

	parse_options(argc, argv);
	check_options();

	init_time();

	if ((infd = open (input_filename, O_RDONLY, 0644)) < 0) {
	  	perror(input_filename);
		exit(1);
	}
	guess_length_and_offset(infd);
	fprintf (stderr, "Writing %Lu bytes of DVD image starting at offset %Lu (layer %d of %d).\n", layer_length, offset, layer+1, layers);
	if ((outfd = open (output_filename, O_WRONLY|O_CREAT, 0644)) < 0) {
	  	perror(output_filename);
		exit(1);
	}
	/* Even if offset == 0, we must do an lseek here, because we do not
	   know where guess_length_and_offst() left infd's seek pointer. */
	if (lseek(infd, (off_t) offset, SEEK_SET) < 0) {
		perror("llseek");
		exit(1);
	}

	write_vol_label(outfd);

	write_ddp_file(outfd);
	if (need_control)
		write_control_file(outfd);
	write_dvd_file(outfd, infd);

	close(infd);
	close(outfd);
	return 0;
}

/*
Format of control data:
   control[0] = 1	?
   control[1] = 0	 2.52 Mbps minimum readout speed
   		1	 5.04 Mbps minimum readout speed
		2	10.08 Mbps minimum readout speed
		| 0x00  for 12cm disc
		| 0x10  for  8cm disc

   control[2] = 0x01	1 layer
   		0x21	2 layer
		| 0x00  for parallel spiral layers
		| 0x10  for opposite spiral layers
   control[3] = 0x00	1 layer
   		0x10	2 layer
   control[4] = 0
   control[5] = 0x03
   control[6] = 0
   control[7] = 0
For parallel spiral layers:
   control[8..11] = BIG_ENDIAN(sectors_this_layer + 0x2FFFF)
   control[12..15] = 0
For opposite spiral layers:
   control[8..11] = BIG_ENDIAN(0xFCFFFF - layer0_sectors + layer1_sectors)
   control[12..15] = BIG_ENDIAN(layer0_sectors + 0x2FFFF)

*/
