/* Copyright 1994 Yggdrasil Computing, Inc. */
/* Written by Adam J. Richter (adam@yggdrasil.com) */
/* This file may be copied under the terms and conditions of version 2
   of the GNU General Public License, as published by the Free
   Software Foundation (Cambridge, Massachusetts). */

/* cdwrite scsi_device [-c byte_count] cdrom_file  */

#include <stdio.h>
#include <stdarg.h>
#include <sys/file.h>
#include <sys/errno.h>
#include "cdwrite.h"

/* volume descriptor types */
#define ISO_VD_PRIMARY 1
#define ISO_VD_END 255
#define ISO_STANDARD_ID "CD001"

#include <string.h>

int max_bytes_to_copy = -1;
int total_bytes_read = 0;
int pack_id = 5;
int auto_request_sense = 1;
static char *version_string = "1.3";

void
print_reply (const char *description, int reply_len, struct sg_reply *rep) {
   int i;
   int bytes_to_show;

   if (reply_len > 18)
     bytes_to_show = 18;
   else
     bytes_to_show = reply_len;
#if 0
   printf ("Reply length %d, sizeof (struct sg_header) %d.\n",
	   reply_len, sizeof(struct sg_header));
   printf ("Reply: pack_len %d reply_len %d pack_id %d result %d.\n",
	   rep->header.pack_len,
	   rep->header.reply_len,
	   rep->header.pack_id,
	   rep->header.result);
#endif
   if ( rep->header.result != 0 || (rep->header.sense_buffer[2] & 0xF) != 0) {
      fprintf (stderr, "%s result %d, pack_id %d sense",
	      description, rep->header.pack_id, rep->header.result);
      for ( i = 0; i < 16; i++ ) {
	 fprintf (stderr, " %d", rep->header.sense_buffer[i]);
      }
      fprintf (stderr, "\n");
   }
   fprintf (stderr, "%d of %d %s reply bytes:", bytes_to_show,
	   reply_len, description);
   for (i = 0; i < bytes_to_show; i++ ) fprintf (stderr, " %d", rep->bytes[i]);
   fprintf (stderr, "\n");
}


int
write_track (int fd, int *reply_len, struct sg_reply *rep) {
   *reply_len = sizeof(struct sg_reply);
   return send_request (fd, "write_track", reply_len, rep, 10,
			WRITE_TRACK,	/* 0 */
			0,		/* 1 */
			0,0,0,		/* 2..4 */
			0,		/* 5: track number */
			1,		/* 6 */
			0,0,0		/* 7..9 */);
}

unsigned int get_733 (char * p){
	return ((p[0] & 0xff)
		| ((p[1] & 0xff) << 8)
		| ((p[2] & 0xff) << 16)
		| ((p[3] & 0xff) << 24));
}

/* We do not know the size of the image - watch for the volume header
   as it goes by, and figure it out. */
int find_total_size(int total_bytes)
{
   unsigned char *start;
   start = &sg_request.bytes[6];

   while(total_bytes >= CD_BLOCK_SIZE)
     {
       /* Look for volume header. */
       if(*start == ISO_VD_PRIMARY && 
	  strncmp(start+1, ISO_STANDARD_ID, strlen(ISO_STANDARD_ID)) == 0)
	 {
	   max_bytes_to_copy = get_733(start+80) * CD_BLOCK_SIZE;
	   return;
	 }
       total_bytes -= CD_BLOCK_SIZE;
       start += CD_BLOCK_SIZE;
     }
}

