/*
 * growisofs 3.3 by Andy Polyakov <appro@fy.chalmers.se>.
 *
 * Use-it-on-your-own-risk, GPL bless...
 *
 * This is a front-end to mkisofs(8) which makes it possible to append
 * data to isofs volumes residing on random write access media like
 * DVD+RW, DVD-RAM, etc. Idea is very simple. Append the new data as it
 * were added to a multisession media and then copy the new volume
 * descriptor(s) to the beginning of volume thus updating the root
 * catalog reference(s)...
 *
 * For further details see http://fy.chalmers.se/~appro/linux/DVD+RW/.
 *
 * Revision history:
 *
 * 1.1:
 * - flush cache before copying volume descriptors;
 * 2.0:
 * - support for /dev/raw*;
 * - support for set-root-uid operation (needed for /dev/raw*);
 * - support for first "session" burning (needed for "poor-man");
 * - "poor-man" support for those who don't want to recompile the
 *   kernel;
 * 2.1:
 * - mkisofs_pid typo;
 * 2.2:
 * - uninitialized in_device variable;
 * - -help option;
 * 3.0:
 * - support for DVD+R;
 * 3.1:
 * - -Z fails if a file system is present and stdin is not a tty;
 * 3.2:
 * - support for image burning (needed for DVD+R as you can't use dd);
 * 3.3:
 * - 'growisofs -Z /dev/scdN image.iso' is too confusing, implement
 *   'growisofs -Z /dev/scdN=image.iso' instead.
 *
 * * Thu Oct 24 2002 Jörg P. M. Haeger <Joerg.Haeger@Uni-Bielefeld.de>
 * - get uid by geteuid instead of getuid to enable the use of s bit
 *
 * * Mon Oct 28 2002 Jörg P. M. Haeger <Joerg.Haeger@Uni-Bielefeld.de>
 * - added a "--force" argument to force the overwriting of an existing
 *   iso file system
 */

#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64

/*
 * I fail to understand why do I have to define this. I mean why doesn't
 * compiler driver define something of this sort?
 */
#define _GNU_SOURCE

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <linux/iso_fs.h>
#include <linux/cdrom.h>
#include <sys/mman.h>
#include <linux/raw.h>
#include <signal.h>

#define CD_BLOCK	((off64_t)2048)
#define VOLDESC_OFF	16
#define DVD_BLOCK	(32*1024)

static unsigned int from_733 (unsigned char *s)
{ unsigned int ret=0;
    ret |= s[0];
    ret |= s[1]<<8;
    ret |= s[2]<<16;
    ret |= s[3]<<24;
  return ret;
}

static void to_733 (unsigned char *s,unsigned int val)
{   s[0] = (val)     & 0xFF;
    s[1] = (val>>8)  & 0xFF;
    s[2] = (val>>16) & 0xFF;
    s[3] = (val>>24) & 0xFF;
    s[4] = (val>>24) & 0xFF;
    s[5] = (val>>16) & 0xFF;
    s[6] = (val>>8)  & 0xFF;
    s[7] = (val)     & 0xFF;
}

char *find_raw_device(int fd, char *device)
{ int   i,rawctl,dev_major,dev_minor;
  char  *ret=NULL;
  struct raw_config_request req;
  struct stat sb;
  static char rawdevname[16] = "";

    if (rawdevname[0]) return rawdevname;

    if (fstat (fd,&sb) < 0)
	perror (":-( unable to fstat"),
	exit (1);

    if (!S_ISBLK(sb.st_mode)) return NULL;

    ret = device;
    dev_major = major (sb.st_rdev);
    dev_minor = minor (sb.st_rdev);

    if ((rawctl=open ("/dev/rawctl",O_RDONLY)) < 0) return ret;

    for (i=1;i<256;i++)
    {	req.raw_minor = i;
        if (ioctl(rawctl,RAW_GETBIND,&req) < 0) break;
	if (req.block_major == dev_major && req.block_minor == dev_minor)
	{   sprintf (rawdevname,"/dev/raw/raw%d",i);
	    ret = rawdevname;
	    break;
	}
    }
    close (rawctl);

  return ret;
}

