/*
 * growisofs 5.1 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;
 * 4.0:
 * - transport C++-fication for better portability;
 * - support for -dvd-compat option (improved DVD+R/RW compatibility);
 * - -dvd-video implies -dvd-compat;
 * - support for SONY DRU-500A;
 * - progress indicator for -Z /dev/scdN=image.iso;
 * - agressive -poor-man-ing;
 * 4.1:
 * - uninitialized errno at exit from -Z /dev/scdN=image.iso;
 * 4.2:
 * - don't print initial bogus progress indicator values;
 * - apparently some firmwares exhibit ambiguity in DVD+R disk
 *   finalizing code;
 * 5.0:
 * - enforced 32K write strategy (needed for DVD-R[W]);
 * - support for DVD-RW Restricted Overwrite Mode;
 * - support for DVD-R[W] Sequential Mode;
 * 5.1:
 * - support for writing speed control;
 *
 * * 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
#if defined(__linux)
/* ... and "engage" glibc large file support */
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <signal.h>
#include <poll.h>
#include <assert.h>

/* symbols from growisofs_mmc.cpp */
ssize_t poor_mans_pwrite64 (int fd,const void *buff,size_t size,off64_t foff);
int  plus_rw_restart_format(int fd);
void plus_rw_finalize	();
void plus_r_finalize	();
void minus_rw_finalize	();
void minus_r_finalize	();
int minus_rw_setup	(int fd);
int get_mmc_profile	(int fd);
off64_t get_capacity	(int fd);
int plusminus_r_C_parm	(int fd,char *C_parm);
int flush_cache		(int fd);

/* simplified */
struct iso_primary_descriptor {
    char type	[1];
    char id	[5];
    char void1	[80-5-1];
    char volume_space_size [8];
    char void2	[2048-80-8];
};

#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;
}

#if defined(__linux)
#include <sys/ioctl.h>
#include <linux/raw.h>

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;
}
#endif

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

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

    if (!poor_man || mmc_profile==0x1A || mmc_profile==0x13)
    {	next_session = from_733(descr->volume_space_size);
	/* pad to closest 32K boundary */
	next_session += 15;
	next_session /= 16;
	next_session *= 16;
	sprintf (C_parm,"16,%u",next_session);
    }
    else if (mmc_profile==0x1B || mmc_profile==0x11 || mmc_profile==0x14)
	next_session=plusminus_r_C_parm (fd,C_parm);

  return next_session;
}

struct writing_progress { time_t zero; off64_t current,final; };