int
pipe_to_cd (int in_fd, int out_fd, int *reply_len, struct sg_reply *rep) {
   unsigned char *start, *end;
   int bytes_written = 0;
   int this_len;
   int iteration = 1;
   int expected_len;
   unsigned char *write_ptr;

   bzero (&sg_request, sizeof(sg_request));
   sg_request.header.pack_len =
	sizeof(struct sg_header) + 6 + WRITE_BLOCK_SIZE;
   sg_request.header.reply_len = 20;
   sg_request.header.pack_id = pack_id++;
   sg_request.header.result = 0;

   sg_request.bytes[0] = WRITE_6;
   sg_request.bytes[4] = BLOCKS_PER_WRITE;

   do {
      int this_write_len;

      /* Read one block. */
      start = &sg_request.bytes[6];
      end = start + WRITE_BLOCK_SIZE;
      do {
	 this_len = read(in_fd, start, end - start );
	 if (this_len > 0) {
	    start += this_len;
	    total_bytes_read += this_len;
	 }
      } while (start != end && (this_len > 0 || errno == EAGAIN));
      if (this_len == 0) break;

      if(max_bytes_to_copy == -1) find_total_size(this_len);

      expected_len = sizeof(struct sg_header) + 6 + WRITE_BLOCK_SIZE;
      write_ptr = (unsigned char*) &sg_request;
      do {
	 this_write_len = write( out_fd, write_ptr, expected_len );
	 if (this_write_len < 0) {
	    perror ("pipe_to_cd: write");
	    return -1;
	 }
	 else if (this_write_len >= 0 && this_write_len != expected_len) {
	    fprintf (stderr, "After writing %d bytes, only wrote %d bytes to CD (should have written %d).\n",
		     bytes_written, this_write_len, sizeof(struct sg_header) + 6 + WRITE_BLOCK_SIZE);
	 }
	 expected_len -= this_write_len;
	 write_ptr += this_write_len;
	 bytes_written += this_write_len;
      } while (expected_len != 0 &&
	       ((max_bytes_to_copy == -1) ||
		(total_bytes_read < max_bytes_to_copy)));
      *reply_len = read (out_fd, rep, sizeof(struct sg_reply));
      quiet_request_sense (out_fd, iteration++, "pipe_to_cd", reply_len, rep);
   } while ((this_len > 0 || errno == EAGAIN) &&
	(max_bytes_to_copy == -1 || total_bytes_read < max_bytes_to_copy));
   printf ("Total bytes written: %d.\n", bytes_written);
   return 0;
}

int
test_unit_ready (int fd, int *reply_len, struct sg_reply *rep) {
   *reply_len = sizeof(struct sg_reply);
   return send_request (fd, "test_unit_ready", reply_len, rep, 6,
			TEST_UNIT_READY,/* 0 */
			0,		/* 1 */
			0,0,0,0		/* 2..5 */);
}

int
rezero_unit (int fd, int *reply_len, struct sg_reply *rep) {
   *reply_len = sizeof(struct sg_reply);
   return send_request (fd, "rezero_unit", reply_len, rep, 6,
			REZERO_UNIT, 	/* 0 */
			0,		/* 1 */
			0,0,0,0		/* 2..5 */);
}

int
start_stop (int fd, int start, int *reply_len, struct sg_reply *rep) {
   *reply_len = sizeof( struct sg_reply );
   return send_request (fd, "start_stop", reply_len, rep, 6,
			START_STOP,	/* 0 */
			1,		/* 1 */
			0,0,		/* 2..3 */
			start,		/* 4 */
			0		/* 5 */);
}

int
synchronize_cache (int fd, int *reply_len, struct sg_reply *rep) {
   *reply_len = sizeof( struct sg_reply );
   return send_request (fd, "synchronize_cache", reply_len, rep, 10,
			SYNCRONIZE_CACHE,	/* 0 */
			0,0,0,0,0,	/* 1..5 */
			0,		/* 6 */
			0,0,0		/* 7..9 */);
}

int
set_removable (int fd, int removable, int *reply_len, struct sg_reply *rep) {
   *reply_len = sizeof( struct sg_reply );
   return send_request (fd, "set_removable", reply_len, rep, 6,
			ALLOW_MEDIUM_REMOVAL,	/* 0 */
			0,0,0,	/* 1..3 */
			!removable, /* 4 */
			0	/* 5 */);
}

int
fixation (int fd, int *reply_len, struct sg_reply *rep) {
   *reply_len = sizeof( struct sg_reply );
   return send_request (fd, "fixation", reply_len, rep, 10,
			FIXATION, /* 0 */
			0,0,0,0,0,0,0, /* 1..7 */
			1,	/* 8 */
			0,0	/* 9..10 */);
}

int
recover (int fd, int *reply_len, struct sg_reply *rep) {
   *reply_len = sizeof( struct sg_reply );
   return send_request (fd, "recover", reply_len, rep, 10,
			RECOVER, /* 0 */
			0,0,0,0,0,0,0, /* 1..7 */
			0,	/* 8 */
			0,0	/* 9..10 */);
}

int
medium_load_unload (int fd, int load, int *reply_len, struct sg_reply *rep) {
   *reply_len = sizeof( struct sg_reply );
   return send_request (fd, "load_unload", reply_len, rep, 10,
			LOAD_UNLOAD, /* 0 */
			1,0,0,0,0,0,0, /* 1..7 */
			!load,	/* 8 */
			0,0	/* 9..10 */);
}