static ssize_t (*pwrite64_method) (int,const void *,size_t,off64_t) = pwrite64;
int   media_written = 0, media_fd = -1, poor_man=0, mmc_profile=-1;
char *media_device;

ssize_t poor_mans_pwrite64 (int fd,const void *buff,size_t size,off64_t foff)
{ struct cdrom_generic_command cgc;
  struct request_sense sense;
  unsigned int lba;

    if (foff&2047 || size&2047)	/* 2K block size	*/
	return -1;

    lba = foff>>11;
    memset (&cgc,0,sizeof(cgc));
    cgc.data_direction = CGC_DATA_WRITE;
    cgc.buffer = (void *)buff;
    cgc.buflen = size;
    cgc.sense  = &sense;
    cgc.cmd[0] = 0x2A;  	/* WRITE(10)		*/
    cgc.cmd[2] = (lba>>24)&0xff;/* Logical Block Addrss	*/
    cgc.cmd[3] = (lba>>16)&0xff;
    cgc.cmd[4] = (lba>>8)&0xff;
    cgc.cmd[5] = lba&0xff;
    cgc.cmd[7] = (size>>19)&0xff;
    cgc.cmd[8] = (size>>11)&0xff;
    if (ioctl (fd,CDROM_SEND_PACKET,&cgc))
    {	fprintf (stderr,":-[ SENSE KEY=%xh/ASC=%xh/ASCQ=%xh ]\n",
			sense.sense_key,sense.asc,sense.ascq);
	return -1;
    }
    media_written = 1;

  return size;
}

void plus_rw_finalize ()
{   if (media_written)
    { struct cdrom_generic_command cgc;

	fprintf (stderr,"%s: flushing cache\n",media_device);
	memset (&cgc,0,sizeof(cgc));
	cgc.data_direction = CGC_DATA_NONE;
	cgc.cmd[0] = 0x35;	/* FLUSH CACHE		*/
	if (ioctl (media_fd,CDROM_SEND_PACKET,&cgc))
	    perror (":-( unable to FLUSH CACHE");

	fprintf (stderr,"%s: stopping de-icing\n",media_device);
	memset (&cgc,0,sizeof(cgc));
	cgc.data_direction = CGC_DATA_NONE;
	cgc.cmd[0] = 0x5B;	/* CLOSE TRACK/SESSION	*/
	cgc.cmd[2] = 0;		/* "Stop De-Icing"	*/
	if (ioctl (media_fd,CDROM_SEND_PACKET,&cgc))
	    perror (":-( unable to CLOSE SESSION");

	fprintf (stderr,"%s: writing lead-out\n",media_device);
	memset (&cgc,0,sizeof(cgc));
	cgc.data_direction = CGC_DATA_NONE;
	cgc.cmd[0] = 0x5B;	/* CLOSE TRACK/SESSION	*/
	cgc.cmd[2] = 0x02;	/* "Close session"	*/
	if (ioctl (media_fd,CDROM_SEND_PACKET,&cgc))
	    perror (":-( unable to CLOSE SESSION");
	media_written = 0;
    }
    _exit (errno);
}