int builtin_dd (int infd,int outfd,off64_t outoff)
{ char *buffer;
  int   n,off,fd;
  struct stat64 sb;
  volatile struct writing_progress *progress;
  pid_t pid=(pid_t)-1;

    if ((fd=open ("/dev/zero",O_RDWR)) < 0)
	perror (":-( unable to open \"/dev/zero\"?"), exit(1);
    progress = mmap (NULL,sizeof(*progress),PROT_READ|PROT_WRITE,
			MAP_SHARED,fd,0);
    close (fd);
    if (progress == MAP_FAILED)
	fprintf (stderr,":-( unable to anonymously mmap %d?\n",
			sizeof(*progress)),
	perror (NULL), exit(1);

    if (fstat64 (infd,&sb))
	perror (":-( unable to fstat64"), exit(1);

    progress->zero=0;
    progress->final=0;
    progress->current=outoff;
    if (S_ISREG(sb.st_mode))
	progress->final=sb.st_size;
    else if (S_ISCHR(sb.st_mode) || S_ISBLK(sb.st_mode))
	progress->final=get_capacity (outfd);

    if (!quiet && progress->final != 0 && (pid=fork()) == 0)
    { double ratio;
      int delta;

	close (infd);
	close (outfd);
	while (1)
	{   poll (NULL,0,3333);
	    if (progress->current)
	    {	delta = time (NULL) - progress->zero;
		ratio = (double)progress->final/(double)progress->current;
		delta *= ratio - 1.0;
		fprintf (stdout,"%10lld/%lld (%4.1f%%) remaining %d:%02d\n",
				progress->current,progress->final,100.0/ratio,
				delta/60,delta%60);
	    }
	    else
		fprintf (stdout,"%10lld/%lld (%4.1f%%) remaining ??:??\n",
				(off64_t)0,progress->final,0.0);
	    fflush (stdout);
	}
	exit(0); // not reached
    }

    if ((fd=open ("/dev/zero",O_RDWR)) < 0)
	perror (":-( unable to open \"/dev/zero\"?"), exit(1);
    buffer = mmap (NULL,DVD_BLOCK,PROT_READ|PROT_WRITE,
			MAP_PRIVATE,fd,0);
    close (fd);
    if (buffer == MAP_FAILED)
	fprintf (stderr,":-( unable to anonymously mmap %d?\n",DVD_BLOCK),
	perror (NULL), exit(1);

    progress->zero=time(NULL);
    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;
	    progress->current=outoff;
	}
    }
    if (off)	// pad to the closest 32K is needed
    {	memset (buffer+off,0,DVD_BLOCK-off);
	if ((n=(*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;
    }

    if (pid!=(pid_t)-1) kill (pid,SIGTERM), waitpid (pid,NULL,0);

    munmap (buffer,DVD_BLOCK);

  return n;
}

void pipe_mkisofs_up (char *mkisofs_argv[],int outfd,off64_t outoff)
{ pid_t mkisofs_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=-1,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=-1,new_size;
  struct iso_primary_descriptor *buffer;
  uid_t uid=geteuid();

    /* mmap buffer so that we can use it with /dev/raw/rawN */
    if ((infd=open ("/dev/zero",O_RDWR)) < 0)
	perror (":-( unable to open \"/dev/zero\"?"), exit(1);
    buffer = mmap (NULL,DVD_BLOCK+IVDs_SIZE,PROT_READ|PROT_WRITE,
			MAP_PRIVATE,infd,0);
    close (infd);
    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 (!strncmp(argv[i],"-speed",6))
	    { char *s;
		if (len>6)	(s=strchr(argv[i],'='))?s++:s;
		else		s=argv[++i];
		if (s)		speed_factor=atoi(s);
		if (speed_factor<=0)
		    fprintf (stderr,"-speed=%d: insane speed factor.\n",
				    speed_factor),
		    exit(1);
		continue;
	    }
	    else if (!strcmp(argv[i],"-dvd-compat"))
	    {	poor_man = 1, dvd_compat = 1;
		continue;
	    }
	    else if (!strcmp(argv[i],"-dvd-video"))
		dvd_compat = 1, poor_man = 1;
		else if (!strcmp(argv[i],"-force") || !strcmp(argv[i],"--force"))
			force = 1;
	    else if (!strcmp(argv[i],"-quiet"))
		quiet = 1;
	    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 (argv[i][1] == '?' ||
	    		!strcmp(argv[i],"-help") ||
	    		!strcmp(argv[i],"--help")
		    )
	    {	printf ("usage: %s -[ZM] /dev/cdrom <mkisofs options>\n",argv[0]);
			printf ("\toptions:\n");
			printf ("\t\t--force\toverwrite an existing iso filesystem\n");
			printf ("\n");
		printf ("       for <mkisofs options> run 'mkisofs %s'\n",argv[i]);
		exit (1);
	    }
	    else if (strstr (argv[i],"-version"))
	    {	printf ("growisofs 5.1, 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 ((uid_t)-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((uid_t)-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 */
		mmc_profile = get_mmc_profile (infd);
		if (out_device == in_device &&
		    ( mmc_profile==0x1A ||  /* DVD+RW */
		      mmc_profile==0x1B ||  /* DVD+R  */
		      mmc_profile==0x13	    /* DVD-RW Restricted Overwrite */
		    ) )
		    poor_man = 1;
	    }
	    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,2048,VOLDESC_OFF*CD_BLOCK) != 2048
		&& dev_found == 'M')
	        perror (":-( unable to pread64(2) primary volume descriptors"),
		exit (1);

	    if (dev_found == 'M')
            {	if (memcmp (buffer,"\1CD001",6))
	            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",6))
		    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 while we were working on -[MZ] options.
     */

    mkisofs_argv[mkisofs_argc] = NULL;

    assert (outfd!=-1);
    assert (next_session!=-1);

#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,2048,VOLDESC_OFF*CD_BLOCK) != 2048)
	{   close (imgfd), imgfd=-1; break;   }
	if (memcmp(buffer,"\1CD001",6))
	{   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 (warn_for_isofs)
    {	fprintf (stderr,"Sleeping for 5 sec..."),	poll (NULL,0,1000);
	fprintf (stderr,"\b\b\b\b\b\b\b\b4 sec..."),	poll (NULL,0,1000);
	fprintf (stderr,"\b\b\b\b\b\b\b\b3 sec..."),	poll (NULL,0,1000);
	fprintf (stderr,"\b\b\b\b\b\b\b\b2 sec..."),	poll (NULL,0,1000);
	fprintf (stderr,"\b\b\b\b\b\b\b\b1 sec..."),	poll (NULL,0,1000);
	fprintf (stderr,"\b\b\b\b\b\b\b\b0 sec...\r");
    }

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

	pwrite64_method = poor_mans_pwrite64;

	if (mmc_profile == 0x1A)
	{   if (dvd_compat)
		plus_rw_restart_format (infd);
	    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 if (mmc_profile == 0x13)
	{   if (minus_rw_setup (media_fd))
		perror (":-( unable to setup write page"), exit (1);
	    atexit (minus_rw_finalize);
	    signal (SIGHUP,minus_rw_finalize);
	    signal (SIGINT,minus_rw_finalize);
	    signal (SIGTERM,minus_rw_finalize);
	    signal (SIGPIPE,minus_rw_finalize);
	}
	else if (mmc_profile == 0x11 || mmc_profile == 0x14)
	{   if (minus_rw_setup (media_fd))
		perror (":-( unable to setup write page"), exit (1);
	    atexit (minus_r_finalize);
	    signal (SIGHUP,minus_r_finalize);
	    signal (SIGINT,minus_r_finalize);
	    signal (SIGTERM,minus_r_finalize);
	    signal (SIGPIPE,minus_r_finalize);
	}
	else
	    fprintf (stderr,":-( %s: mounted media doesn't appear to be "
	    			"DVD+RW or DVD+R\n",out_device),
	    exit(1);
    }

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

    pipe_mkisofs_up (mkisofs_argv,outfd,next_session*CD_BLOCK);

    if (next_session!=0 && (!poor_man || mmc_profile==0x1A || mmc_profile==0x13))
    {
	/*
	 * (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);
	flush_cache (infd);

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

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

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

	new_size = from_733(buffer[16].volume_space_size) + next_session;

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

	memcpy (buffer+0,buffer+16,sizeof(struct iso_primary_descriptor));
	to_733(buffer[0].volume_space_size,new_size);

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

	    if (buffer[16+i].type[0] != buffer[i].type[0])
	    {	fprintf (stderr,"volume descriptor mismatch, did you "
				"use same mkisofs options?\n");
		break;
	    }
	    memcpy (buffer+i,buffer+16+i,sizeof(struct iso_primary_descriptor));
	    to_733(buffer[i].volume_space_size,new_size);
	}

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

	flush_cache (infd);
    }

    errno = 0;
    exit (0);
}