int
inquiry (int fd, int *reply_len, struct sg_reply *rep, char * manu, 
	 char * model) {
  int result;
  int i;
  char * reply;
   *reply_len = sizeof( struct sg_reply );
  result = send_request (fd, "inquiry", reply_len, rep, 6,
			INQUIRY, /* 0 */
			0,0,0,255,0);
  for(i=15; i>8; i--)
    if(rep->bytes[i] != ' ') break;
  reply = &rep->bytes[8];
  while(i-->=8) *manu++ = *reply++;
  *manu = 0;

  for(i=31; i>16; i--)
    if(rep->bytes[i] != ' ') break;
  reply = &rep->bytes[16];
  while(i-->=16) *model++ = *reply++;
  *model = 0;

  return result;
}

int
request_sense (int fd, int *reply_len, struct sg_reply *rep) {
   *reply_len = 18;
   return send_request (fd, "request_sense", reply_len, rep, 6,
			REQUEST_SENSE, /* 0 */
			1,0,0,18,0 /* 1..5 */);
}

int
quiet_active_request_sense (int fd, int iteration, char *note,
		     int *reply_len, struct sg_reply *rep) {
   const int saved_autosense = auto_request_sense;
   *reply_len = 18;

   if (!auto_request_sense) return;
   auto_request_sense = 0;
   return send_request (fd, NULL, reply_len, rep, 6,
			REQUEST_SENSE, /* 0 */
			1,0,0,18,0 /* 1..5 */);
}

int
quiet_request_sense (int fd, int iteration, char *note,
		     int *reply_len, struct sg_reply *rep) {
   int result;
   const int saved_autosense = auto_request_sense;
   *reply_len = 18;
   if (rep->header.sense_buffer[2] & 0xF || rep->header.result != 0) {
      if (iteration != 0) {
	 fprintf (stderr, "SENSE_ERROR iter %d: ", iteration);
      }
      print_reply( note, *reply_len, rep);
#if 0
      quiet_active_request_sense (fd, iteration, "request_sense", reply_len, rep);
#endif
   }
   return 0;
}

int
read_capacity (int fd, int *reply_len, struct sg_reply *rep) {
   int result;
   *reply_len = sizeof( struct sg_reply );
   
   result= send_request (fd, "read_capacity", reply_len, rep, 10,
			READ_CAPACITY, /* 0 */
			0,0,0,0,0,0,0,0,0 /* 1..9 */);
   printf ("Capacity = %d blocks, block length = %d (0x%x).\n",
	   (rep->bytes[0] << 24) + (rep->bytes[1] << 16) +
	   (rep->bytes[2] << 8) + rep->bytes[3],
	   (rep->bytes[4] << 24) + (rep->bytes[5] << 16) +
	   (rep->bytes[6] << 8) + rep->bytes[7],
	   (rep->bytes[4] << 24) + (rep->bytes[5] << 16) +
	   (rep->bytes[6] << 8) + rep->bytes[7]);

}

int
reserve_track (int fd, unsigned long len,
	       int *reply_len, struct sg_reply *rep) {
   *reply_len = sizeof( struct sg_reply );
   return send_request (fd, "reserve_track", reply_len, rep, 10,
			RESERVE_TRACK, /* 0 */
			0,0,0,0, /* 1..4 */
			(len >> 24) & 0xFF, /* 5 */
			(len >> 16) & 0xFF, /* 6 */
			(len >> 8) & 0xFF, /* 7 */
			len & 0xFF, /* 8 */
			0	/* 9 */);
}

int
mode_select( int fd, int *reply_len, int dummy, int speed,
	    struct sg_reply *rep) {
   int result;
   *reply_len = sizeof( struct sg_reply );
   send_request (fd, "mode_select6#1", reply_len, rep, 26,
			MODE_SELECT, /* 0 */
			0x10,	/* 1 : page format bit */
			0, 0,	/* 2..3 */
			20,	/* 4 : parameter list length? */
			0,	/* 5 */

			/* Mode select header: */
			0,	/* 6+0 reserved */
			0,	/* 6+1 medium type */
			0,	/* 6+2 res<<7 + host_application_code */
			0,	/* 6+3 block descriptor length */

			/* Mode Page 0x21 */
			0x21,	/* 6+4+0 page code */
			14,	/* 6+4+1 paramter length? */
			0,	/* 6+4+2 reserved */
			1,	/* 6+4+3 2048-byte non-mixed CDROM sectors */
			0,	/* 6+4+4 create new track ??? */
			0,0,0,0,0, /* reserved... */
			0,0,0,0,0, /* reserved */
			0	/* 6+4+15, total byte count = 26 */);
   *reply_len = sizeof( struct sg_reply );
   return send_request (fd, "mode_select6#2", reply_len, rep, 18,
			MODE_SELECT, /* 0 */
			0x10,	/* 1 : page format bit */
			0, 0,	/* 2..3 */
			12,	/* 4 : parameter list length? */
			0,	/* 5 */

			/* Mode select header: */
			0,	/* 6+0 reserved */
			0,	/* 6+1 medium type */
			0,	/* 6+2 res<<7 + host_application_code */
			0,	/* 6+3 block descriptor length */

			/* Mode Page 0x23 */
			0x23,	/* 6+4+0 page code */
			6,	/* 6+4+1 paramter length? */
			speed,	/* 6+4+2 speed, 1 = audio, 2 = double speed */
			dummy,	/* 6+4+3 1 = dummy write, 0 = real */
			0,0,0,0 /* ...6+4+7 reserved, total byte count = 18 */
			);
}