void plus_r_finalize ()
{   if (media_written)
    { struct cdrom_generic_command cgc;

	fprintf (stderr,"%s: flushing cache\n",media_device);
	memset (&cgc,0,sizeof(cgc));
	cgc.data_direction = CGC_DATA_NONE;
	cgc.cmd[0] = 0x35;	/* FLUSH CACHE		*/
	if (ioctl (media_fd,CDROM_SEND_PACKET,&cgc))
	    perror (":-( unable to FLUSH CACHE");

	fprintf (stderr,"%s: stopping de-icing\n",media_device);
	memset (&cgc,0,sizeof(cgc));
	cgc.data_direction = CGC_DATA_NONE;
	cgc.cmd[0] = 0x5B;	/* CLOSE TRACK/SESSION	*/
	cgc.cmd[2] = 0x01;	/* "Close track"	*/
	cgc.cmd[5] = 0xFF;
	if (ioctl (media_fd,CDROM_SEND_PACKET,&cgc))
	    perror (":-( unable to CLOSE SESSION");

	fprintf (stderr,"%s: writing lead-out\n",media_device);
	memset (&cgc,0,sizeof(cgc));
	cgc.data_direction = CGC_DATA_NONE;
	cgc.cmd[0] = 0x5B;	/* CLOSE TRACK/SESSION	*/
	cgc.cmd[2] = 0x02;	/* "Close session"	*/
	cgc.cmd[5] = 0xFF;
	if (ioctl (media_fd,CDROM_SEND_PACKET,&cgc))
	    perror (":-( unable to CLOSE SESSION");
	media_written = 0;
    }
    _exit (errno);
}

int get_mmc_profile (int fd)
{ struct cdrom_generic_command cgc;
  unsigned char buf[8];

    memset (&cgc,0,sizeof(cgc));
    cgc.buffer = buf;
    cgc.buflen = 8;
    cgc.data_direction = CGC_DATA_READ;
    cgc.cmd[0] = 0x46;  /* GET CONFIGURATION    */
    cgc.cmd[8] = 8;
    if (ioctl (fd,CDROM_SEND_PACKET,&cgc))
	perror (":-( unable to GET CONFIGURATION"),
	exit (1);

  return buf[6]<<8|buf[7];
}

int setup_C_parm (int fd,char *C_parm,struct iso_primary_descriptor *descr)
{ unsigned int next_session, prev_session;

    if (!poor_man || mmc_profile==0x1A)
    {	next_session = from_733(descr->volume_space_size);
	/* pad to closest 32K boundary for optimal performance */
	next_session += 15;
	next_session /= 16;
	next_session *= 16;
	sprintf (C_parm,"16,%u",next_session);
    }
    else if (mmc_profile==0x1B)
    { struct cdrom_generic_command cgc;
      struct request_sense sense;
      unsigned char buf[36];

	memset (&cgc,0,sizeof(cgc));
	cgc.buffer = buf;
	cgc.buflen = sizeof(buf);
	cgc.data_direction = CGC_DATA_READ;
	cgc.sense=&sense; memset (&sense,0,sizeof(sense));
	cgc.cmd[0] = 0x52;  /* GET TRACK INFORMATION	*/
	cgc.cmd[1] = 1;
	cgc.cmd[5] = 0xFF;
	cgc.cmd[8] = sizeof(buf);
	if (ioctl (fd,CDROM_SEND_PACKET,&cgc))
	{   perror (":-( unable to GET TRACK INFORMATION");
	    if (sense.asc==0x24) /* hp dvd200i returns 24 if media is full */
	        fprintf (stderr,":-( media must be full already\n");
	    exit (1);
	}
	next_session  = buf[12]<<24;
	next_session |= buf[13]<<16;
	next_session |= buf[14]<<8;
	next_session |= buf[15];

    	memset (&cgc,0,sizeof(cgc));
	cgc.buffer = buf;
	cgc.buflen = 12;
	cgc.data_direction = CGC_DATA_READ;
	cgc.cmd[0] = 0x43;  /* READ TOC	*/
	cgc.cmd[2] = 1;
	cgc.cmd[8] = 12;
	if (ioctl (fd,CDROM_SEND_PACKET,&cgc))
	    perror (":-( unable to READ TOC"),
	    exit (1);

	prev_session  = buf[8]<<24;
	prev_session |= buf[9]<<16;
	prev_session |= buf[10]<<8;
	prev_session |= buf[11];

	sprintf (C_parm,"%d,%d",prev_session+16,next_session);
    }

  return next_session;
}

