// KreateCD - CD recording software for the K desktop environment
//
// 1999 by Alexander Feigl <Alexander.Feigl@gmx.de>
//
// This file is subject to the terms and conditions of the GNU General      
// Public License.  See the file COPYING in the main directory of the       
// KreateCD distribution for more details.                                     

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef USE_IOCTL_CALLS
#include <sys/ioctl.h>
#endif

#include <string.h>

#ifdef USE_IOCTL_CALLS
#include <linux/cdrom.h>
#endif

#define BLOCK_SIZE 2048
#define BUFFER_SIZE (BLOCK_SIZE*100)

#define ISODCL(from, to) (to - from + 1)

static int padflag;

#ifndef USE_IOCTL_CALLS
static long int lastpos=-1;
#endif

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


static int isonum_733 (char * p)
{
	return (isonum_731 (p));
}

struct iso_primary_descriptor {
	unsigned char type			[ISODCL (  1,   1)]; /* 711 */
	unsigned char id			[ISODCL (  2,   6)];
	unsigned char version			[ISODCL (  7,   7)]; /* 711 */
	unsigned char unused1			[ISODCL (  8,   8)];
	unsigned char system_id			[ISODCL (  9,  40)]; /* aunsigned chars */
	unsigned char volume_id			[ISODCL ( 41,  72)]; /* dunsigned chars */
	unsigned char unused2			[ISODCL ( 73,  80)];
	unsigned char volume_space_size		[ISODCL ( 81,  88)]; /* 733 */
	unsigned char unused3			[ISODCL ( 89, 120)];
	unsigned char volume_set_size		[ISODCL (121, 124)]; /* 723 */
	unsigned char volume_sequence_number	[ISODCL (125, 128)]; /* 723 */
	unsigned char logical_block_size	[ISODCL (129, 132)]; /* 723 */
	unsigned char path_table_size		[ISODCL (133, 140)]; /* 733 */
	unsigned char type_l_path_table		[ISODCL (141, 144)]; /* 731 */
	unsigned char opt_type_l_path_table	[ISODCL (145, 148)]; /* 731 */
	unsigned char type_m_path_table		[ISODCL (149, 152)]; /* 732 */
	unsigned char opt_type_m_path_table	[ISODCL (153, 156)]; /* 732 */
	unsigned char root_directory_record	[ISODCL (157, 190)]; /* 9.1 */
	unsigned char volume_set_id		[ISODCL (191, 318)]; /* dunsigned chars */
	unsigned char publisher_id		[ISODCL (319, 446)]; /* achars */
	unsigned char preparer_id		[ISODCL (447, 574)]; /* achars */
	unsigned char application_id		[ISODCL (575, 702)]; /* achars */
	unsigned char copyright_file_id		[ISODCL (703, 739)]; /* 7.5 dchars */
	unsigned char abstract_file_id		[ISODCL (740, 776)]; /* 7.5 dchars */
	unsigned char bibliographic_file_id	[ISODCL (777, 813)]; /* 7.5 dchars */
	unsigned char creation_date		[ISODCL (814, 830)]; /* 8.4.26.1 */
	unsigned char modification_date		[ISODCL (831, 847)]; /* 8.4.26.1 */
	unsigned char expiration_date		[ISODCL (848, 864)]; /* 8.4.26.1 */
	unsigned char effective_date		[ISODCL (865, 881)]; /* 8.4.26.1 */
	unsigned char file_structure_version	[ISODCL (882, 882)]; /* 711 */
	unsigned char unused4			[ISODCL (883, 883)];
	unsigned char application_data		[ISODCL (884, 1395)];
	unsigned char unused5			[ISODCL (1396, 2048)];
};