main( int argc, char **argv ) {
   int fd;
   struct sg_reply reply;
   char manufacturer[10];
   char model[20];
   int reply_len;
   int speed_factor = 1;
   int dummy_write = 0;
   unsigned long timeout = 24000;
				/* default timeout = 1 minute, but it */
				/* takes slightly longer than that to */
				/* write the leadin and leadout. (The */
				/* fixation command.)  So, we set it */
				/* to 4 minutes. */

   while (argc > 2) {
      if (strcmp(argv[1], "-bytes") == 0) {
	 max_bytes_to_copy = atoi(argv[2]);
	 argv += 2;
	 argc -= 2;
      }
      if (strcmp(argv[1], "-dummy") == 0) {
	 dummy_write = 1;
	 argv++;
	 argc--;
      }
      else if (strcmp(argv[1], "-speed") == 0) {
	 switch (speed_factor = atoi(argv[2])) {
	  case 1:
	  case 2:
	    argv += 2;
	    argc -= 2;
	    break;
	  default:
	    fprintf (stderr, "%d is not a support speed factor, must be 1 (audio speed recording) or 2 (double speed recording).\n");
	    exit(1);
	 }
      }
   }
   if (argc != 2) {
      fprintf (stderr,
	       "Usage: cdwrite [-bytes byte_count] [-dummy] [-speed speed_factr] scsi_device < cdrom_file\n");
      exit(1);
   }
 
   bzero (&reply, sizeof(struct sg_reply));
   if ( (fd = open (argv[1], O_RDWR)) < 0 ) {
      perror ("opening scsi device");
      exit (4);
   }

   set_timeout(fd, timeout);

   /* First make sure we know how to talk to this writer */
   inquiry (fd, &reply_len, &reply, &manufacturer[0], &model[0]);
   if(strcmp(manufacturer,"PHILIPS") && strcmp(manufacturer,"IMS")) {
     fprintf(stderr,"Requested device is %s [%s] - not a supported CDROM writer.\n",
	     manufacturer, model);
     exit(1);
   }

   medium_load_unload (fd, 1, &reply_len, &reply);
   medium_load_unload (fd, 1, &reply_len, &reply);
   set_removable (fd, 0, &reply_len, &reply);
   start_stop (fd, 1, &reply_len, &reply);
   rezero_unit (fd, &reply_len, &reply);
   test_unit_ready (fd, &reply_len, &reply);
   start_stop (fd, 1, &reply_len, &reply);
   mode_select (fd, &reply_len, dummy_write, speed_factor, &reply);
   /* reserve_track( fd, (buf.st_size + 2047) / 2048, &reply_len, &reply); */
   write_track (fd, &reply_len, &reply);
   pipe_to_cd (0, fd, &reply_len, &reply);
   synchronize_cache (fd, &reply_len, &reply);
   fixation(fd, &reply_len, &reply);
   synchronize_cache (fd, &reply_len, &reply);
   start_stop (fd, 0, &reply_len, &reply);
   set_removable (fd, 1, &reply_len, &reply);
   medium_load_unload (fd, 0, &reply_len, &reply);
   medium_load_unload (fd, 0, &reply_len, &reply);
   close(fd);
   return 0;
}

alt_main( int argc, char **argv ) {
   int fd;
   struct sg_reply reply;
   int reply_len;

   bzero (&reply, sizeof(struct sg_reply));
   if ( (fd = open (argv[1], O_RDWR)) < 0 ) {
      perror ("open");
      exit (1);
   }

   medium_load_unload (fd, 1, &reply_len, &reply);
   medium_load_unload (fd, 1, &reply_len, &reply);
   read_capacity (fd, &reply_len, &reply);
   medium_load_unload (fd, 0, &reply_len, &reply);
   medium_load_unload (fd, 0, &reply_len, &reply);
   close(fd);
   return 0;
}