int builtin_dd (int infd,int outfd,off64_t outoff)
{ char *buffer;
  int   n,off;

    buffer = mmap (NULL,DVD_BLOCK,PROT_READ|PROT_WRITE,
			MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
    if (buffer == MAP_FAILED)
	fprintf (stderr,"unable to anonymously mmap %d?\n",DVD_BLOCK),
	exit(1);

    off = 0;
    while ((n=read (infd,buffer+off,DVD_BLOCK-off)) > 0)
    {	off += n;
	if (off == DVD_BLOCK)
	{   off = 0;
	    if ((n=(*pwrite64_method) (outfd,buffer,DVD_BLOCK,outoff))
			!= DVD_BLOCK)
	    {	if (n>0)	errno = EIO;
		else if (n==0)	errno = ENOSPC;
		n = -1;
		break;
	    }
	    outoff += DVD_BLOCK;
	}
    }
    if (off)
    {	memset (buffer+off,0,DVD_BLOCK-off);
	if ((*pwrite64_method) (outfd,buffer,DVD_BLOCK,outoff) != DVD_BLOCK)
	{    if (n>0)		errno = EIO;
	     else if (n==0)	errno = ENOSPC;
	     n = -1;
	}
	outoff += DVD_BLOCK;
    }

    munmap (buffer,DVD_BLOCK);

  return n;
}

void pipe_mkisofs_up (char *mkisofs_argv[],int outfd,off64_t outoff)
{ pid_t mkisofs_pid,pid;
  int fildes[2],ret,n;

    if (pipe (fildes) < 0)
        perror (":-( unable to create pipe"), exit(1);

    if ((mkisofs_pid=fork ()) == -1)
	perror (":-( unable to fork mkisofs"), exit(1);
    else if (mkisofs_pid == 0)
    {	dup2  (fildes[1],1);
	close (fildes[0]);
	close (fildes[1]);
	close (outfd);
	execvp ("mkisofs",mkisofs_argv);
	perror (":-( unable to execute mkisofs"), exit (1);
    }

    close (fildes[1]);

    n=builtin_dd(fildes[0],outfd,outoff);

    if (n==0) /* mkisofs must have finished, consume the exit code */
    {	if ((waitpid (mkisofs_pid,&ret,0)) == -1)
	    perror (":-( waitpid failed"), exit (1);

	if (!WIFEXITED(ret) || WEXITSTATUS(ret)!=0)
	    fprintf (stderr,":-( mkisofs failed: %d\n",errno=ret), exit (1);
    }
    else if (n<0)
    {	perror (":-( write failed"), exit (1);   }
}

#define MAX_IVDs	4
#define IVDs_SIZE	(sizeof(struct iso_primary_descriptor)*MAX_IVDs)

int main (int argc, char *argv[])
{ int  infd,outfd,imgfd=-1;
  char *in_device=NULL,*out_device=NULL,*in_image=NULL;
  char dev_found;
  int  i,dry_run=0,force=0,warn_for_isofs=0;
  char **mkisofs_argv,C_parm[24],M_parm[16];
  int  mkisofs_argc;
  unsigned int next_session,new_size;
  struct iso_primary_descriptor *buffer;
  uid_t uid=geteuid();

    /* mmap buffer so that we can use it with /dev/raw/rawN */
    buffer = mmap (NULL,2*IVDs_SIZE,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0);
    if (buffer == MAP_FAILED)
	fprintf (stderr,":-( unable to anonymously mmap %d: ",2*IVDs_SIZE),
	perror (NULL), exit (1);

    mkisofs_argv = malloc ((argc+3)*sizeof(char *));
    if (mkisofs_argv == NULL)
        fprintf (stderr,":-( unable to allocate %d: ",(argc+3)*sizeof(char *)),
	perror (NULL), exit (1);

    mkisofs_argv[0] = "mkisofs", mkisofs_argc = 1;

    for (i=1;i<argc;i++)
    { int len=strlen(argv[i]);
	dev_found = '\0';
	if (argv[i][0] == '-')
	{   if (argv[i][1] == 'M')
	    {	if (len > 2) in_device = argv[i]+2;
		else	     in_device = argv[++i];
		dev_found = 'M';
	    }
	    else if (!strncmp(argv[i],"-prev-session",13))
	    {	if (len > 13) in_device = argv[i]+13;
		else          in_device = argv[++i];
		dev_found = 'M';
	    }
	    else if (argv[i][1] == 'Z')
	    {	if (len > 2) in_device = argv[i]+2;
		else	     in_device = argv[++i];
		dev_found = 'Z';
	    }
	    else if (!strncmp(argv[i],"-zero-session",13))
	    {	if (len > 13) in_device = argv[i]+13;
		else          in_device = argv[++i];
		dev_found = 'Z';
	    }
	    else if (!strcmp(argv[i],"-poor-man"))
	    {	poor_man = 1;
		continue;
	    }
	    else if (argv[i][1] == 'C' || !strncmp(argv[i],"-cdrecord-params",16))
		continue;
	    else if (argv[i][1] == '#' || !strcmp(argv[i],"-dry-run"))
	    {	dry_run = 1;
		continue;
	    }
		else if (!strcmp(argv[i],"-force"))
			force = 1;
	    else if (argv[i][1] == '?' ||
	    		!strcmp(argv[i],"-help") ||
	    		!strcmp(argv[i],"--help")
		    )
	    {	printf ("usage: %s -[ZM] /dev/cdrom <mkisofs options>\n",argv[0]);
		printf ("       for <mkisofs options> run 'mkisofs %s'\n",argv[i]);
		printf ("\toptions:\n");
		printf ("\t\t--force\toverwrite an existing iso filesystem\n");
		printf ("\n");
		exit (1);
	    }
	    else if (strstr (argv[i],"-version"))
	    {	printf ("growisofs 3.3, front-ending to ");
		fflush (stdout);
		execlp ("mkisofs","mkisofs","-version",NULL);
		perror (":-( unable to execute mkisofs");
		exit (1);
	    }
	}

	if (dev_found && in_device)
	{   if (*in_device == '=') in_device++;

	    if (dev_found == 'Z')
		in_image = strchr(in_device,'=');

	    /*
	     * I ignore return values from set{re}uid calls because if
	     * they fail we have no privileges to care about and should
	     * just proceed anyway...
	     */
	    setreuid (-1,uid);	/* get real, but preserve saved uid */

	    infd=-1;

	    while (in_image)
	    {	*in_image='\0';
		if ((infd = open64 (in_device,O_RDONLY)) < 0)
		    *in_image='=',
		    in_image=strchr(in_image+1,'=');
		else break;
	    }

	    if (infd < 0 && (infd = open64 (in_device,O_RDONLY)) < 0)
		fprintf (stderr,":-( unable to open64(\"%s\",O_RDONLY): ",
				in_device),
		perror (NULL), exit (1);

	    if (in_image)
	    {	in_image++;
		if ((imgfd = open64(in_image,O_RDONLY)) < 0)
		    fprintf (stderr,":-( unable to open64(\"%s\",O_RDONLY): ",
				    in_image),
		perror (NULL), exit(1);
	    }

	    setreuid(-1,0);	/* get root for a /dev/raw sake */

	    out_device=find_raw_device (infd,in_device);
	    if (out_device)	/* original block device or /dev/raw */
	    {	if (out_device == in_device) setuid (uid);
		if ((outfd=open64 (out_device,O_RDWR)) < 0)
		{   if (errno == EROFS)	/* must be unpatched kernel */
		    {	out_device=in_device, outfd=dup(infd), poor_man=1;   }
		    else
		    {	fprintf (stderr,":-( unable to open64(\"%s\",O_RDWR): ",
				out_device),
			perror (NULL), exit (1);
		    }
		}
		setuid (uid);	/* drop all privileges */
	    }
	    else
	    {	setuid (uid);	/* drop all privileges */
		out_device=in_device;
		if ((outfd=open64 (out_device,O_RDWR)) < 0)
		{   fprintf (stderr,":-( unable to open64(\"%s\",O_RDWR): ",
					out_device),
		    perror (NULL), exit (1);
		}
	    }

            if (pread64 (infd,buffer,IVDs_SIZE,VOLDESC_OFF*CD_BLOCK) != IVDs_SIZE
		&& dev_found == 'M')
	        perror (":-( unable to pread64(2) primary volume descriptors"),
		exit (1);

	    if (poor_man)
	    {	mmc_profile = get_mmc_profile (infd);
		if (!(mmc_profile==0x1A || mmc_profile==0x1B))
		    fprintf (stderr,":-( %s: mounted media doesn't appear "
					"to be DVD+RW or DVD+R\n",in_device),
		    exit (1);
	    }

	    if (dev_found == 'M')
            {	if (memcmp (buffer,"\1CD001",5))
	            fprintf (stderr,":-( %s doesn't look like isofs...\n",
		    		in_device), exit(1);

		next_session=setup_C_parm(infd,C_parm,buffer);

		mkisofs_argv[mkisofs_argc++] = "-C";
		mkisofs_argv[mkisofs_argc++] = C_parm;
		sprintf (M_parm,"/dev/fd/%d",infd);
		mkisofs_argv[mkisofs_argc++] = "-M";
		mkisofs_argv[mkisofs_argc++] = M_parm;
		len = 3 + strlen(C_parm) + 3 + strlen(M_parm);
	    }
	    else
	    {	if (!memcmp (buffer,"\1CD001",5))
		    warn_for_isofs = 1;
		next_session = 0;
		continue;
	    }
	}
	else
	{   mkisofs_argv[mkisofs_argc++] = argv[i];   }
    }

    if (in_device == NULL)
        fprintf (stderr,"%s: previous \"session\" device is not specified, "
			"do use -M or -Z option\n",argv[0]),
	exit (1);

    /*
     * If we passed the statement above, then we should know that all
     * privileges were dropped when we were working on -[MZ] options.
     */

    mkisofs_argv[mkisofs_argc] = NULL;

#if 0
    if (mkisofs_argc==2 && next_session==0) do
    { struct stat sb;
        if ((imgfd = open(mkisofs_argv[1],O_RDONLY)) < 0) break;
        if (fstat(imgfd,&sb) < 0)
            perror (":-( unable to fstat"), exit (1);
	if (!S_ISREG(sb.st_mode))
	{   close (imgfd), imgfd=-1; break;   }
	if (pread64 (imgfd,buffer,CD_BLOCK,VOLDESC_OFF*CD_BLOCK) != CD_BLOCK)
	{   close (imgfd), imgfd=-1; break;   }
	if (memcmp(buffer,"\1CD001",5))
	{   close (imgfd), imgfd=-1; break;   }
    } while (0);
#endif

    if (warn_for_isofs)
    {	if (isatty (0) || force)
	    fprintf (stderr,"WARNING: %s already carries isofs!\n",out_device),
	    printf ("About to execute '");
	else
	    fprintf (stderr,"FATAL: %s already carries isofs!\n",out_device),
	    exit(1);
    }
    else
	printf ("Executing '");

    if (imgfd>=0)
        printf ("dd if=%s of=%s obs=32k",in_image,
		poor_man?in_device:out_device);
    else
    {   for (i=0;i<mkisofs_argc;i++) printf ("%s ",mkisofs_argv[i]);
        printf ("| dd of=%s obs=32k seek=%u",
		poor_man?in_device:out_device,next_session/16);
    }
    printf ("'\n");
    fflush (stdout);

    if (dry_run) close (infd), close (outfd), exit (0);

    if (poor_man)
    {	media_fd = dup2 (infd,outfd);
	media_device=out_device=in_device;

	pwrite64_method = poor_mans_pwrite64;

	if (mmc_profile == 0x1A)
	{   atexit (plus_rw_finalize);
	    signal (SIGHUP,plus_rw_finalize);
	    signal (SIGINT,plus_rw_finalize);
	    signal (SIGTERM,plus_rw_finalize);
	    signal (SIGPIPE,plus_rw_finalize);
	}
	else if (mmc_profile == 0x1B)
	{   atexit (plus_r_finalize);
#if 1
	    /* I'm not sure if this is appropriate, but I need
	     * to try certain thing out first... */
	    signal (SIGHUP,plus_r_finalize);
	    signal (SIGINT,plus_r_finalize);
	    signal (SIGTERM,plus_r_finalize);
	    signal (SIGPIPE,plus_r_finalize);
#endif
	}
	else
	    fprintf (stderr,":-( %s: mounted media doesn't appear to be "
	    			"DVD+RW or DVD+R\n",out_device),
	    exit(1);
    }

    if (warn_for_isofs && !force) fprintf (stderr,"Sleeping for 5 sec...\n"), sleep (5);

    if (imgfd>=0)
    {   if (builtin_dd (imgfd,outfd,next_session*CD_BLOCK) < 0)
	    perror (":-( write failed"), exit (1);
	exit(0);
	/* NOT REACHED */
    }

    pipe_mkisofs_up (mkisofs_argv,outfd,next_session*CD_BLOCK);

    if (next_session!=0 && !(poor_man && mmc_profile==0x1B))
    { struct cdrom_generic_command cgc;
	/*
	 * (At least) hp100i needs cache flush in order to proceed with
	 * read at this point. This is being moved to kernel, but it won't
	 * ever harm to do this from user-land.
	 */
	fprintf (stderr,"%s: flushing cache\n",in_device);
	memset (&cgc,0,sizeof(cgc));
	cgc.data_direction = CGC_DATA_NONE;
	cgc.cmd[0] = 0x35;	/* FLUSH CACHE	*/
	ioctl (infd,CDROM_SEND_PACKET,&cgc);

	fprintf (stderr,"%s: copying volume descriptor(s)\n",out_device);

	if (pread64 (outfd,buffer+MAX_IVDs,IVDs_SIZE,
		(next_session+VOLDESC_OFF)*CD_BLOCK) != IVDs_SIZE)
	    perror (":-( unable to pread64(2) primary volume descriptors"),
	    exit (1);

	if (memcmp (buffer+MAX_IVDs,"\1CD001",5))
	    fprintf (stderr,":-( %s:%d doesn't look like isofs!\n",
				out_device,next_session),
	    errno = EINVAL,
	    exit (1);

	new_size = from_733(buffer[MAX_IVDs].volume_space_size) + next_session;
	to_733(buffer[MAX_IVDs].volume_space_size,new_size);

	if ((*pwrite64_method) (outfd,buffer+MAX_IVDs,sizeof(*buffer),
		VOLDESC_OFF*CD_BLOCK) != sizeof(*buffer))
            perror (":-( unable to pwrite64(2) primary volume descriptor"),
	    exit (1);

	/*
	 * copy secondary volume descriptor(s) which are expected to
	 * appear in the very same order
	 */
	for (i=1;i<4;i++)
	{   if (buffer[i].type[0] == (char)ISO_VD_END)	break;
	    if (memcmp (buffer[i].id,"CD001",4))	break;

	    if (buffer[i].type[0] != buffer[4+i].type[0])
	    {	fprintf (stderr,"volume descriptor mismatch, did you "
				"use same mkisofs options?\n");
		break;
	    }
	    to_733(buffer[4+i].volume_space_size,new_size);
	    if ((*pwrite64_method)(outfd,buffer+MAX_IVDs+i,sizeof(*buffer),
			(VOLDESC_OFF+i)*CD_BLOCK) != sizeof(*buffer))
		perror (":-( unable to pwrite64(2) volume descriptor"),
		exit (1);
	}
    }

    errno = 0;
    exit (0);
}