// reads cdrom with ioctl - WARNING BUFFER MUST BE N*BLOCK_SIZE BYTES
static int read_cdrom(int file,char *buffer,long int size,long int lba)
 {
#ifdef USE_IOCTL_CALLS
  struct cdrom_msf mymsf;
  long int startmsf;
#endif
  char *xbuffer;
  long int xsize;
#ifdef USE_IOCTL_CALLS
  startmsf=lba+CD_MSF_OFFSET;
#endif
  xbuffer=buffer;
  xsize=size;
  while (xsize>0)
   {
    if (padflag)
     {
      memset(xbuffer,xsize,0);
      break;
     }
#ifdef USE_IOCTL_CALLS
    mymsf.cdmsf_min0=startmsf/(60*75);
    mymsf.cdmsf_sec0=(startmsf/75)%60;
    mymsf.cdmsf_frame0=startmsf%75;
    memcpy(xbuffer,&mymsf,sizeof(mymsf));
    if (ioctl(file,CDROMREADMODE1,xbuffer)!=0)
#else
    if (lastpos!=lba*BLOCK_SIZE)
     {
      if (lseek(file,lba*BLOCK_SIZE,SEEK_SET)==-1)
       {
        return(0);
       }
      lastpos=lba*BLOCK_SIZE;
     }
    if (read(file,xbuffer,BLOCK_SIZE)!=BLOCK_SIZE)
#endif
     {
      int xchar;
#ifdef USE_IOCTL_CALLS
      fprintf(stderr,"#READ# %ld\n",startmsf-CD_MSF_OFFSET);
#else
      fprintf(stderr,"#READ# %ld\n",lba);
#endif
      xchar=getc(stdin);
      switch (xchar)
       {
        case 'R':
          continue;
        case 'I':
          memset(xbuffer,0,BLOCK_SIZE);
          break;
        case 'P':
          memset(xbuffer,0,BLOCK_SIZE);
          padflag=1;
          break;

        default:
          return(-1);
       }
     }
#ifdef USE_IOCTL_CALLS
    ++startmsf;
#else
    ++lba;
#endif
    xsize-=BLOCK_SIZE;
    xbuffer+=BLOCK_SIZE;
   }
  return(size);
 }


static long int getISOBlocks(int cdrom)
 {
  long int file_addr;
  long int span;
  struct iso_primary_descriptor ipd;

  file_addr = 16 << 11;
  if (read_cdrom(cdrom, (char *) &ipd, sizeof(ipd),16)!=sizeof(ipd)) return(-1);
  if (strncmp((char *) ipd.id,"CD001",5)!=0)
   {
    return(-1);
   }
  span=isonum_733((char *) ipd.volume_space_size);
  return(span);
 }



static int readCDROM(int cdrom,long int start_block,long int iso_size,char *outfile,int isomode)
 {
  char *buffer;
  long int oldoffset,offset,rest,thisx,cblock;
  int ifile;

  buffer=(char *) malloc(BUFFER_SIZE);
  if (buffer==NULL)
   {
    fprintf(stderr,"#ERROR# Memory\n");
    return(20);
   }

  ifile=open(outfile,O_WRONLY|O_CREAT|O_TRUNC,0644);
  if (ifile==-1)
   {
    free(buffer);
    fprintf(stderr,"#ERROR# Write\n");
    return(20);
   }

  offset=0;
  oldoffset=0;
  cblock=start_block;

  rest=iso_size*BLOCK_SIZE;
  while (rest)
   {
    int rsize;

    thisx= (rest>BUFFER_SIZE)?BUFFER_SIZE:rest;

    rsize=read_cdrom(cdrom,buffer,thisx,cblock);
    if (rsize!=thisx)
     {
      if (rsize!=-1)
       {
        rest-=rsize;
        if (write(ifile,buffer,thisx)!=thisx)
         {
          fprintf(stderr,"#ERROR# Write\n");
          free(buffer);
          close(ifile);
          remove(outfile);
          return(20);
         }

        if ( (!isomode) && ( (rest/BLOCK_SIZE/75)<=3) )
         {
          if ( (rest/BLOCK_SIZE/75)<=3) break; /* workaround for inexact size */
         }
       }
      fprintf(stderr,"#ERROR# Read\n");
      free(buffer);
      close(ifile);
      remove(outfile);
      return(20);
     }
    cblock+=(rsize/BLOCK_SIZE);
    if (write(ifile,buffer,thisx)!=thisx)
     {
      fprintf(stderr,"#ERROR# Write\n");
      free(buffer);
      close(ifile);
      remove(outfile);
      return(20);
     }
    
    rest-=thisx;
    offset+=thisx;
    if ((oldoffset+1024*500)< offset)
     {
      oldoffset=offset;
      fprintf(stderr,"#PROGRESS# %ld %ld\n",offset/BLOCK_SIZE,iso_size);
      oldoffset=offset;
     }
   }

  close(ifile);
  free(buffer);
  return(0);
 }





int readiso(int argc,char **argv)
 {
  int cdrom,err,isomode;
  long start,size,isosize;
  if (argc!=5) return(20);


  padflag=0;
  cdrom=open(argv[1],O_RDONLY);
  if (cdrom==-1)
   {
    fprintf(stderr,"#ERROR# Device\n");
    return(20);
   } 

  start=strtol(argv[2],NULL,10);
  size=strtol(argv[4],NULL,10);

  isosize=getISOBlocks(cdrom);
  if (isosize!=-1)
   {
    if (isosize<size) size=isosize;
   }

  isomode=(isosize!=-1)?1:0;
  err=readCDROM(cdrom,start,size,argv[3],isomode);
  if (err) return(err);
  fprintf(stderr,"#OK#\n");

  return(0);
 }
