/*
 * growisofs 7.1 by Andy Polyakov <appro@fy.chalmers.se>.
 *
 * Version 6.0 is dedicated to all personal friends of mine! The code
 * was initially intended to be a 2006 New Year gift to them, but it
 * didn't happen:-( But it doesn't change the fact that I think of
 * you, guys! Cheers!
 *
 * Use-it-on-your-own-risk, GPL bless...
 *
 * This front-end to mkisofs(8) was originally developed to facilitate
 * appending of data to ISO9660 volumes residing on random write access
 * DVD media such as DVD+RW, DVD-RAM, as well as plain files/iso images.
 * At later stages even support for multi-session recording to DVD
 * write-once media such as DVD+R and DVD-R was added.
 *
 * As for growing random access volumes. The idea is very simple. The
 * program appends new data as it was added to a multisession media and
 * then copies the new volume descriptor(s) to the beginning of media
 * thus effectively updating the root catalog reference...
 *
 * 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 disc
 *   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;
 * 5.2:
 * - re-make it work under Linux 2.2 kernel;
 * - progress indicator to display recording velocity;
 * - code to protect against overburns;
 * - undocumented -use-the-force-luke flag to overwrite the media
 *   none interactively;
 * - brown-bag bug in "LONG WRITE IN PROGRESS" handling code fixed;
 * 5.3:
 * - Pioneer workarounds/fix-ups, most notably DVR-x05 doesn't seem
 *   to digest immediate "SYNC CACHE";
 * - support for DVD-RW Quick Format, upon release verified to work
 *   with Pioneer DVR-x05;
 * - bug in DVD+RW overburn "protection" code fixed;
 * - media reload is moved here from dvd+rw-format;
 * - refuse to burn if session starts close to or beyond 4GB limit
 *   (limitation of Linux isofs implementation);
 * - dry_run check is postponed all the way till the first write;
 * 5.4:
 * - split first write to two to avoid "empty DMA table?" in kernel log;
 * - setup_fds is introduced to assist ports to another platforms;
 * - set-root-uid assistant code directly at entry point (see main());
 * - OpenBSD/NetBSD support added, it's worth noting that unlike 3.3
 *   port by Maarten Hofman, it's /dev/rcd?c which is expected to be
 *   passed as argument, not /dev/cd?c.
 * 5.5:
 * - fix for ENOENT at unmount, I should have called myself with execlp,
 *   not execl;
 * - security: chdir("/") in set-root-uid assistant;
 * - use /proc/mounts instead of MOUNTED (a.k.a. /etc/mtab) in Linux
 *   umount code;
 * 5.6:
 * - unconditional exit in set-root-uid assistant, mostly for aesthetic
 *   reasons;
 * - support for DVD-RW DAO recordings (whenever Pioneer-ish Quick
 *   Format is not an option, DAO should fill in for it, as it's the
 *   only recording strategy applicable after *minimal* blanking
 *   procedure);
 * - support for SG_IO pass-through interface, or in other words
 *   support for Linux 2>=5;
 * - 'growisofs -M /dev/cdrom=/dev/zero', this is basically a counter-
 *   intuitive kludge assigned to fill up multi-session write-once
 *   media for better compatibility with DVD-ROM/-Video units, to keep
 *   it mountable [in the burner unit] volume descriptors from previous
 *   session are copied to the new session;
 * - disable -dvd-compat with -M option and DVD+R, advice to fill up
 *   the media as above instead;
 * - postpone Write Page setup all the way till after dry_run check;
 * - if recording to write-once media is terminated by external event,
 *   leave the session opened, so that the recording can be resumed
 *   (though no promises about final results are made, it's just that
 *   leaving it open makes more sense than to close the session);
 * - ask unit to perform OPC if READ DISC INFORMATION doesn't return
 *   any OPC descriptors;
 * - get rid of redundant Quick Grow in Restricted Overwrite;
 * - Solaris 2.x support is merged, it's volume manager aware, i.e.
 *   you can run it with or without volume manager;
 * 5.7:
 * - Solaris USB workaround;
 * - 15 min timeout for FLUSH CACHE in DVD-RW DAO;
 * - revalidate recording speed;
 * - load media upon start-up (Linux used to auto-close tray upon open,
 *   but not the others, which is why this functionality is added so
 *   late);
 * 5.8:
 * - elder Ricoh firmwares seem to report events differently, which
 *   triggered growisofs and dvd+rw-format to end-less loop at startup
 *   [event handling was introduced in 5.6 for debugging purposes];
 * - int ioctl_fd is transformed to void *ioctl_handle to facilitate
 *   port to FreeBSD;
 * - FreeBSD support contributed by Matthew Dillon;
 * - volume descriptors from second session were discarded in
 *   Restricted Overwrite since 5.6;
 * 5.9:
 * - some [SONY] firmwares make it impossible to tell apart minimally
 *   and fully blanked media, so we need a way to engage DAO manually
 *   [in DVD-RW]... let's treat multiple -dvd-compat options as "cry"
 *   for DAO;
 * - refuse to finalize even DVD-R media with -M flag (advise to fill
 *   it up with -M /dev/cdrom=/dev/zero too), apparently DVD-units
 *   [or is it just SONY?] also "misplace" legacy lead-out in the same
 *   manner as DVD+units;
 * - oops! DAO hung at >4MB buffer because of sign overflow;
 * - couple of human-readable error messages in poor_mans_pwrite64;
 * - work around Plextor firmware deficiency which [also] manifests as
 *   end-less loop upon startup;
 * 5.10:
 * - increase timeout for OPC, NEC multi-format derivatives might
 *   require more time to fulfill the OPC procedure;
 * - extended syntax for -use-the-force-luke option, it's now possible
 *   to engage DVD-R[W] dummy mode by -use-the-force-luke=[tty,]dummy
 *   for example, where "tty" substitutes for the original non-extended
 *   option meaning, see the source for more easter eggs;
 * - FreeBSD: compile-time option to pass -M /dev/fd/0 to mkisofs to
 *   make life easier for those who mount devfs, but not fdescfs;
 * - eliminate potential race conditions;
 * - avoid end-less loop if no media was in upon tray load;
 * - interpret value of MKISOFS environment variable as absolute path
 *   to mkisofs binary;
 * - to facilitate for GUI front-ends return different exit codes, most
 *   notably exit value of 128|errno denotes a fatal error upon program
 *   startup [messages worth popping up in a separate modal dialog
 *   perhaps?], errno - fatal error during recording and 1 - warnings
 *   at exit;
 * - to facilitate for GUI front-ends auto-format blank DVD+RW media;
 * - Linux: fix for failure to copy volume descriptors when DVD-RW
 *   Restricted Overwrite procedure is applied to patched kernel;
 * - FreeBSD: growisofs didn't close tray upon startup nor did the rest
 *   of the tools work with open tray;
 * - bark at -o option and terminate execution, the "problem" was that
 *   users seem to misspell -overburn once in a while, in which case it
 *   was passed down to mkisofs and an iso-image was dumped to current
 *   working directory instead of media;
 * - generalize -M /dev/cdrom=file.iso option, but if file.iso is not
 *   /dev/zero, insist on sane -C argument to be passed prior -M and
 *   double-verify the track starting address;
 * 5.11:
 * - authorship statement in -version output;
 * - make speed_factor floating point and print "Current Write Speed"
 *   factor for informational purposes;
 * - Pioneer DVR-x06 exhibited degraded performance when recording DVD+;
 * - Pioneer DVR-x06 failed to complete DVD+ recording gracefully;
 * - alter set-root-uid behaviour under Linux from "PAM-junky" to more
 *   conservative one;
 * 5.12:
 * - single Pioneer DVR-x06 user reported that very small fraction of
 *   his recordings get terminted with "LONG WRITE IN PROGRESS," even
 *   though growisofs explicitly reserves for this condition... It
 *   turned out that at those rare occasions unit reported a lot of free
 *   buffer space, which growisofs treated as error condition. It's not
 *   clear if it's firmware deficiency, but growisofs reserves even for
 *   this apparently rare condition now.
 * - [major] issue with MODE SENSE/SELECT on SANYO derivatives, such as
 *   Optorite, is addressed;
 * - Linux can't open(2) a socket by /dev/fd/N, replace it with dup(2);
 * - more relaxed command line option parsing and simultaneously a
 *   zealous check to make sure that no mkisofs options are passed
 *   along with -[ZM] /dev/cdrom=image;
 * - report I/O error if input stream was less than 64K;
 * - -M /dev/cdrom=/dev/zero didn't relocate the lead-out in DVD-RW
 *   Restricted Overwrite;
 * 5.13:
 * - workarounds for Panasonic/MATSUSHITA DVD-RAM LF-D310;
 * - Solaris: media load upon start-up;
 * 5.14:
 * - LG GSA-4040B failed to auto-format DVD+RW blanks;
 * - '| growisofs -Z /dev/cdrom=/dev/fd/0' failed with "already carries
 *   isofs" even when running interactively, so I check on /dev/tty
 *   instead of isatty(0);
 * - error output was confusing when overburn condition was raised in
 *   dry-run mode;
 * - more sane default drain buffer size to minimize system load when
 *   unit fails to return usable buffer utilization statistics under
 *   "LONG WRITE IN PROGRESS" condition;
 * - progress indicator process was orphaned if -Z /dev/cdrom=file.iso
 *   terminated prematurely;
 * - -overburn -Z /dev/cdrom=file.iso printed two "ignored" messages;
 * - Solaris: use large-file API in setup_fds;
 * - HP-UX: HP-UX support is contributed by HP;
 * - block signals in the beginning of recording, formally it shouldn't
 *   be necessary, but is apparently needed for some units (is it?);
 * - prepare code for -speed even in DVD+ context, need a test-case...
 * - TEAC DV-W50D and Lite-On LDW-811S failed to set recording velocity,
 *   deploy GET PERFORMANCE/SET STREAMING commands;
 * - Lite-On LDW-811S returns 0s in Write Speed descriptors in page 2A,
 *   this would cause a problem if DVD+ speed control was implemented;
 * 5.15:
 * - confusing output when DAO mode is manually engaged and DVD-RW media
 *   is minimally blanked;
 * - complement -use-the-force-luke=dao[:size] to arrange for piping
 *   non-iso images in DAO mode (size is to be expressed in 2KB chunks);
 * - Pioneer DVR-x06 apparently needs larger timeout to avoid "the LUN
 *   appears to be stuck" message in the beginning of DAO recording;
 * - HP-UX: fix-up umount code;
 * - HP-UX: make sure user doesn't pass /dev/rscsi/cXtYlZ, they should
 *   stick to /dev/rdsk/cXtYdZ;
 * - implement -use-the-force-luke=seek:N -Z /dev/dvd=image to arrange
 *   for 'builtin_dd if=image of=/dev/dvd obs=32k seek=N/16' (note that
 *   N is expected to be expressed in 2KB chunks);
 * - skip overwrite check for blank media to avoid read errors at start,
 *   which reportedly may cause bus reset in some configurations;
 * - make get_mmc_profile load media, explicit media load used to be on
 *   per platform basis, while it doesn't have to;
 * - postpone handle_events till after dry-run checkpoint;
 * - error reporting revised;
 * - Optorite seems to insist on resuming suspended DVD+RW format, at
 *   least it's apparently the only way to get *reliable* results
 *   (formally this contradicts specification, which explicitly states
 *   that format is to be resumed automatically and transparently);
 * - FreeBSD: FreeBSD 5-CURRENT since 2003-08-24, including 5.2 fails
 *   to pull sense data automatically, at least for ATAPI transport,
 *   so I reach for it myself (it's apparently a kernel bug, which
 *   eventually will be fixed, but I keep the workaround code just in
 *   case);
 * - -speed option in DVD+ context is enabled, upon release tested with
 *   Plextor PX-708A;
 * - make builtin_dd print amount of transferred data, together with
 *   -use-the-force-luke=seek:N it's easier to maintain "tar-formatted"
 *   rewritable media;
 * 5.16:
 * - brown-bag bug in "LONG WRITE IN PROGRESS" code path;
 * 5.17:
 * - Linux: fix for /proc/sys/dev/cdrom/check_media set to 1;
 * - HP-UX: INQUIRY buffer is required to be 128 bytes. Well, "required"
 *   is wrong word in this context, as it's apparently a kernel bug
 *   addressed in PHKL_30038 (HPUX 11.11) and PHKL_30039 (HPUX 11.23).
 *   This "change" affects all dvd+rw-tools, but I don't bump their
 *   version numbers for this, as it's an "ugly" workaround for an
 *   *external* problem;
 * - switch to GET PERFORMANCE even for current write speed (most
 *   notably required for NEC and derivatives);
 * - the above change required adaptations for Pioneer and LG units,
 *   which don't/fail to provide current write speed through GET
 *   PERFORMANCE despite the fact that the command is mandatory;
 * - HP-UX: retain root privileges in setup_fds, SIOC_IO requires them;
 * - fix for COMMAND SEQUENCE ERROR in the beginning of DVD-recording;
 * - drop privileges prior mkisofs -version;
 * 5.18:
 * - refuse to run if ${SUDO_COMMAND} is set;
 * - minimize amount of compiler warnings on 64-bit platforms;
 * - skip count-down if no_tty_check is set;
 * - -use-the-force-luke=tracksize:size option by suggestion from K3b;
 * - Linux: fix for "Bad file descriptor" with DVD+RW kernel patch;
 * 5.19:
 * - IRIX: IRIX 6.x port is added;
 * - Solaris: get rid of media reload, which made it possible to improve
 *   volume manager experience as well;
 * - address speed verification issues with NEC ND-2500 and Plextor
 *   PX-708A;
 * - make DVD-RAM work in "poor-man" mode;
 * - average write speed report at the end of recording;
 * - LG GSA-4081B fails to "SET STREAMING" with "LBA OUT OF RANGE" for
 *   DVD+RW media, but not e.g. DVD-R;
 * 5.20:
 * - DVD-RAM reload if recorded with -poor-man;
 * - -use-the-force-luke=wrvfy for WRITE AND VERIFY(10);
 * - "flushing cache" takes forever, from 5.19-1;
 * - HP-UX: inconsistency between /dev/rdsk and /dev/rscsi names;
 * - handle non-fatal OPC errors;
 * - DVD+R Double Layer support;
 * - -use-the-force-luke=4gms to allow ISO9660 directory structures
 *   to cross 4GB boundary, the option is effective only with DVD+R DL
 *   and for data to be accessible under Linux isofs a kernel patch is
 *   required;
 * - more sane sanity check for -use-the-force-luke=tracksize:N;
 * - -use-the-force-luke=break:size to set Layer Break position for
 *   Double Layer recordings;
 * - speed verification issue with 8x AccessTek derivatives addressed;
 * - -use-the-force-luke=noload to leave tray ejected at the end;
 * - allow to resume incomplete sessions recorded with -M option;
 * - Layer Break position sanity check with respect to dataset size;
 * - #if directive to get rid of sudo check at compile time with
 *   'make WARN=-DI_KNOW_ALL_ABOUT_SUDO';
 * 5.21:
 * - Linux: fix for kernel version 2.6>=8, 2.6.8 itself is deficient,
 *   but the problem can be worked around by installing this version
 *   set-root-uid;
 * 6.0:
 * - fix for DVD+R recordings in Samsung TS-H542A;
 * - DVD-R Dual Layer DAO and Incremental support;
 * - versioning harmonization;
 * - multi-threaded(*) design;
 *   *)	apparently strace-ing NPTL processes can disrupt synchroni-
 *	zation between threads and cause a deadlock condition. If
 *	you ought to strace growisofs, make sure it's started with
 *	LD_ASSUME_KERNEL environment variable set to 2.4.
 * - asynchronous ring buffer implementation;
 * - Win32/Mingw32 port [for platform-specific notes see
 *   http://fy.chalmers.se/~appro/linux/DVD+RW/tools/win32/];
 * - refine Pioneer strategies;
 * - WRITE procedure to recognize "IN PROCESS OF BECOMING READY"
 *   [observed on newer Lite-On 1693S firmware and NEC];
 * - allow for more intuitive interpretation of -speed factor with
 *   units returning minimal velocity other than 1x;
 * - -use-the-force-luke=noopc to skip OPC altogether;
 * - implement 50% check for DVD-R DL;
 * - demote failure to change speed to warning;
 * 6.1:
 * - FreeBSD: improve backward binary compatibility;
 * - Linux: default rlimit for locked pages is way too small [note
 *   that in order to allocate ring buffer larger than default 32MB
 *   through command line option, you have to increase memorylocked
 *   limit accordingly prior application start];
 * - make -use-the-force-luke=noload, which leaves tray open, work;
 * - DEFAULT_BUF_SIZE_MB is a macro, which can be redefined at
 *   compile time with 'make WARN=-DDEFAULT_BUF_SIZE_MB=[16|64]' to
 *   change the default ring buffer size;
 * - ± localization;
 * - Linux: utilize O_EXCL flag [but do see commentary below!];
 * - Treat only x73xx OPC errors as fatal;
 * - Fix typo in -speed scaling code;
 * - permit tracksize to be not divisible by 32KB in DAO mode;
 * 7.0:
 * - Blu-ray Disc support [upon release tested with Panasonic SW-5582];
 * - Mac OS X 10>=2 support [upon release tested on 10.4 only];
 * - Linux: copy <linux/raw.h> definitions directly into application
 *   code in order to secure backward compatibility;
 * - Linux: overcome 16MB O_DIRECT limitaton for NFS client;
 * - limit ring buffer size to 1/4 of RAM;
 * - copy volume descriptors if -C was specified with -M /dev/dvd=image
 *   [by request from K3b];
 * - -use-the-force-luke=spare[:none|min] to control blank BD pre-format
 *   [for finer control use dvd+rw-format];
 * - some units, e.g. Lite-on SOHW-1693S, seem to fire off OPC already
 *   upon Layer Break command, therefore longer timeout is required;
 * - Linux: deploy BLKFLSBUF to avoid media reloads when possible;
 * - add unit buffer utilization indicator [by request from K3b];
 * 7.1:
 * - Linux: allow compilation with non-substitution 2.6 kernel headers;
 * - Linux: not all 2.4 filesystem types support direct I/O;
 * - Linux: lock corresponding /dev/srN;
 * - use PTHREAD_STACK_MIN when creating threads;
 * - use _SC_PHYS_PAGES instead of _SC_AVPHYS_PAGES when calculating
 *   ring buffer size limit;
 * - Mac OS X: allow compilation on 10.5;
 * - human-readable sense code transcription;
 * - Solaris: privileges, authorization and hald awareness;
 * - in order to minimize swap reservations mmap ring buffer with
 *   MAP_SHARED;
 * - add -F/-free-space option displaying next session offset and
 *   media capacity, which facilitates free space calculation [by
 *   suggestion from Bacula project];
 * - allow session to cross 4GB boundary even on single-layer media
 *   [by request from Bacula project];
 * - HP-UX: fix compilation warnings;
 * - refine x73xx error treatment;
 * - handle deferred errno from reader thread;
 * - return non-zero exit status more aggressively;
 */
#define PRINT_VERSION(cmd)	do {			\
    char *s=strrchr((cmd),'/');				\
    s ? s++ : (s=(cmd));				\
    printf ("* %.*sgrowisofs by <appro@fy.chalmers.se>,"\
	    " version 7.1,\n",(int)(s-(cmd)),(cmd));	\
} while (0)

#define _LARGEFILE_SOURCE 
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64

#if defined(__linux)
/* ... and "engage" glibc large file support */
# ifndef _GNU_SOURCE
#  define _GNU_SOURCE
# endif
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || \
      (defined(__APPLE__) && defined(__MACH__))
# define off64_t	off_t
# if !defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) || \
              __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__<1050
#  define stat64	stat
#  define fstat64	fstat
# endif
# define open64		open
# define pread64	pread
# define pwrite64	pwrite
# define lseek64	lseek
# ifndef __unix
#  define __unix
# endif
#elif defined(_WIN32)
# undef  _WIN32_WINNT
# define _WIN32_WINNT	0x0500
# define off64_t	__int64
# define stat64		_stati64
# define fstat64	_fstati64
# define lseek64	_lseeki64
# define ssize_t	LONG_PTR
# define perror		_mask_perror
#endif

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include "mp.h"

#if defined(__unix) || defined(__unix__)
# include <unistd.h>
# include <sys/time.h>
# include <sys/wait.h>
# include <sys/mman.h>
# include <signal.h>
# include <poll.h>
# include <sys/resource.h>
# include <sys/utsname.h>

# include <errno.h>
# define set_errno(e)	(errno=(e))
# define FATAL_START(e)	(0x80|(e))
# define FATAL_MASK	 0x7F

#ifdef __FreeBSD__
# include <sys/syscall.h>
# ifndef SYS_mlockall
#  define SYS_mlockall 324
# endif
#endif

#ifdef O_DIRECT
# ifdef __linux
/*
 * Linux 2.4 ext3 doesn't support direct I/O:-? I mean it allows to open
 * file for direct I/O and then fails to perform actual I/O. It appears
 * to be trivial omission in kernel source, but "better-safe-than-sorry"
 * principle makes me excuse all 2.4 kernels from attempt to open input
 * file for direct I/O.
 */
static int open_directio (const char *name,int flag)
{ struct utsname uts;

    if (uname(&uts)<0)			return -1;
    if (strncmp(uts.release,"2.6",3)<0)	return -1;
    return open64(name,flag|O_DIRECT);
}
#  define OPEN_DIRECTIO(n,f)	open_directio((n),(f))
# else
#  define OPEN_DIRECTIO(n,f)	open64((n),(f)|O_DIRECT)
# endif
#endif

#elif defined(_WIN32)

#include "win32err.h"
# include <io.h>
# include <process.h>
# ifndef F_OK
#  define F_OK 0
# endif
# ifndef S_ISREG
#  define S_ISREG(m)	(((m)&_S_IFMT)==_S_IFREG)
# endif
static ssize_t pread64(int,void *,size_t,off64_t);
static ssize_t pwrite64(int,const void *,size_t,off64_t);
# define poll(a,b,t)	Sleep(t)
# define LLD		"I64d"

#endif

#ifndef EMEDIUMTYPE
#define EMEDIUMTYPE     EINVAL
#endif
#ifndef ENOMEDIUM
#define ENOMEDIUM       ENODEV
#endif

#ifndef LLD
#define LLD		"lld"
#endif

#ifndef DEFAULT_BUF_SIZE_MB
/* (!)	default is 32MB, which is somewhat wasteful for 4x recording
 *	speed and somewhat close to the "edge" for 16x:-) */	
#define DEFAULT_BUF_SIZE_MB	32
#endif

typedef ssize_t (*pwrite64_t)(int,const void *,size_t,off64_t);

/*
 * Symbols from growisofs_mmc.cpp
 */
/*
 * These may terminate the program if error appears fatal.
 * The return value is therefore is always sane and suitable
 * for assignment.
 */
int        get_mmc_profile	(void *fd);
int        plusminus_r_C_parm	(void *fd,char *C_parm);
pwrite64_t poor_mans_setup	(void *fd,off64_t leadout,unsigned int lbreak);
char      *plusminus_locale	();
int        __1x			();
/*
 * These never terminate the program.
 * Pay attention to the return value.
 */
int        media_reload		(char *file,struct stat *ref, unsigned int cap2k);
int        fumount		(int fd);
off64_t    get_capacity		(void *fd);
int        poor_man_rewritable	(void *fd,void *buf);
float      get_buffer_stats	(void *fd);

/* simplified */
struct iso_primary_descriptor {
    unsigned char type	[1];
    unsigned char id	[5];
    unsigned char void1	[80-5-1];
    unsigned char volume_space_size [8];
    unsigned char void2	[2048-80-8];
};

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

static struct iso_primary_descriptor saved_descriptors[MAX_IVDs];

static pwrite64_t pwrite64_method = pwrite64;

/*
 * Page-aligned ring buffer...
 */
static char         *the_buffer;
static unsigned int  the_buffer_size=DEFAULT_BUF_SIZE_MB*1024*1024;

/*
 * Synchronization objects
 */
static volatile unsigned int highest_ecc_block,reader_exit;
static void *the_ring_semaphore;
static volatile struct { time_t zero; off64_t current,final; } progress;

/*
 * in_fd is passed to mkisofs, out_fd - to pwrite and ioctl_fd - to ioctl.
 */
static int	in_fd=-1,out_fd=-1;

#ifndef INVALID_HANDLE
#define INVALID_HANDLE ((void *)-1)
#endif
static void	*ioctl_handle=INVALID_HANDLE;
#define ioctl_fd ((long)ioctl_handle)

static int	poor_man=-1, zero_image=0, quiet=1,
		overburn=0, no_tty_check=0, dry_run=0, dao_size=0,
		no_4gb_check=0, layer_break=0,next_session=-1;
static float	butlz=1.00001;	/* unit buffer utilization */
static char	*in_device=NULL,*out_device=NULL,*argv0;

int    dvd_compat=0, test_write=0, no_reload=0, mmc_profile=0,
       wrvfy=0, no_opc=0, spare=0;
double speed_factor=0.0;
char  *ioctl_device;
int    _argc;
char **_argv;


#if defined(__linux)

#include <linux/types.h>
#include <linux/cdrom.h>
#include <scsi/scsi.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#ifndef _LINUX_WAIT_H
#define _LINUX_WAIT_H	/* linux headers are impaired */
#endif
#include <linux/capability.h>

#if 0
#include <linux/raw.h>
#else
/* As they threat to remove <linux/raw.h>, copy definitions here */
#define RAW_SETBIND	_IO(0xac,0)
#define RAW_GETBIND	_IO(0xac,1)

struct raw_config_request
{   int		raw_minor;
    __u64	block_major,block_minor;
};
#endif

#if 0
int dev_open(const char *pathname,int flags);
int dev_open_patched();

int open64_wrapper(const char *pathname,int flags,...)
{ struct stat64 sb;

    if (stat64(pathname,&sb)==0 && S_ISBLK(sb.st_mode) && dev_open_patched())
    {	poor_man = 1;
	return dev_open(pathname,flags|O_LARGEFILE);
    }

  return open64(pathname,flags);
}
#endif

char *find_raw_device(struct stat64 *sb)
{ int   i,rawctl;
  dev_t dev_major,dev_minor;
  char  *ret=NULL;
  struct raw_config_request req;
  static char rawdevname[24] = "";

    if (!S_ISBLK(sb->st_mode)) return NULL;

    dev_major = major (sb->st_rdev);
    dev_minor = minor (sb->st_rdev);

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

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

int grab_sg (int blkfd)
{ struct { unsigned int dev_id,host_unique_id; } idlunblk,idlunsg;
  FILE *fp;
  int   host_no,channel,lun,id,type,i,sgfd=-1;
  char  str[128];
  struct stat sb;

    if (ioctl (blkfd,SCSI_IOCTL_GET_IDLUN,&idlunblk) < 0)	return -1;

    if ((fp=fopen ("/proc/scsi/sg/devices","r")) == NULL)	return -1;

    for (i=0;i>=0;i++)
    {	if (fgets (str,sizeof(str),fp) == NULL)	break;

	if (sscanf (str,"%d\t%d\t%d\t%d\t%d",
			&host_no,&channel,&id,&lun,&type) != 5)
	    continue;

	if (type != 5)	continue;	/* skip over non-CD-ROM devices */

	if (idlunblk.dev_id ==	((id & 0xff) +
				((lun & 0xff) << 8) +
				((channel & 0xff) << 16) +
				((host_no & 0xff) << 24)))
	{   sprintf (str,"/dev/sg%d",i);
	    if (stat (str,&sb) < 0)	break;
	    errno = ENOENT;
	    if (minor(sb.st_rdev) != i)	break;
	    sgfd = open (str,O_RDWR|O_NONBLOCK|O_EXCL);
	    if (sgfd>=0)
	    {	if (ioctl (sgfd,SCSI_IOCTL_GET_IDLUN,&idlunsg) < 0 ||
		    idlunblk.dev_id != idlunsg.dev_id ||
		    idlunblk.host_unique_id != idlunsg.host_unique_id)
		    close (sgfd), sgfd = -1, errno = ENOENT;
	    }
	    break;
	}
    }
    fclose (fp);

  return sgfd;
}

char *setup_fds (char *device)
{ char *odevice;
  uid_t uid=getuid();
  struct stat64 sb,sc;
  int fd;

    /*
     * 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...
     */
#if 0
#define GET_REAL
    /*
     * Get real, but preserve saved uid. I count that user is
     * logged on console and therefore owns the 'device' [this
     * is a PAM's resposibility to arrange].
     */
    setreuid ((uid_t)-1,uid);
#else
    /*
     * More "traditional" set-root-uid behaviour. I assume that if
     * administrator has installed growisofs set-root-uid, then
     * [s]he consciously wanted to grant access to /dev/scdN to
     * everybody, not just console user.
     */
#endif

    if ((in_fd = open64 (device,O_RDONLY)) < 0)
	if (!(errno == ENOMEDIUM || errno == EMEDIUMTYPE) ||
	    (in_fd = open64 (device,O_RDONLY|O_NONBLOCK)) < 0)
	    fprintf (stderr,":-( unable to open64(\"%s\",O_RDONLY): ",device),
	    perror (NULL), exit (FATAL_START(errno));

#ifdef GET_REAL
    setreuid ((uid_t)-1,uid);
#endif

    if (fstat64(in_fd,&sb) < 0)
	fprintf (stderr,":-( unable to stat64(\"%s\"): ",device),
	perror (NULL), exit (FATAL_START(errno));

    if (!S_ISBLK(sb.st_mode))
    {	setuid(uid);		/* drop all privileges	*/
	close (in_fd);		/* reopen as mortal	*/
	if ((in_fd = open64 (device,O_RDONLY)) < 0)
	    fprintf (stderr,":-( unable to re-open64(\"%s\",O_RDONLY): ",
			    device),
	    perror (NULL), exit (FATAL_START(errno));
      open_rw:
	if ((out_fd = open64 (device,O_RDWR)) < 0)
	    fprintf (stderr,":-( unable to open64(\"%s\",O_RDWR): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if (fstat64(out_fd,&sc) < 0)
	    fprintf (stderr,":-( unable to stat64(\"%s\"): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if (sb.st_dev!=sc.st_dev || sb.st_ino!=sc.st_ino)
	    fprintf (stderr,":-( %s: race condition detected!\n",device),
	    exit(FATAL_START(EPERM));
      opened_rw:
	poor_man = 0;
	if (ioctl_handle!=INVALID_HANDLE)
	    close (ioctl_fd), ioctl_handle = INVALID_HANDLE;
	setuid (uid);		/* drop all privileges */
	return device;
    }

    /*
     * O_RDWR is expected to provide for none set-root-uid
     * execution under Linux kernel 2.6[.8]. If it fails
     * [as under 2.6.8], CAP_SYS_RAWIO in combination
     * with set-root-uid should fill in for the kernel
     * deficiency...
     */
    if ((fd = open64 (device,O_RDWR|O_NONBLOCK)) >= 0)
    {	if (fstat64 (fd,&sc) < 0)
	    fprintf (stderr,":-( unable to stat64(\"%s\"): ",device),
	    perror (NULL), exit (FATAL_START(errno));

	if (sc.st_rdev != sb.st_rdev)
	    fprintf (stderr,":-( %s: race condition detected!\n",device),
	    exit(FATAL_START(EPERM));

 	ioctl_handle=(void *)(long)fd;
    }
    else
	ioctl_handle=(void *)(long)dup(in_fd);

    /*
     * get_mmc_profile terminates the program if ioctl_handle is
     * not an MMC unit...
     */
    mmc_profile = get_mmc_profile (ioctl_handle);

    /* Consume media_changed flag */
    if (ioctl (ioctl_fd,CDROM_MEDIA_CHANGED,CDSL_CURRENT) < 0)
    {	fprintf (stderr,":-( %s: CD_ROM_MEDIA_CHANGED ioctl failed: ",
			device),
	perror (NULL), exit (FATAL_START(errno));
    }

    switch (mmc_profile&0xFFFF)
    {	case 0x11:	/* DVD-R			*/
	case 0x13:	/* DVD-RW Restricted Overwrite	*/
	case 0x14:	/* DVD-RW Sequential		*/
	case 0x15:	/* DVD-R Dual Layer Sequential	*/
	case 0x16:	/* DVD-R Dual Layer Jump	*/
	case 0x1B:	/* DVD+R			*/
	case 0x2B:	/* DVD+R Double Layer		*/
	case 0x41:	/* BD-R SRM			*/
	case 0x42:	/* BR-R RRM			*/
	open_poor_man:
	    poor_man = 1;
	    out_fd = dup(ioctl_fd);

	    /* recording reportedly can be disrupted even by "sg scan"... */
	    if (!dry_run && grab_sg (ioctl_fd)<0 && errno==EBUSY)
		fprintf (stderr,":-( %s: failed to grab associated sg device\n",
				device),
		exit (FATAL_START(errno));
	    /* I consciously leak descriptor to /dev/sg, it gets released
	     * automatically upon program exit/termination */

#if defined(PR_SET_KEEPCAPS)
	    if (getuid()!= 0 && prctl(PR_SET_KEEPCAPS,1) == 0) do {
#if defined(_LINUX_CAPABILITY_VERSION) && defined(CAP_SYS_RAWIO) && defined(SYS_capset)
		struct __user_cap_header_struct	h;
		struct __user_cap_data_struct	d;

		h.version	= _LINUX_CAPABILITY_VERSION;
		h.pid		= 0;
		d.effective	= 0;
		d.permitted	= 1<<CAP_SYS_RAWIO;
		d.inheritable	= 0;
		if (syscall(SYS_capset,&h,&d) < 0) break;

		/* drop all privileges & effective capabilities */
		/* setuid(uid) seems to preserve saved uid
		   after dropping CAP_SETUID above... */
		setresuid (uid,uid,uid);

		d.effective	= 1<<CAP_SYS_RAWIO;
		if (syscall(SYS_capset,&h,&d) < 0) break;
#endif
	    } while (0);
#endif
	    setuid (uid);	/* drop all privileges	*/
	    return ioctl_device=device;
	case 0x12:	/* DVD-RAM			*/
	case 0x1A:	/* DVD+RW			*/
	case 0x2A:	/* DVD+RW Double Layer		*/
	case 0x43:	/* BD-RE			*/
	    if (poor_man>0) goto open_poor_man;
	    break;
	default:
	    fprintf (stderr,":-( %s: media is not recognized as "
			    "recordable DVD: %X\n",device,mmc_profile);
	    exit (FATAL_START(EMEDIUMTYPE));
    }

    /*
     * Attempt to locate /dev/raw/raw*
     */
#ifdef GET_REAL
#undef GET_REAL
    setreuid((uid_t)-1,0);	/* get root for a /dev/raw sake */
#endif
    if ((odevice=find_raw_device (&sb)))	/* /dev/raw */
    {	if ((out_fd=open64 (odevice,O_RDWR)) < 0)
	{   if (errno == EROFS)	/* must be unpatched kernel */
		goto open_poor_man;
	    else
		fprintf (stderr,":-( unable to open64(\"%s\",O_RDWR): ",
				odevice),
		perror (NULL), exit (FATAL_START(errno));
	}
	device=odevice;
	goto opened_rw;
    }
    if ((mmc_profile&0xFFFF) == 0x12)	goto open_rw;	/* DVD-RAM */
    else				goto open_poor_man;
}

#elif defined(__OpenBSD__) || defined(__NetBSD__)

char *setup_fds (char *device)
{ uid_t uid=getuid();
  struct stat sb,sc;

    /*
     * We might be entering as euid=root!
     */
    if ((in_fd = open (device,O_RDONLY)) < 0)
	fprintf (stderr,":-( unable to open(\"%s\",O_RDONLY): ",device),
	perror (NULL), exit (FATAL_START(errno));

    if (fstat (in_fd,&sb) < 0)
	fprintf (stderr,":-( unable to stat(\"%s\"): ",device),
	perror (NULL), exit (FATAL_START(errno));

    if (!S_ISCHR(sb.st_mode))
    {	if (S_ISBLK(sb.st_mode) && !strncmp (device,"/dev/cd",7))
	{   fprintf (stderr,":-) you most likely want to use /dev/r%s instead!\n",
			    device+5);
	    if (isatty(0) && !dry_run) poll(NULL,0,5000);
	}
	setuid(uid);	/* drop all privileges	*/
	close (in_fd);	/* reopen as mortal	*/
	if ((in_fd = open (device,O_RDONLY)) < 0)
	    fprintf (stderr,":-( unable to reopen(\"%s\",O_RDONLY): ",device),
	    perror (NULL), exit (FATAL_START(errno));
      open_rw:
	if ((out_fd = open (device,O_RDWR)) < 0)
	    fprintf (stderr,":-( unable to open(\"%s\",O_RDWR): ",device),
	    perror (NULL), exit (FATAL_START(errno));
      opened_rw:
	poor_man = 0;
	close (ioctl_fd);
	ioctl_handle = INVALID_HANDLE;
	setuid (uid);	/* drop all privileges	*/
	return device;
    }

    /*
     * Still as euid=root! But note that get_mmc_profile makes sure it's
     * an MMC device, as it terminates the program if the unit doesn't
     * reply to GET CONFIGURATON. In combination with following switch
     * this means that if installed set-root-uid, growisofs grants
     * access to DVD burner(s), but not to any other device.
     */
    ioctl_handle = (void *)(long)open (device,O_RDWR); /* O_RDWR is a must for SCIOCCOMMAND */
    if (ioctl_handle == INVALID_HANDLE)
    {	fprintf (stderr,":-( unable to open(\"%s\",O_RDWR): ",device),
	perror (NULL);
	if (errno == EBUSY)
	    fprintf (stderr,"    is its block counterpart mounted?\n");
	exit (FATAL_START(errno));
    }
    if (fstat(ioctl_fd,&sc) < 0)
	fprintf (stderr,":-( unable to stat(\"%s\"): ",device),
	perror (NULL), exit (FATAL_START(errno));
    if (sb.st_dev!=sc.st_dev || sb.st_ino!=sc.st_ino)
	fprintf (stderr,":-( %s: race condition detected!\n",device),
	exit(FATAL_START(EPERM));

    mmc_profile = get_mmc_profile (ioctl_handle);
    switch (mmc_profile&0xFFFF)
    {	case 0x11:	/* DVD-R			*/
	case 0x13:	/* DVD-RW Restricted Overwrite	*/
	case 0x14:	/* DVD-RW Sequential		*/
	case 0x15:	/* DVD-R Dual Layer Sequential	*/
	case 0x16:	/* DVD-R Dual Layer Jump	*/
	case 0x1A:	/* DVD+RW			*/
	case 0x1B:	/* DVD+R			*/
	case 0x2A:	/* DVD+RW Double Layer		*/
	case 0x2B:	/* DVD+R Double Layer		*/
	case 0x41:	/* BD-R SRM			*/
	case 0x42:	/* BD-R RRM			*/
	case 0x43:	/* BD-RE			*/
	open_poor_man:
	    poor_man = 1;
	    out_fd = dup(ioctl_fd);
	    setuid (uid);	/* drop all privileges	*/
	    return ioctl_device=device;
	case 0x12:	/* DVD-RAM			*/
	    if (poor_man>0) goto open_poor_man;
	    out_fd = dup(ioctl_fd);
	    goto opened_rw;
	default:
	    fprintf (stderr,":-( %s: media is not recognized as "
			    "recordable DVD: %X\n",device,mmc_profile);
	    exit (FATAL_START(EMEDIUMTYPE));
    }
    /* not actually reached */
    setuid(uid);
    goto open_rw;
}

#elif defined(__FreeBSD__)

#include <sys/cdio.h>
#include <camlib.h>
#include <cam/scsi/scsi_pass.h>

#undef	ioctl_fd
#define	ioctl_fd (((struct cam_device *)ioctl_handle)->fd)

#define PASS_STDIN_TO_MKISOFS

char *setup_fds (char *device)
{ uid_t		uid=getuid();
  struct stat	sb,sc;
  union ccb	ccb;
  char		pass[32]; /* periph_name is 16 chars long */
  struct cam_device *cam;
  int		once=1;

    /*
     * We might be entering as euid=root!
     */
    if ((in_fd = open (device,O_RDONLY)) < 0)
	fprintf (stderr,":-( unable to open(\"%s\",O_RDONLY): ",device),
	perror (NULL), exit (FATAL_START(errno));

    if (fstat (in_fd,&sb) < 0)
	fprintf (stderr,":-( unable to stat(\"%s\"): ",device),
	perror (NULL), exit (FATAL_START(errno));

    if (!S_ISCHR(sb.st_mode))
    {	setuid(uid);	/* drop all privileges	*/
	close (in_fd);	/* reopen as mortal	*/
	if ((in_fd = open (device,O_RDONLY)) < 0)
	    fprintf (stderr,":-( unable to reopen(\"%s\",O_RDONLY): ",device),
	    perror (NULL), exit (FATAL_START(errno));
      open_rw:
	if ((out_fd = open (device,O_RDWR)) < 0)
	    fprintf (stderr,":-( unable to open(\"%s\",O_RDWR): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if (fstat(out_fd,&sc) < 0)
	    fprintf (stderr,":-( unable to stat(\"%s\"): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if (sb.st_dev!=sc.st_dev || sb.st_ino!=sc.st_ino)
	    fprintf (stderr,":-( %s: race condition detected!\n",device),
	    exit(FATAL_START(EPERM));
      opened_rw:
	poor_man = 0;
	if (ioctl_handle && ioctl_handle != INVALID_HANDLE)
	    cam_close_device (ioctl_handle);
	ioctl_handle = INVALID_HANDLE;
	setuid (uid);	/* drop all privileges	*/
	return device;
    }

    /*
     * Still as euid=root! But note that get_mmc_profile makes sure it's
     * an MMC device, as it terminates the program if the unit doesn't
     * reply to GET CONFIGURATON. In combination with following switch
     * this means that if installed set-root-uid, growisofs grants
     * access to DVD burner(s), but not to any other device.
     */

    for (once=1;1;once--)
    {	memset (&ccb,0,sizeof(ccb));
	ccb.ccb_h.func_code = XPT_GDEVLIST;
	if (ioctl (in_fd,CAMGETPASSTHRU,&ccb) < 0)
	{   if (errno==ENXIO && once)
	    {	if (ioctl (in_fd,CDIOCCLOSE)==0) continue;	}
	    fprintf (stderr,":-( unable to CAMGETPASSTHRU for %s: ",device),
	    perror (NULL), exit (FATAL_START(errno));
	}
	break;
    }

    sprintf (pass,"/dev/%.15s%u",ccb.cgdl.periph_name,ccb.cgdl.unit_number);
    cam = cam_open_pass (pass,O_RDWR,NULL);
    if (cam == NULL)
    {	fprintf (stderr,":-( unable to cam_open_pass(\"%s\",O_RDWR): ",pass),
	perror (NULL);
	exit (FATAL_START(errno));
    }

    ioctl_handle = (void *)cam;

    mmc_profile = get_mmc_profile (ioctl_handle);
    switch (mmc_profile&0xFFFF)
    {	case 0x11:	/* DVD-R			*/
	case 0x13:	/* DVD-RW Restricted Overwrite	*/
	case 0x14:	/* DVD-RW Sequential		*/
	case 0x15:	/* DVD-R Dual Layer Sequential	*/
	case 0x16:	/* DVD-R Dual Layer Jump	*/
	case 0x1A:	/* DVD+RW			*/
	case 0x1B:	/* DVD+R			*/
	case 0x2A:	/* DVD+RW Double Layer		*/
	case 0x2B:	/* DVD+R Double Layer		*/
	case 0x41:	/* BD-R SRM			*/
	case 0x42:	/* BD-R RRM			*/
	case 0x43:	/* BD-RE			*/
	open_poor_man:
	    poor_man = 1;
	    out_fd = dup(in_fd);	/* it's ignored in poor_man ... */
	    setuid (uid);		/* drop all privileges	*/
	    return ioctl_device=cam->device_path;
	case 0x12:	/* DVD-RAM			*/
	    if (poor_man>0) goto open_poor_man;
	    break;
	default:
	    fprintf (stderr,":-( %s: media is not recognized as "
			    "recordable DVD: %X\n",device,mmc_profile);
	    exit (FATAL_START(EMEDIUMTYPE));
    }
    setuid(uid);
    goto open_rw;
}

#elif defined(__sun) || defined(sun)

#include <volmgt.h>
#include <sys/cdio.h>
#include <dlfcn.h>
#include <pwd.h>
#include <stdarg.h>
#include <alloca.h>

#if defined(__SunOS) && __SunOS >= 510
#include <priv.h>

/* run-time linking allows for backward binary compatibility, i.e.
 * binary compiled on 5>=10 can still be executed on 5<10. */
#if defined(__GNUC__) && __GNUC__>=2
static void rtld(void) __attribute__((constructor));
#elif defined(__SUNPRO_C)
#pragma init(rtld)
#endif

static int   not_impl_i() { errno = ENOTSUP; return -1;   }
static void *not_impl_p() { errno = ENOTSUP; return NULL; }
static union {
    void *p[8];
    struct {
	uint_t (*getpflags)(uint_t);
	int (*setpglags)(uint_t,uint_t);
	int (*getppriv)(priv_ptype_t,priv_set_t*);
	int (*setppriv)(priv_op_t,priv_ptype_t,priv_set_t*);
	char *(*priv_set_to_str)(const priv_set_t*,char,int);
	priv_set_t *(*priv_str_to_set)(const char*,const char*,const char**);
	priv_set_t *(*priv_allocset)(void);
	void (*priv_freeset)(priv_set_t*);
    } f;
} table = {
    {	(void *)not_impl_i,
	(void *)not_impl_i,
	(void *)not_impl_i,
	(void *)not_impl_i,
	(void *)not_impl_p,
	(void *)not_impl_p,
	(void *)not_impl_p,
	(void *)not_impl_p    }
};
#define	getpflags	(*table.f.getpflags)
#define	setpglags	(*table.f.setpglags)
#define	getppriv	(*table.f.getppriv)
#define	setppriv	(*table.f.setppriv)
#define	priv_set_to_str	(*table.f.priv_set_to_str)
#define	priv_str_to_set	(*table.f.priv_str_to_set)
#define	priv_allocset	(*table.f.priv_allocset)
#define	priv_freeset	(*table.f.priv_freeset)

static priv_set_t *basic_priv,*permitted_priv;

static void rtld(void)
{   void *g = dlopen(NULL,RTLD_LAZY),*p;
    if (g)
    {	table.p[0] = (p=dlsym(g,"getpflags")) ? p : (void*)not_impl_i;
	table.p[1] = (p=dlsym(g,"setpflags")) ? p : (void*)not_impl_i;
	table.p[2] = (p=dlsym(g,"getppriv")) ? p : (void*)not_impl_i;
	table.p[3] = (p=dlsym(g,"setppriv")) ? p : (void*)not_impl_i;
	table.p[4] = (p=dlsym(g,"priv_set_to_str")) ? p : (void*)not_impl_p;
	table.p[5] = (p=dlsym(g,"priv_str_to_set")) ? p : (void*)not_impl_p;
	table.p[6] = (p=dlsym(g,"priv_allocset")) ? p : (void*)not_impl_p;
	table.p[7] = (p=dlsym(g,"priv_freeset"))  ? p : (void*)not_impl_p;
	dlclose(g);

	basic_priv = priv_str_to_set("basic","",NULL);
    }
}
#endif

/* execlp is used to re-invoke itself, so pass on privileges... */
int execlp (const char *path,const char *arg0,...)
{ va_list ap;
  int argc=0;
  char **argv,**p;

    va_start(ap,arg0);
    while (va_arg(ap,char*)) argc++;
    va_end(ap);

    argv = p = alloca((argc+2)*sizeof(char *));

    *p++ = (char *)arg0;
    va_start(ap,arg0);
    while (argc--) *p++ = va_arg(ap,char*);
    va_end(ap);
    *p = NULL;

#if defined(setppriv)
    if (permitted_priv && getuid()!=0)
	setppriv (PRIV_SET,PRIV_INHERITABLE,permitted_priv);
#endif

  return execvp(path,argv);
}

char *setup_fds (char *device)
{ uid_t uid=getuid();
  struct stat64 sb,sc;
  int v;

    if ((v=volmgt_running()))
    { char *file=NULL,*volname;

	/*
	 * I leak some memory here, but I don't care...
	 */
	if ((volname=volmgt_symname (device)))
	    file=media_findname (volname);
	else
	    file=media_findname (device);

	if (file) device=file;
	else      v=0;
    }

    /*
     * We might be entering as euid=root!
     */
#ifdef setppriv
    if (permitted_priv && uid!=0)
	setppriv(PRIV_SET,PRIV_EFFECTIVE,permitted_priv);
#endif
    if ((in_fd = open64 (device,O_RDONLY)) < 0)
	if (errno != ENXIO ||
	    (in_fd = open64 (device,O_RDONLY|O_NONBLOCK)) < 0)
	    fprintf (stderr,":-( unable to open(\"%s\",O_RDONLY): ",device),
	    perror (NULL), exit (FATAL_START(errno));

    if (fstat64 (in_fd,&sb) < 0)
	fprintf (stderr,":-( unable to stat(\"%s\"): ",device),
	perror (NULL), exit (FATAL_START(errno));

    if (!S_ISCHR(sb.st_mode))
    { char *s;
	if (S_ISBLK(sb.st_mode) && (s=strstr (device,"/dsk/")))
	{   fprintf (stderr,":-) you most likely want to use %.*s/r%s instead!\n",
			    (int)(s-device),device,s+1);
	    if (isatty(0) && !dry_run) poll(NULL,0,5000);
	}
#ifdef setppriv
	/* if we were rewarded with extra privileges, drop them now */
	if (basic_priv && uid!=0)
	    setppriv(PRIV_SET,PRIV_PERMITTED,basic_priv);
#endif
	setuid(uid);	/* drop all privileges	*/
	close (in_fd);	/* reopen as mortal	*/
	if ((in_fd = open64 (device,O_RDONLY)) < 0)
	    fprintf (stderr,":-( unable to reopen(\"%s\",O_RDONLY): ",device),
	    perror (NULL), exit (FATAL_START(errno));
      open_rw:
	if ((out_fd = open64 (device,O_RDWR)) < 0)
	    fprintf (stderr,":-( unable to open(\"%s\",O_RDWR): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if (fstat64(out_fd,&sc) < 0)
	    fprintf (stderr,":-( unable to stat(\"%s\"): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if (sb.st_dev!=sc.st_dev || sb.st_ino!=sc.st_ino)
	    fprintf (stderr,":-( %s: race condition detected!\n",device),
	    exit(FATAL_START(EPERM));
	poor_man = 0;
	close (ioctl_fd);
	ioctl_handle = INVALID_HANDLE;
	setuid (uid);	/* drop all privileges	*/
	return device;
    }

    /*
     * Still as euid=root! But note that get_mmc_profile makes sure it's
     * an MMC device, as it terminates the program if the unit doesn't
     * reply to GET CONFIGURATON. In combination with following switch
     * this means that if installed set-root-uid, growisofs grants
     * access to DVD burner(s), but not to any other device.
     */
    /*
     * But before we proceed check for solaris.device.cdrw authorization.
     * In default installation it's granted to all users. See
     * 'man chkauthattr' for further details...
     */
    if (uid!=0)
    {	void *secdb = dlopen("libsecdb.so.1",RTLD_LAZY);
	union { void *p; int (*f)(const char *,const char *); } chkauthattr;

	if (secdb && (chkauthattr.p=dlsym(secdb,"chkauthattr")))
	{   struct passwd *pwd = getpwuid(uid);
	    if (pwd==NULL || !chkauthattr.f("solaris.device.cdrw",pwd->pw_name))
		fprintf(stderr,":-( solaris.device.cdrw is not granted to %s\n",
			pwd?pwd->pw_name:"(unknown)"),
		exit (FATAL_START(EACCES));
	}
	if (secdb) dlclose (secdb);
    }

    ioctl_handle = (void *)(long)dup (in_fd);
#if 0
    if (ioctl(ioctl_handle,CDROMSTART)<0 && errno==ENXIO)
	media_load(ioctl_handle);
#endif

    mmc_profile = get_mmc_profile (ioctl_handle);
    switch (mmc_profile&0xFFFF)
    {	case 0x11:	/* DVD-R			*/
	case 0x12:	/* DVD-RAM			*/
	case 0x13:	/* DVD-RW Restricted Overwrite	*/
	case 0x14:	/* DVD-RW Sequential		*/
	case 0x15:	/* DVD-R Dual Layer Sequential	*/
	case 0x16:	/* DVD-R Dual Layer Jump	*/
	case 0x1A:	/* DVD+RW			*/
	case 0x1B:	/* DVD+R			*/
	case 0x2A:	/* DVD+RW Double Layer		*/
	case 0x2B:	/* DVD+R Double Layer		*/
	case 0x41:	/* BD-R SRM			*/
	case 0x42:	/* BD-R RRM			*/
	case 0x43:	/* BD-RE			*/
	    poor_man = 1;
	    out_fd = dup(ioctl_fd);
#if 0	    /* 'man uscsi' maintains that root privileges are required upon
	     * issue of USCSI ioctl, we therefore can't drop them...
	     */
	    setuid (uid);	/* drop all privileges	*/
#endif
	    return ioctl_device=device;
	default:
	    fprintf (stderr,":-( %s: media is not recognized as "
			    "recordable DVD: %X\n",device,mmc_profile);
	    exit (FATAL_START(EMEDIUMTYPE));
    }
    /* not reached */
    setuid(uid);
    goto open_rw;
}

#elif defined(__hpux)

#ifdef SCTL_MAJOR
#  define SCTL_SANITY_CHECK SCTL_MAJOR-1
#  if SCTL_SANITY_CHECK<=0
#    undef SCTL_MAJOR
#  endif
#  undef SCTL_SANITY_CHECK
#endif

#ifndef SCTL_MAJOR
#error "SCTL_MAJOR is undefined or not sane."
#endif

#ifndef minor
#include <sys/mknod.h>
#endif
#include <sys/diskio.h>

#define seteuid(x)	setreuid((uid_t)-1,x)

#if 1
#define CANNOT_PASS_DEV_FD_N_TO_MKISOFS
#elif 0
--- ./multi.c.orig	Wed Dec 25 15:15:24 2002
+++ ./multi.c	Tue Nov 11 17:12:27 2003
@@ -1067,3 +1067,13 @@
 open_merge_image(path)
 	char	*path;
 {
+	int fd;
+
+	if (sscanf (path,"/dev/fd/%u",&fd) == 1) {
+		int fdd = dup(fd);	/* validate file descriptor */
+		if (fdd < 0) return -1;
+		close (fdd);
+		in_image = fdopen (fd,"rb");
+		return in_image ? 0 : -1;
+	}
+
#endif

#ifdef CANNOT_PASS_DEV_FD_N_TO_MKISOFS
char *get_M_parm (int fd, char *device)
{ struct stat sb;
  dev_t m;
  char *ret=device;
  static char ctl[16];

    if (fstat (fd,&sb)==0 && S_ISCHR(sb.st_mode))
    {	m = minor (sb.st_rdev);
	sprintf (ctl,"%d,%d,%d",(m>>16)&0xFF,(m>>12)&0xF,(m>>8)&0xF);
	ret = ctl;
    }

  return ret;
}
#endif

char *setup_fds (char *device)
{ uid_t uid=getuid();
  struct stat64 sb,sc;
  dev_t  m;
  static char rscsi [32];
  disk_describe_type ddt;

    /*
     * We might be entering as euid=root!
     */
    if ((in_fd = open64 (device,O_RDONLY)) < 0)
	fprintf (stderr,":-( unable to open(\"%s\",O_RDONLY): ",device),
	perror (NULL), exit (FATAL_START(errno));

    if (fstat64 (in_fd,&sb) < 0)
	fprintf (stderr,":-( unable to stat(\"%s\"): ",device),
	perror (NULL), exit (FATAL_START(errno));

    if (!S_ISCHR(sb.st_mode))
    { char *s;
	if (S_ISBLK(sb.st_mode) && (s=strstr (device,"/dsk/")))
	{   fprintf (stderr,":-) you most likely want to use %.*s/r%s instead!\n",
			    (int)(s-device),device,s+1);
	    if (isatty(0) && !dry_run) poll(NULL,0,5000);
	}
	setuid(uid);	/* drop all privileges	*/
	close (in_fd);	/* reopen as mortal	*/
	if ((in_fd = open64 (device,O_RDONLY)) < 0)
	    fprintf (stderr,":-( unable to reopen(\"%s\",O_RDONLY): ",device),
	    perror (NULL), exit (FATAL_START(errno));
      open_rw:
	if ((out_fd = open64 (device,O_RDWR)) < 0)
	    fprintf (stderr,":-( unable to open(\"%s\",O_RDWR): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if (fstat64(out_fd,&sc) < 0)
	    fprintf (stderr,":-( unable to stat(\"%s\"): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if (sb.st_dev!=sc.st_dev || sb.st_ino!=sc.st_ino)
	    fprintf (stderr,":-( %s: race condition detected!\n",device),
	    exit(FATAL_START(EPERM));
	poor_man = 0;
	close (ioctl_fd);
	ioctl_handle = INVALID_HANDLE;
	setuid (uid);	/* drop all privileges	*/
	return device;
    }

    /*
     * Still as euid=root! But note that get_mmc_profile makes sure it's
     * an MMC device, as it terminates the program if the unit doesn't
     * reply to GET CONFIGURATON. In combination with following switch
     * this means that if installed set-root-uid, growisofs grants
     * access to DVD burner(s), but not to any other device.
     */

    m=minor(sb.st_rdev);

    /* Make sure user isn't using /dev/rscsi/cXtYlZ... */
#if 1
    if (ioctl (in_fd,DIOC_DESCRIBE,&ddt) != 0)
#else
    if (major(sb.st_rdev) == SCTL_MAJOR)
#endif
	fprintf (stderr,":-( stick to /dev/rdsk/c%ut%u%c%x!\n",
			(m>>16)&0xFF,(m>>12)&0xF,'d',(m>>8)&0xF),
	exit(FATAL_START(EINVAL));

    /*
     * Even though /dev/rdsk/cXtYdZ accepts SIOC_IO as well, we have to
     * use /dev/rscsi/cXtYlZ for pass-through access in order to avoid
     * command replay by upper "class" driver...
     */
    sprintf (rscsi,"/dev/rscsi/c%ut%u%c%x",
		    (m>>16)&0xFF,(m>>12)&0xF,'l',(m>>8)&0xF);
    ioctl_handle = (void *)(long)open64 (rscsi,O_RDONLY);
    if (ioctl_handle == INVALID_HANDLE)
    {	fprintf (stderr,":-( unable to open(\"%s\",O_RDONLY): ",rscsi),
	perror (NULL);
	if (errno == ENOENT)
	    fprintf (stderr,":-! consider "
			    "'mknod %s c %d 0x%06x; chmod 0600 %s'\n",
			    rscsi,SCTL_MAJOR,(m&0xFFFF00)|2,rscsi);
	exit (FATAL_START(errno));
    }
    if (fstat64 (ioctl_fd,&sc) < 0)
	fprintf (stderr,":-( unable to stat(\"%s\"): ",rscsi),
	perror (NULL), exit (FATAL_START(errno));
    /* Make sure we land on same SCSI ID... */
    if ((m&0xFFFF00) != (minor(sc.st_rdev)&0xFFFF00))
	fprintf (stderr,":-( SCSI ID mismatch: %06x!=%06x\n",
			m,minor(sc.st_rdev)),
	exit(FATAL_START(EPERM));

    mmc_profile = get_mmc_profile (ioctl_handle);
    switch (mmc_profile&0xFFFF)
    {	case 0x11:	/* DVD-R			*/
	case 0x12:	/* DVD-RAM			*/
	case 0x13:	/* DVD-RW Restricted Overwrite	*/
	case 0x14:	/* DVD-RW Sequential		*/
	case 0x15:	/* DVD-R Dual Layer Sequential	*/
	case 0x16:	/* DVD-R Dual Layer Jump	*/
	case 0x1A:	/* DVD+RW			*/
	case 0x1B:	/* DVD+R			*/
	case 0x2A:	/* DVD+RW Double Layer		*/
	case 0x2B:	/* DVD+R Double Layer		*/
	case 0x41:	/* BD-R SRM			*/
	case 0x42:	/* BD-R RRM			*/
	case 0x43:	/* BD-RE			*/
	    poor_man = 1;
	    out_fd = dup(ioctl_fd);
#if 0	    /* HP-UX requires root privileges upon SIOC_IO ioctl */
	    setuid (uid);	/* drop all privileges	*/
#endif
	    return ioctl_device=rscsi;
	default:
	    fprintf (stderr,":-( %s: media is not recognized as "
			    "recordable DVD: %X\n",device,mmc_profile);
	    exit (FATAL_START(EMEDIUMTYPE));
    }
    setuid(uid);
    goto open_rw;
}

/*
 * PA-RISC HP-UX doesn't have /dev/zero:-(
 */
static fd_set _dev_zero;

static int open64_zero(const char *pathname, int flags)
{ int fd;
    if (strcmp(pathname,"/dev/zero"))
	return open64 (pathname,flags);
    else if ((fd=open ("/dev/null",flags)) >= 0)
	FD_SET (fd,&_dev_zero);
  return fd;    
}

static ssize_t read_zero (int fd, void *buf, size_t count)
{   if (!FD_ISSET(fd,&_dev_zero))	return read (fd,buf,count);
    memset (buf,0,count);		return count;
}

static int close_zero (int fd)
{ int ret=close (fd);
    if (ret>=0 && FD_ISSET(fd,&_dev_zero))	FD_CLR (fd,&_dev_zero);
  return ret;
}

static int dup2_zero (int oldfd, int newfd)
{ int ret;
    ret = dup2 (oldfd,newfd);
    if (ret >= 0)
    {	FD_CLR (newfd,&_dev_zero);
	if (FD_ISSET(oldfd,&_dev_zero))	FD_SET (ret,&_dev_zero);
	else				FD_CLR (ret,&_dev_zero);
    }
  return ret;
}
#define open64	open64_zero
#define read	read_zero
#define close	close_zero
#define dup2	dup2_zero

#elif defined(__sgi)

#include <sys/attributes.h>
#include <sys/param.h>
#include <mediad.h>

char *setup_fds (char *device)
{ uid_t uid=getuid();
  struct stat64 sb,sc;
  char	hw_path[MAXPATHLEN],*s;
  int	hw_len=sizeof(hw_path)-1;
  int	bus=0,tgt=4,lun=0;	/* default config for O2 */
  static char rscsi[64];

    /*
     * We might be entering as euid=root!
     */
    if ((in_fd = open64 (device,O_RDONLY)) < 0)
    {	if (errno==EIO)	goto tray_might_be_open;
	fprintf (stderr,":-( unable to open(\"%s\",O_RDONLY): ",device),
	perror (NULL), exit (FATAL_START(errno));
    }

    if (fstat64 (in_fd,&sb) < 0)
	fprintf (stderr,":-( unable to stat(\"%s\"): ",device),
	perror (NULL), exit (FATAL_START(errno));

    if (!S_ISCHR(sb.st_mode))
    {	if (S_ISBLK(sb.st_mode) && !strncmp (device,"/dev/dsk",7))
	{   fprintf (stderr,":-) you most likely want to use "
			    "/dev/r%s instead!\n",device+5);
	    if (isatty(0) && !dry_run) poll(NULL,0,5000);
	}
      open_rw:
	setuid(uid);	/* drop all privileges	*/
	close (in_fd);	/* reopen as mortal	*/
	if ((in_fd = open64 (device,O_RDONLY)) < 0)
	    fprintf (stderr,":-( unable to reopen(\"%s\",O_RDONLY): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if ((out_fd = open64 (device,O_RDWR)) < 0)
	    fprintf (stderr,":-( unable to open(\"%s\",O_RDWR): ",device),
	    perror (NULL), exit (FATAL_START(errno));
      opened_rw:
	poor_man = 0;
	close (ioctl_fd);
	ioctl_handle = INVALID_HANDLE;
	setuid (uid);	/* drop all privileges	*/
	return device;
    }

    /*
     * Still as euid=root! But note that get_mmc_profile makes sure it's
     * an MMC device, as it terminates the program if the unit doesn't
     * reply to GET CONFIGURATON. In combination with following switch
     * this means that if installed set-root-uid, growisofs grants
     * access to DVD burner(s), but not to any other device.
     */

  tray_might_be_open:

    if ((in_fd<0) ? attr_get (device,"_devname",hw_path,&hw_len,0) :
		    attr_getf (in_fd,"_devname",hw_path,&hw_len,0) )
	fprintf (stderr,":-( unable to obtain hw_path for \"%s\": ",device),
	perror (NULL), exit (FATAL_START(errno));
    if (hw_len>=sizeof(hw_path)) hw_len=sizeof(hw_path)-1; /* paranoia */
    hw_path[hw_len]='\0';

    if ((s=strstr(hw_path,"/scsi_ctlr/")))
	sscanf (s,"/scsi_ctlr/%d/target/%d/lun/%d/",&bus,&tgt,&lun);

    /* Make sure user is using /dev/rdsk/dksXdYlZvol... */
    if ((s=strstr(hw_path,"/disk/volume/char"))==NULL)
    {   if (lun)
	    fprintf (stderr,":-( stick to /dev/rdsk/dks%dd%dl%dvol!\n",
			    bus,tgt,lun);
	else
	    fprintf (stderr,":-( stick to /dev/rdsk/dks%dd%dvol!\n",
			    bus,tgt);
	exit(FATAL_START(EINVAL));
    }

    memcpy (s,"/scsi",6);
    ioctl_handle = (void *)(long)open64 (hw_path,O_RDWR);
    if (ioctl_handle == INVALID_HANDLE)
	fprintf (stderr,":-( unable to open(\"%s\",O_RDWR): ",hw_path),
	perror (NULL), exit (FATAL_START(errno));
    if (fstat64 (ioctl_fd,&sc) < 0)
	fprintf (stderr,":-( unable to stat(\"%s\"): ",hw_path),
	perror (NULL), exit (FATAL_START(errno));
    memcpy (s,"/disk/volume/char",6);

    mmc_profile = get_mmc_profile (ioctl_handle);
    switch (mmc_profile&0xFFFF)
    {	case 0x11:	/* DVD-R			*/
	case 0x13:	/* DVD-RW Restricted Overwrite	*/
	case 0x14:	/* DVD-RW Sequential		*/
	case 0x15:	/* DVD-R Dual Layer Sequential	*/
	case 0x16:	/* DVD-R Dual Layer Jump	*/
	case 0x1A:	/* DVD+RW			*/
	case 0x1B:	/* DVD+R			*/
	case 0x2A:	/* DVD+RW Double Layer		*/
	case 0x2B:	/* DVD+R Double Layer		*/
	case 0x41:	/* BD-R SRM			*/
	case 0x42:	/* BD-R RRM			*/
	case 0x43:	/* BD-RE			*/
	open_poor_man:
	    poor_man = 1;
	    out_fd = dup (ioctl_fd);
	    if (in_fd<0 && (in_fd=open64 (hw_path,O_RDONLY))<0)
		/* hope for the best? */ ;
	    setuid (uid);	/* drop all privileges	*/
	    mediad_get_exclusiveuse (hw_path,"growisofs");
	    if (mediad_last_error ()==RMED_NOERROR)
		putenv ("MEDIAD_GOT_EXCLUSIVEUSE=");	/* kludge... */
	    sprintf (rscsi,"/dev/scsi/sc%dd%dl%d",bus,tgt,lun);
	    return ioctl_device=rscsi;	/* might be bogus... */
	case 0x12:	/* DVD-RAM			*/
	    /* Some of latest tentative IRIX releases seem to implement
	     * DVD-RAM writing at dksc level, but I'm not sure which one.
	     * So I just fall down to poor-man, at least for now... */
	    goto open_poor_man;
	    break;
	default:
	    fprintf (stderr,":-( %s: media is not recognized as "
			    "recordable DVD: %X\n",device,mmc_profile);
	    exit (FATAL_START(EMEDIUMTYPE));
    }
    /* not actually reached */
    setuid(uid);
    goto open_rw;
}

#elif defined(_WIN32)

#define CANNOT_PASS_DEV_FD_N_TO_MKISOFS
#define PASS_STDIN_TO_MKISOFS

static ssize_t pread64 (int fd,void *p,size_t sz,off64_t off)
{ HANDLE	h = (HANDLE)_get_osfhandle(fd);
  OVERLAPPED	ov;
  DWORD		br=0; /* bytes read */

    if (h==INVALID_HANDLE_VALUE) return -1;
    memset (&ov,0,sizeof(ov));
    ov.Offset     = (DWORD)(off&0xFFFFFFFF);
    ov.OffsetHigh = (DWORD)(off>>32);

  return ReadFile (h,p,sz,&br,&ov) ? br : -1;
}

static ssize_t pwrite64 (int fd,const void *p,size_t sz,off64_t off)
{ HANDLE	h = (HANDLE)_get_osfhandle(fd);
  OVERLAPPED	ov;
  DWORD		bw=0; /* bytes written */

    if (h==INVALID_HANDLE_VALUE) return -1;
    memset (&ov,0,sizeof(ov));
    ov.Offset     = (DWORD)(off&0xFFFFFFFF);
    ov.OffsetHigh = (DWORD)(off>>32);

  return WriteFile (h,p,sz,&bw,&ov) ? bw : -1;
}

static struct {
	unsigned long fds_bits[512/sizeof(unsigned long)];
	} _dev_zero;
#define NFDBITS (sizeof(unsigned long)*8)
#define _FD_SET(n, p)    ((p)->fds_bits[(n)/NFDBITS] |= (1UL << ((n) % NFDBITS)))
#define _FD_CLR(n, p)    ((p)->fds_bits[(n)/NFDBITS] &= ~(1UL << ((n) % NFDBITS)))
#define _FD_ISSET(n, p)  ((p)->fds_bits[(n)/NFDBITS] & (1UL << ((n) % NFDBITS)))

/*
 * This subroutine is trimmed for particular growisofs needs!
 */
static int open64_zero(const char *pathname, int flags)
{ HANDLE h;
  int    fd,zero=0;
  DWORD  access=GENERIC_READ;

    if      (!strcmp (pathname,"/dev/null"))	pathname="NUL:", zero=0;
    else if (!strcmp (pathname,"/dev/zero"))	pathname="NUL:", zero=1;

    if      (flags & O_RDONLY)	access=GENERIC_READ;
    else if (flags & O_WRONLY)	access=GENERIC_WRITE;
    else if (flags & O_RDWR)	access=GENERIC_READ|GENERIC_WRITE;

    h = CreateFile(pathname,access,
		/* always share for write as I commonly open same
		 * file twice: with O_RDONLY and with O_WRONLY... */
		FILE_SHARE_READ|FILE_SHARE_WRITE,
		NULL,			/* don't inherit */
		OPEN_EXISTING,		/* and never create any files */
		FILE_FLAG_NO_BUFFERING|	/* bypass file system cache */
		(flags&O_WRONLY)?FILE_FLAG_WRITE_THROUGH:0,
		NULL);
    if (h == INVALID_HANDLE_VALUE) return -1;

    flags &= ~_O_TEXT;
    flags |= _O_BINARY|_O_NOINHERIT;
    fd = _open_osfhandle((size_t)h,flags);
    if (fd>=0 && zero) _FD_SET (fd,&_dev_zero);

  return fd;    
}

static ssize_t read_zero (int fd, void *buf, size_t count)
{   if (!_FD_ISSET(fd,&_dev_zero))	return _read (fd,buf,count);
    memset (buf,0,count);		return count;
}

static int close_zero (int fd)
{ int ret=_close (fd);
    if (ret>=0 && _FD_ISSET(fd,&_dev_zero))	FD_CLR (fd,&_dev_zero);
  return ret;
}

static int dup2_zero (int oldfd, int newfd)
{ int ret;
    ret = _dup2 (oldfd,newfd);
    if (ret >= 0)
    {	_FD_CLR (newfd,&_dev_zero);
	if (_FD_ISSET(oldfd,&_dev_zero))_FD_SET (ret,&_dev_zero);
	else				_FD_CLR (ret,&_dev_zero);
    }
  return ret;
}

#define open64	open64_zero
#define read	read_zero
#define close	close_zero
#define dup2	dup2_zero

char *setup_fds (char *device)
{ char   dev[32];
  HANDLE h;

    if (device[1] != ':' || device[2] != '\0')
    {	if ((in_fd = open64 (device,O_RDONLY)) < 0)
	    fprintf (stderr,":-( unable to open(\"%s\",O_RDONLY): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if ((out_fd = open64 (device,O_RDWR)) < 0)
	    fprintf (stderr,":-( unable to open(\"%s\",O_RDWR): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	poor_man = 0;
	ioctl_handle = INVALID_HANDLE;

      return device;
    }

    sprintf(dev,"%.*s\\",sizeof(dev)-2,device);
    if (GetDriveType(dev)!=DRIVE_CDROM)
	fprintf (stderr,":-( %s is not a CDROM device\n",dev),
	exit (FATAL_START(EINVAL));

    sprintf(dev,"\\\\.\\%.*s",sizeof(dev)-5,device);
    h = CreateFile (dev,GENERIC_WRITE|GENERIC_READ,
			FILE_SHARE_READ|FILE_SHARE_WRITE,
			NULL,OPEN_EXISTING,0,NULL);
    if (h==INVALID_HANDLE_VALUE)
	fprintf (stderr,":-( unable to open %s: ",dev),
	perror (NULL), exit(FATAL_START(errno));

    ioctl_handle = h;
    out_fd = _open_osfhandle((size_t)h,O_RDWR);
    /*
     * Unfortunately we're forced to pass read-write handle down to
     * mkisofs, because we are going to FSCTL_LOCK_VOLUME out_fd and
     * then we can't have other handles open.
     */
    in_fd = out_fd;

    mmc_profile = get_mmc_profile (ioctl_handle);
    switch (mmc_profile&0xFFFF)
    {	case 0x11:	/* DVD-R			*/
	case 0x12:	/* DVD-RAM			*/
	case 0x13:	/* DVD-RW Restricted Overwrite	*/
	case 0x14:	/* DVD-RW Sequential		*/
	case 0x15:	/* DVD-R Dual Layer Sequential	*/
	case 0x16:	/* DVD-R Dual Layer Jump	*/
	case 0x1A:	/* DVD+RW			*/
	case 0x1B:	/* DVD+R			*/
	case 0x2A:	/* DVD+RW Double Layer		*/
	case 0x2B:	/* DVD+R Double Layer		*/
	case 0x41:	/* BD-R SRM			*/
	case 0x42:	/* BD-R RRM			*/
	case 0x43:	/* BD-RE			*/
	    poor_man = 1;
	    return ioctl_device=strdup(dev);
	default:
	    fprintf (stderr,":-( %s: media is not recognized as "
			    "recordable DVD: %X\n",device,mmc_profile);
	    exit (FATAL_START(EMEDIUMTYPE));
    }
    /* not reached */
}


#elif defined(__APPLE__) && defined(__MACH__)

#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
#define MAP_ANONYMOUS MAP_ANON
#endif

#define PASS_STDIN_TO_MKISOFS
#define CANNOT_PASS_DEV_FD_N_TO_MKISOFS
#if !defined(CANNOT_PASS_DEV_FD_N_TO_MKISOFS)
#error "CANNOT_PASS_DEV_FD_N_TO_MKISOFS has to be defined"
/*
 * Even though /dev/fd/N is present on Mac OS X we can't pass it
 * to mkisofs for following reason. The trouble is that in order
 * to ObtainExclusiveAccess, which is required for raw SCSI, no
 * /dev/[r]diskN descriptors may be open by that time. Now, if
 * I pass /dev/fd/N, mkisofs would reopen it and close only this
 * duplicate descriptor leaving original N open. Therefore I
 * pass -M - to allow mkisofs to simply take stdin and close it
 * when it's done with previous session directory scructure.
 * Needless to mention that mkisofs has to be patched to accept
 * dash as -M argument:

--- mkisofs/multi.c.orig        2004-05-15 18:59:40.000000000 +0200
+++ mkisofs/multi.c     2006-09-11 23:50:23.000000000 +0200
@@ -1137,6 +1137,14 @@
 open_merge_image(path)
        char    *path;
 {
+       if (path[0]=='-' && path[1]=='\0') {
+#ifdef NEED_O_BINARY
+               setmode(fileno(stdin),O_BINARY);
+#endif
+               in_image = stdin;
+               return (0);
+       }
+
 #ifndef        USE_SCG
        in_image = fopen(path, "rb");
        if (in_image == NULL) {
---
 */
#endif

#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/scsi/SCSITaskLib.h>

#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \
    __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__>=1050
#define stat64	stat
#define fstat64	fstat
#endif

static io_object_t		  scsiob=IO_OBJECT_NULL;
static IOCFPlugInInterface	**plugin=NULL;
static MMCDeviceInterface	**mmcdif=NULL;
static SCSITaskDeviceInterface	**taskif=NULL;

char *setup_fds (char *device)
{ uid_t		uid=getuid();
  struct stat	sb,sc;

    /*
     * We might be entering as euid=root!
     */
    if ((in_fd = open (device,O_RDONLY)) < 0)
	fprintf (stderr,":-( unable to open(\"%s\",O_RDONLY): ",device),
	perror (NULL), exit (FATAL_START(errno));

    if (fstat (in_fd,&sb) < 0)
	fprintf (stderr,":-( unable to stat(\"%s\"): ",device),
	perror (NULL), exit (FATAL_START(errno));

    if (!S_ISCHR(sb.st_mode))
    {	setuid(uid);	/* drop all privileges	*/
	close (in_fd);	/* reopen as mortal	*/
	if ((in_fd = open (device,O_RDONLY)) < 0)
	    fprintf (stderr,":-( unable to reopen(\"%s\",O_RDONLY): ",device),
	    perror (NULL), exit (FATAL_START(errno));
      open_rw:
	if ((out_fd = open (device,O_RDWR)) < 0)
	    fprintf (stderr,":-( unable to open(\"%s\",O_RDWR): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if (fstat(out_fd,&sc) < 0)
	    fprintf (stderr,":-( unable to stat(\"%s\"): ",device),
	    perror (NULL), exit (FATAL_START(errno));
	if (sb.st_dev!=sc.st_dev || sb.st_ino!=sc.st_ino)
	    fprintf (stderr,":-( %s: race condition detected!\n",device),
	    exit(FATAL_START(EPERM));
      opened_rw:
	poor_man = 0;
	if (ioctl_handle && ioctl_handle != INVALID_HANDLE)
	{   if (taskif)	(*taskif)->Release(taskif),		taskif=NULL;
	    if (mmcdif)	(*mmcdif)->Release(mmcdif),		mmcdif=NULL;
	    if (plugin)	IODestroyPlugInInterface(plugin),	plugin=NULL;
	}
	ioctl_handle = INVALID_HANDLE;
	setuid (uid);	/* drop all privileges	*/
	return device;
    }

    /*
     * Still as euid=root! But note that get_mmc_profile makes sure it's
     * an MMC device, as it terminates the program if the unit doesn't
     * reply to GET CONFIGURATON. In combination with following switch
     * this means that if installed set-root-uid, growisofs grants
     * access to DVD burner(s), but not to any other device.
     */

    { io_object_t		parent;
      CFMutableDictionaryRef	match,bsddev;
      CFNumberRef		num;
      kern_return_t		kret;
      int			i;
      SInt32			score=0;

	if ((match = CFDictionaryCreateMutable (kCFAllocatorDefault,0,
				&kCFTypeDictionaryKeyCallBacks,
				&kCFTypeDictionaryValueCallBacks)) == NULL
	    ||
	    (bsddev = CFDictionaryCreateMutable (kCFAllocatorDefault,0,
				&kCFTypeDictionaryKeyCallBacks,
				&kCFTypeDictionaryValueCallBacks)) == NULL)
	{   if (match)	CFRelease (match);
	    fprintf (stderr,":-( unable to CFDictionaryCreateMutable\n");
	    exit (FATAL_START(ENOMEM));
	}

	i = major (sb.st_rdev);
	num = CFNumberCreate (kCFAllocatorDefault,kCFNumberIntType,&i);
	CFDictionarySetValue (bsddev,CFSTR("BSD Major"),num);
	CFRelease (num);

	i = minor (sb.st_rdev);
	num = CFNumberCreate (kCFAllocatorDefault,kCFNumberIntType,&i);
	CFDictionarySetValue (bsddev,CFSTR("BSD Minor"),num);
	CFRelease (num);

	CFDictionarySetValue (match,CFSTR(kIOPropertyMatchKey),bsddev);
	CFRelease (bsddev);

	if ((scsiob = IOServiceGetMatchingService (kIOMasterPortDefault,match))
	    == IO_OBJECT_NULL)
	{   fprintf (stderr,":-( unable to IOServiceGetMatchingService\n");
	    exit (FATAL_START(ENXIO));
	}

	// traverse up to "SCSITaskAuthoringDevice"
	while ((kret = IORegistryEntryGetParentEntry (scsiob,kIOServicePlane,
				&parent)) == kIOReturnSuccess)
	{ CFStringRef	uclient;
	  const char	*s;
	  int		cmp;

	    IOObjectRelease (scsiob);
	    scsiob = parent;
	    uclient = (CFStringRef)IORegistryEntryCreateCFProperty (scsiob,
				CFSTR(kIOPropertySCSITaskDeviceCategory),
				kCFAllocatorDefault,0);
	    if (uclient)
	    {	s = CFStringGetCStringPtr (uclient,kCFStringEncodingMacRoman);
		cmp = strcmp (s,kIOPropertySCSITaskAuthoringDevice);
		CFRelease (uclient);
		if (cmp==0)	break;
	    }
	}
	if (kret!=kIOReturnSuccess)
	{   if (scsiob!=IO_OBJECT_NULL)	IOObjectRelease (scsiob);
	    fprintf (stderr,":-( unable to locate \"SCSITaskAuthoringDevice\""
			    ": %x\n",kret);
	    exit (FATAL_START(ENXIO));
	}

	if ((kret = IOCreatePlugInInterfaceForService (scsiob,
				kIOMMCDeviceUserClientTypeID,
				kIOCFPlugInInterfaceID,
				&plugin,&score)) != kIOReturnSuccess)
	{   IOObjectRelease (scsiob);
	    fprintf (stderr,":-( unable to IOCreatePlugInInterface"
			    ": 0x%x\n",kret);
	    exit (FATAL_START(errno=ENXIO));
	}
	if ((*plugin)->QueryInterface (plugin,
				CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID),
				(void**)&mmcdif) != S_OK)
	{   IODestroyPlugInInterface (plugin),	plugin=NULL;
	    IOObjectRelease (scsiob);
	    fprintf (stderr,":-( unable to QueryInterface\n");
	    exit (FATAL_START(ENXIO));
	}
	if ((taskif = (*mmcdif)->GetSCSITaskDeviceInterface (mmcdif)) == NULL)
	{   (*mmcdif)->Release (mmcdif),	mmcdif=NULL;
	    IODestroyPlugInInterface (plugin),	plugin=NULL;
	    IOObjectRelease (scsiob);
	    fprintf (stderr,":-( unable to GetSCSITaskDeviceInterface\n");
	    exit (FATAL_START(ENXIO));
	}

	/*
	 * ioctl_handle is reassigned to taskif after ObtainExclusiveAccess
	 */
	ioctl_handle = mmcdif;
    }

    mmc_profile = get_mmc_profile (ioctl_handle);
    switch (mmc_profile&0xFFFF)
    {	case 0x11:	/* DVD-R			*/
	case 0x12:	/* DVD-RAM			*/
	case 0x13:	/* DVD-RW Restricted Overwrite	*/
	case 0x14:	/* DVD-RW Sequential		*/
	case 0x15:	/* DVD-R Dual Layer Sequential	*/
	case 0x16:	/* DVD-R Dual Layer Jump	*/
	case 0x1A:	/* DVD+RW			*/
	case 0x1B:	/* DVD+R			*/
	case 0x2A:	/* DVD+RW Double Layer		*/
	case 0x2B:	/* DVD+R Double Layer		*/
	case 0x41:	/* BD-R SRM			*/
	case 0x42:	/* BD-R RRM			*/
	case 0x43:	/* BD-RE			*/
	open_poor_man:
	    poor_man = 1;
	    out_fd = dup(in_fd);	/* it's ignored in poor_man ... */
	    setuid (uid);	/* drop all privileges	*/
	    return ioctl_device=device;
	    break;
	default:
	    fprintf (stderr,":-( %s: media is not recognized as "
			    "recordable DVD: %X\n",device,mmc_profile);
	    exit (FATAL_START(EMEDIUMTYPE));
    }
    setuid(uid);
    goto open_rw;
}

#else
#error "Unsupported OS"
#endif

#if defined(__unix) || defined(__unix__)

static double this_very_moment()
{ struct timeval tv;
    gettimeofday (&tv,NULL);
  return tv.tv_usec/1e6+tv.tv_sec;
}

static int	signals_blocked;
static void	(*signal_handler)(void);

void sigs_mask (int mask)
{ sigset_t set;

    if (signals_blocked == mask) return;

    sigemptyset (&set);
    sigaddset (&set,SIGHUP),	sigaddset (&set,SIGINT),
    sigaddset (&set,SIGTERM),	sigaddset (&set,SIGPIPE);

    sigprocmask ((signals_blocked=mask)?SIG_BLOCK:SIG_UNBLOCK,&set,NULL);
}

static void common_handler(int sig)
{   if (!signals_blocked)
    {	progress.zero = 0;
	(*signal_handler)();
    }
}

#define sigit(SIG)	do {		\
    sigaction (SIG,NULL,&sa);		\
    sa.sa_mask = mask;			\
    sa.sa_handler = common_handler;	\
    sa.sa_flags &= ~SA_NODEFER;		\
    sa.sa_flags |= SA_RESETHAND;	\
    sigaction (SIG,&sa,NULL);		\
} while (0)

void atsignals(void(*func)(void))
{ sigset_t         mask;
  struct sigaction sa;

    signal_handler = func;

    sigemptyset (&mask);
    sigaddset (&mask,SIGHUP),	sigaddset (&mask,SIGINT),
    sigaddset (&mask,SIGTERM),	sigaddset (&mask,SIGPIPE);

    sigit (SIGHUP);	sigit (SIGINT);
    sigit (SIGTERM);	sigit (SIGPIPE);
}
#undef sigit

#elif defined(_WIN32)

static double this_very_moment()
{ LARGE_INTEGER t;
    GetSystemTimeAsFileTime((FILETIME *)&t);
  return t.QuadPart/1e7;
}

static BOOL	signals_blocked;
static void	(*signal_handler)(void);
static HANDLE	signal_thread;

void sigs_mask (int mask) { signals_blocked = mask;  }

static BOOL WINAPI ctrl_handler (DWORD code)
{   if (!signals_blocked)
    {	progress.zero = 0;
	SuspendThread (signal_thread);
	(*signal_handler)();
    }
  return TRUE;
}

void atsignals (void(*func)(void))
{   signal_handler = func;

    signal_thread = OpenThread (THREAD_SUSPEND_RESUME,FALSE,GetCurrentThreadId());
    if (signal_thread == NULL)
	perror (":-( unable to OpenThread"),
	_exit (FATAL_START(errno));

    if (!SetConsoleCtrlHandler (ctrl_handler,TRUE))
	perror (":-( unable to SetConsoleCtrlHandler"),
	_exit (FATAL_START(errno));
}

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

static int setup_C_parm (char *C_parm,struct iso_primary_descriptor *descr)
{ int next_session=-1,profile=mmc_profile&0xFFFF;

    if (!poor_man ||	profile==0x1A || profile==0x2A ||
			profile==0x13 || profile==0x12 ||
			profile==0x42 || profile==0x43)
    {	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 (	profile==0x2B || profile==0x1B ||
		profile==0x11 || profile==0x14 ||
		profile==0x15 || profile==0x16 ||
		profile==0x41)
	next_session=plusminus_r_C_parm (ioctl_handle,C_parm);

  return next_session;
}

/*
 * Threads workers
 */
THR_TYPE progress_print (void *arg)
{ double		ratio,velocity,slept;
  int			delta,rbu,nfirst=0;
  off64_t		outoff=*(off64_t *)arg;
  off64_t		lastcurrent=outoff,current;
#if defined(__unix) || defined(__unix__)
  sigset_t		set;

    sigfillset (&set);
    pthread_sigmask (SIG_SETMASK,&set,NULL);
#endif

    while (1)
    {	slept =  this_very_moment();
	lastcurrent = progress.current;
	poll (NULL,0,3333);
	slept -= this_very_moment();
	if (progress.zero==0 || !nfirst++) continue;
	if ((current = progress.current) > outoff)
	{   delta = time (NULL) - progress.zero;
	    ratio = (double)(progress.final-outoff) /
		    (double)(current-outoff);
	    delta *= ratio - 1.0;
	    velocity=(current-lastcurrent)/(-slept*1024*__1x());
	    rbu = (int)((current - outoff)/DVD_BLOCK);
	    rbu = highest_ecc_block - rbu;
	    fprintf (stdout,"%11"LLD"/%"LLD" (%4.1f%%) @%.1fx, "
			    "remaining %d:%02d "
			    "RBU %5.1f%% "	/* ring buffer utilization */
			    "UBU %5.1f%%\n",	/* unit buffer utilization */
			    current,progress.final,100.0/ratio,
			    velocity,delta/60,delta%60,
			    (100.0*rbu*DVD_BLOCK)/the_buffer_size,
			    (100.0*butlz));
	    lastcurrent=current;
	    butlz=1.00001;
	}
	else
	{   rbu = (int)((current - outoff)/DVD_BLOCK);
	    rbu = highest_ecc_block - rbu;
	    fprintf (stdout,"%11"LLD"/%"LLD" (%4.1f%%) @0x, "
			    "remaining ??:?? "
			    "RBU %5.1f%% "	/* ring buffer utilization */
			    "UBU %5.1f%%\n",	/* unit buffer utilization */
			    current,progress.final,0.0,
			    (100.0*rbu*DVD_BLOCK)/the_buffer_size,0.0);
	}
	fflush (stdout);
    }
}

/*
 * Synchronization between reader() and builtin_dd() threads is done
 * through a semaphore enumerating available ECC blocks in the ring
 * buffer and a volatile 32-bit variable denoting highest available
 * block. Special note about the synchronization on this variable.
 * Most would argue that access to this variable should be serialized
 * by a mutex, but is it really required? Being aligned at natural
 * processor word boundary [compiler does it!], both read and write
 * operations per se are atomic [hardware does it!] and the point is
 * that builtin_dd() thread only reads it, while only reader() thread
 * updates it. In this case of distinct and atomic reader and writer
 * a spin-lock upon given condition for variable in question is more
 * than sufficient. The spin-lock is complemented with quantum yield
 * in the loop body. This scheme is chosen in order to ensure maximum
 * responsiveness upon moment the first data block is available in
 * previously exhausted ring buffer, yet give the reader() thread
 * every opportunity to put the data there. One might argue that one
 * could take a nap in the spin-lock loop body, but the trouble is
 * that this is likely to incur undesired effects, because system
 * interval timer accuracy is commonly not less and sometimes even
 * much larger than characteristic time for recording of a single ECC
 * block at ever increasing burning velocity. Some OSes do offer
 * adequate interval timer resolution, but there're quite a few among
 * them which arrange it by looping in kernel mode for instrumented
 * amount of spins. This means that "yielding" spin-lock in user-land
 * is as appropriate for all practical reasons. Hibernating would be
 * due if a "catastrophic" event, LUN buffer underrun, is a known
 * fact, in which case one can afford falling asleep for coarse
 * interval ["coarse" refers to OS interval timer resolution in
 * comparison to characteristic ECC block recording time].
 */

THR_TYPE reader (void *arg)
{ int		n,infd=(size_t)arg;
  unsigned int	off,mask=(the_buffer_size/DVD_BLOCK)-1;
  char		*block;
#if defined(__unix) || defined(__unix__)
  sigset_t		set;

    sigfillset (&set);
    pthread_sigmask (SIG_SETMASK,&set,NULL);
#endif

    while (1)
    {	if (!__semaphore_wait(the_ring_semaphore)) {
#if defined(__unix) || defined(__unix__)
	    if (errno==EINTR) continue;
#endif
	    return (reader_exit = errno);
	}
	block = the_buffer + (highest_ecc_block & mask)*DVD_BLOCK;
	off = 0;
    eintr:
	while ((n=read (infd,block+off,DVD_BLOCK-off)) > 0)
	{   off += n;
	    if (off == DVD_BLOCK)
	    {	highest_ecc_block++;
		break;
	    }
	}
	if (n<0) {
#if defined(__unix) || defined(__unix__)
	    if (errno==EINTR) goto eintr;
#endif
	    return (reader_exit = errno);
	}
	if (n==0)
	{   if (off)	memset (block+off,0,DVD_BLOCK-off),
			highest_ecc_block++;
	    break;
	}
    }

  return (reader_exit = (unsigned int)-1);
}

/*
 * This is executed in main thread context
 */
int builtin_dd (int infd,int outfd,off64_t outoff)
{ char		*block;
  int		 n;
  unsigned int	 off,mask;
  struct stat64  sb;
  off64_t	 capacity=0,tracksize=0,startoff=outoff;

    if (fstat64 (infd,&sb))
#ifdef _WIN32
	memset (&sb,0,sizeof(sb)), sb.st_mode = (~_S_IFREG)&_S_IFMT;
#else
	perror (":-( unable to fstat64"), exit(FATAL_START(errno));
#endif

    if (ioctl_handle!=INVALID_HANDLE)
	capacity = get_capacity (ioctl_handle);

    progress.zero=0;
    progress.current=outoff;
    if (dao_size || S_ISREG(sb.st_mode))
    {	tracksize = dao_size ? (dao_size*CD_BLOCK) : sb.st_size;
	progress.final=outoff+tracksize;
	if (capacity && progress.final > capacity)
	{   fprintf (stderr,":-( %s: %"LLD" blocks are free, "
			    "%"LLD" to be written!\n",
			    ioctl_device,
			    (capacity-outoff)/2048,tracksize/2048);
	    if (overburn)
		fprintf (stderr,":-! ignoring...\n");
	    else
		close(infd), close(outfd),
		exit (FATAL_START(ENOSPC));
	}
    }
    else progress.final=0;

    /* suck in first 64K and examine ISO9660 Primary Descriptor if present */
    off = 0;
    while ((n=read (infd,the_buffer+off,2*DVD_BLOCK-off)) > 0)
    {	off += n;
	if (off == 2*DVD_BLOCK)
	{   if (!memcmp(the_buffer+DVD_BLOCK,"\1CD001",6))
	    { struct iso_primary_descriptor *descr = saved_descriptors;

		/*
		 * Save descriptor set for use at the end of recording!
		 */
		memcpy (saved_descriptors,the_buffer+DVD_BLOCK,DVD_BLOCK);

		if (!zero_image)
	        {   if (tracksize==0)
		    {	tracksize=from_733(descr->volume_space_size)*CD_BLOCK;
			if (capacity && (outoff+tracksize) > capacity)
		    	{   fprintf (stderr,":-( %s: %"LLD" blocks are free, "
					    "%"LLD" to be written!\n",
					    ioctl_device,
					    (capacity-outoff)/2048,
					    tracksize/2048);
			    if (overburn)
				fprintf (stderr,":-! ignoring...\n");
			    else
			    {	n = -1; set_errno(FATAL_START(ENOSPC));
				goto out;
			    }
			}
		    }
		    /* else already checked for overburn condition */

		    /* layer_break is meaningful only for -Z recording */
		    if (layer_break>0 && !outoff)
		    {	if (tracksize > layer_break*CD_BLOCK*2)
			{   fprintf (stderr,":-( insane Layer Break position "
					    "with respect to dataset size\n");
			    n = -1; set_errno(FATAL_START(EINVAL));
			    goto out;
			}
			if (!progress.final) progress.final = tracksize;
			//tracksize = layer_break*CD_BLOCK*2;
		    }
		}
		else if (capacity > outoff)
		{ int i=0;
		  unsigned int ts = (tracksize=capacity-outoff)/2048;

		    while (i<16 && descr->type[0] != (unsigned char)255)
			to_733 (descr->volume_space_size,ts),
			descr++, i++;
		}
		else
		{   fprintf (stderr,":-( capacity is undefined or insane?\n");
		    n = -1; set_errno(FATAL_START(EINVAL));/* ... or whatever */
		    goto out;
		}
	    }
	    else if (outoff && zero_image)
	    {	fprintf (stderr,":-( no volume descriptors found "
				"in previous session?\n");
		n = -1; set_errno(FATAL_START(ENOTDIR)); /* kludge! */
		goto out;
	    }
	    break;
	}
    }
    if (n<=0) goto out;

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

    if (quiet<=0) __thread_create(progress_print,&outoff);

    /* yeah, really kludgy, shuffling file descriptor like that... */
    if (zero_image)
	close(infd), infd=open64 ("/dev/zero",O_RDONLY);

    highest_ecc_block = off/DVD_BLOCK;
    assert (highest_ecc_block==2);
    /* Fill the_buffer up to the_buffer_size */
    while ((n=the_buffer_size-off, n%=DVD_BLOCK, n=n?n:DVD_BLOCK,
	    n=read (infd,the_buffer+off,n)) > 0)
    {	off += n;
	if (off == the_buffer_size)
	    break;
    }
    if (n<0) goto out;

    highest_ecc_block = (off+DVD_BLOCK-1)/DVD_BLOCK;

    if (off==the_buffer_size)
    {	the_ring_semaphore = __semaphore_create(the_buffer_size/DVD_BLOCK);
	if (the_ring_semaphore == NULL)
	    perror(":-( failed to create semaphore"), exit(FATAL_START(errno));
	if (__thread_create(reader,(void *)(size_t)infd) == NULL)
	    perror(":-( failed to create thread"), exit(FATAL_START(errno));
	reader_exit = 0;
    }
    else
    {	memset (the_buffer+off,0,the_buffer_size-off);
	reader_exit = (unsigned int)-1;	/* reader "exited" already */
    }

    if (poor_man || next_session==0)	/* unmount media */
    {
#if defined(__unix) || defined(__unix__)
      pid_t rpid,pid;
      int   rval;

	if ((pid=fork()) == (pid_t)-1)
	    perror (":-( unable to fork -umount"), exit (FATAL_START(errno));

	/* pass through set-root-uid if any */
	if (pid == 0)
	{   char str[12];

	    if ((rval=fcntl (in_fd,F_GETFD))<0) rval=0;
	    fcntl (in_fd,F_SETFD,rval&~FD_CLOEXEC);

	    sprintf (str,"%d",in_fd);
	    execlp (argv0,"-umount",str,in_device,NULL);
	    exit (FATAL_START(errno));
	}
	while (1)
	{   rpid = waitpid (pid,&rval,0);
	    if (rpid == (pid_t)-1 || (rpid != pid && (errno=ECHILD,1)))
	    {	if (errno==EINTR)	continue;
		else			perror (":-( waipid failed"),
					exit(FATAL_START(errno));
	    }
	    if (WIFSTOPPED(rval))	continue;
	    errno=0;
	    if (WIFEXITED(rval))	errno=WEXITSTATUS(rval);
	    else			errno=ECHILD;
	    break;
	}
	set_errno(errno&FATAL_MASK);
	if (errno)
	{   if (errno==EBUSY)
		fprintf (stderr,":-( %s: unable to proceed with recording: ",
			    in_device),
#ifdef __hpux
		fprintf (stderr,"device is mounted\n"),
#else
		fprintf (stderr,"unable to unmount\n"),
#endif
		exit (FATAL_START(errno));
	    else
		fprintf (stderr,":-( unable to umount %s: ",in_device);
	    perror (""), exit (FATAL_START(errno));
	}
#elif defined(_WIN32)
	if (fumount (ioctl_fd))
	    fprintf (stderr,":-( unable to umount %s: ",in_device),
	    perror (NULL), exit (FATAL_START(errno));
#endif
    }

    if (poor_man)
    {
#ifdef __linux
      int i;
	/*
	 * Linux 2.6 kernel allows to claim O_EXCL on block device.
	 * However! It does not really exclude the possibility for
	 * another application to interfere with ongoing recording,
	 * because kernel serializes *only* O_EXCL calls and lets
	 * through those without. And the trouble naturally is that
	 * there are automounting/autoplaying facilities, which
	 * don't adhere to O_EXCL. Note that mount(2) system call
	 * does acquire such exclusive lock at kernel level all by
	 * itself, but most commonly it's user-land file system
	 * detection subroutines, those determining 3rd argument
	 * for mount(2) call, which turn to be the culprit. E.g.
	 * among examined mount(8) and submountd(8) both were found
	 * needing patching. Once mount(8) is patched, one can allow
	 * autofs to handle DVD recorder [because automount deploys
	 * mount(8)]. And once submountd(8) is patched it would be
	 * possible to exempt subfs mount point from umount method
	 * in transport.hxx. Keep in mind that this "list" is not
	 * by any means complete...
	 */

	for (i=3;i>=0;i--)
	{   /*
	     * For reference, I can't do it earlier as exclusive lock
	     * could have been granted to mounted file system, the one
	     * we've tried to unmount just a moment ago...
	     */
	    int fd = open64 (ioctl_device,O_RDONLY|O_NONBLOCK|O_EXCL);
	    struct stat64 sb,sc;

	    if (fd<0)
	    {	if (errno==EBUSY)
		{   if (i==0)
			fprintf (stderr,":-( unable to O_EXCL %s: some"
					    "one was in time to remount?\n",
					ioctl_device),
			exit (FATAL_START(errno));

		    poll(NULL,0,157);	/* retry... */
		}
		else	break;		/* running under 2.4? */
	    }
	    else
	    {	if (fstat64 (ioctl_fd,&sb) || fstat64 (fd,&sc) ||
		    sb.st_rdev != sc.st_rdev)
		    fprintf (stderr,":-( %s: race condition detected!\n",
				    ioctl_device),
		    exit(FATAL_START(EPERM));
		/*
		 * Note that I effectively leak this file descriptor,
		 * but as it's meant to be closed at the very end, I
		 * might as well let kernel clean it up automagically
		 * upon process termination...
		 */
		break;
	    }
	}
#elif defined(__APPLE__) && defined(__MACH__)
      IOReturn rval;

	close (in_fd);
	close (out_fd);

	if ((rval = (*taskif)->ObtainExclusiveAccess (taskif)) 
			!= kIOReturnSuccess)
	{   (*taskif)->Release (taskif),	taskif=NULL;
	    (*mmcdif)->Release (mmcdif),	mmcdif=NULL;
	    IODestroyPlugInInterface (plugin),	plugin=NULL;
	    IOObjectRelease (scsiob),		scsiob=IO_OBJECT_NULL;
	    fprintf (stderr,":-( unable to ObtainExclusiveAccess: 0x%x\n",rval);
	    exit (FATAL_START(EBUSY));
	}

	ioctl_handle = taskif;
#endif
	/*
	 * See commentary section in growisofs_mmc.cpp for
	 * further details on poor_mans_setup
	 */
	pwrite64_method = poor_mans_setup (ioctl_handle,
					   outoff+tracksize, (unsigned int)layer_break);
    }

    if (!progress.final)
    {	if (tracksize)	progress.final = outoff+tracksize;
	else		progress.final = capacity;
    }
    if (capacity && progress.final>capacity)
	progress.final = capacity;

    progress.zero=time(NULL);

    off  = 0;	/* off is now used as written ECC block counter!!! */
    mask = (the_buffer_size/DVD_BLOCK)-1;
    while (1)
    {	/* "yielding" spin-lock */
	while (!reader_exit && (off == highest_ecc_block))
	{   if (poor_man && butlz>1.0) butlz = get_buffer_stats(ioctl_handle);
	    __thread_yield();
	}

	if (off == highest_ecc_block) break;	/* no more data */

        block = the_buffer + (off & mask)*DVD_BLOCK;
	if ((n=(*pwrite64_method) (outfd,block,DVD_BLOCK,outoff)) != DVD_BLOCK)
	{   if (n>0)		set_errno(EIO);
	    else if (n==0)	set_errno(ENOSPC);
	    n = -1;
	    goto out;
	}
	if (the_ring_semaphore) __semaphore_post(the_ring_semaphore);

	/* collect statistics every 64th block or every second MB */
	if (poor_man && (off&63)==0)
	{ float u = get_buffer_stats(ioctl_handle);
	    /* but show only the minimal value; might appear a bit
	     * alarming, but it's really more informative... */
	    if (u<butlz) butlz=u;
	}

	off++;
	outoff += DVD_BLOCK;
	progress.current=outoff;
    }
    n = (reader_exit==(unsigned int)-1)?0:(set_errno(reader_exit),-1);

    printf ("builtin_dd: %"LLD"*2KB out @ average %.1fx%dKBps\n",
	    (outoff-startoff)/2048,
	    ((outoff-startoff)/(1024.0*__1x()))/(time(NULL)-progress.zero),
	    __1x());
out:
    if (quiet<=0) progress.zero=0; /* shut the progress_print thread up */

    { int saved_errno=errno;

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

	set_errno(outoff ? saved_errno : (n--?saved_errno:FATAL_START(EIO)));
    }

  return n;
}

/*
 * Setup pipe to mkisofs and start it
 */
#if defined(__unix) || defined(__unix__)

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

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

    if ((mkisofs_pid=fork ()) == (pid_t)-1)
	perror (":-( unable to fork mkisofs"), exit(FATAL_START(errno));
    else if (mkisofs_pid == 0)
    {	dup2  (fildes[1],1);
	close (fildes[0]);
	close (fildes[1]);
	close (outfd);	/* redundant:-) */

	if (next_session)
	{
#ifdef PASS_STDIN_TO_MKISOFS
	    dup2(infd,0);
	    close(infd);
	    infd=0;
#endif
	    if ((n=fcntl (infd,F_GETFD))<0) n=0;
	    fcntl (infd,F_SETFD,n&~FD_CLOEXEC);
	}
	else
	    close (infd);
	/*
	 * If platform-specific setup_fds did not drop privileges,
	 * do it now. I ignore return value because if it fails,
	 * then privileges were dropped already.
	 */
	setuid(getuid());
	execvp (mkisofs_argv[0],mkisofs_argv);
	fprintf (stderr,":-( unable to execute %s: ",mkisofs_argv[0]),
	perror (NULL), exit (FATAL_START(errno));
    }

    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 (errno);

	if (!WIFEXITED(ret) || WEXITSTATUS(ret)!=0)
	    fprintf (stderr,":-( %s has failed: %d\n",mkisofs_argv[0],WEXITSTATUS(ret)),
	    exit (errno=WIFEXITED(ret)?WEXITSTATUS(ret):255);
    }
    else if (n<0)
    { int err = errno;
	errno = err&FATAL_MASK;	/* they might be passing FATAL_START */
	perror (":-( write failed"), exit (err);
    }
}

#elif defined(_WIN32)

void pipe_mkisofs_up (char *mkisofs_argv[],int infd,int outfd,off64_t outoff)
{ STARTUPINFO		si;
  PROCESS_INFORMATION	pi;
  HANDLE		hRead;
  int			n;
  char			*cmd = (char *)saved_descriptors; /* borrow it */

    memset (&si,0,sizeof(si));
    si.cb	= sizeof(si);
    si.dwFlags	= STARTF_USESTDHANDLES;

    if (!CreatePipe (&hRead,&si.hStdOutput,NULL,0))
	perror (":-( unable to create pipe"), exit (FATAL_START(errno));
    SetHandleInformation (si.hStdOutput,HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT);

    si.hStdInput = (HANDLE)_get_osfhandle (infd);
    SetHandleInformation (si.hStdInput,HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT);

    si.hStdError = GetStdHandle (STD_ERROR_HANDLE);
    SetHandleInformation (si.hStdError,HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT);

    { char **argv,*arg;
      size_t len0;
      size_t cmdsz = sizeof(saved_descriptors);

	if (cmdsz>32*1024) cmdsz = 32*1024;

	if ((arg=strrchr(mkisofs_argv[0],'\\')))arg++;
	else					arg = mkisofs_argv[0];

	cmdsz--;
	assert ((len0=strlen (arg)) < cmdsz);

	strcpy (cmd,arg), cmd[len0++] = ' ', cmdsz -= len0;

	for (argv=mkisofs_argv+1;*argv;argv++)
	{   size_t len1 = strlen (*argv);
	    int    quot = 0;

	    if (strchr (*argv,' ') && (*argv)[0]!='"' && (*argv)[len1-1]!='"')
		quot = 2;
	    assert ((len1 + quot) < cmdsz);

	    if (quot) cmd[len0++] = '"',cmdsz--;
    	    strcpy (cmd+len0,*argv),	cmdsz-=len1, len0+=len1;
	    if (quot) cmd[len0++] = '"',cmdsz--;
	    cmd[len0++] = ' ';
	}

	if (cmd[len0-1] == ' ')	cmd[len0-1] = '\0';
	else			cmd[len0]   = '\0';
    }

    if (!CreateProcess (mkisofs_argv[0],cmd,NULL,NULL,
			HANDLE_FLAG_INHERIT,
    			0,NULL,NULL,&si,&pi))
	fprintf (stderr,":-( unable to execute %s: ", mkisofs_argv[0]),
	perror(NULL), exit (FATAL_START(errno));

    memset (saved_descriptors,0,sizeof(saved_descriptors));
    CloseHandle (si.hStdOutput);
    CloseHandle (pi.hThread);

    n=builtin_dd(_open_osfhandle((size_t)hRead,O_RDONLY),outfd,outoff);

    if (n==0) /* mkisofs must have finished, consume the exit code */
    { DWORD ret;

	if (GetExitCodeProcess (pi.hProcess,&ret) && ret)
	    fprintf (stderr,":-( mkisofs has failed: %d\n",ret),
	    exit (1);
	else
	    perror (":-( GetExitCodeProcess failed"), exit(errno);
    }
    else if (n<0)
    { int err = errno;
	set_errno(err&FATAL_MASK);	/* they might be passing FATAL_START */
	perror (":-( write failed"), exit (err); 
    }
}

#endif

int main (int argc, char *argv[])
{ int  imgfd=-1;
  char *in_image=NULL,*env;
  char dev_found='\0';
  int  i,n,warn_for_isofs=0;
  char **mkisofs_argv,C_parm[24],M_parm_[16],*M_parm=M_parm_;
  int  mkisofs_argc,growisofs_argc;
  int  alleged_next_session=-1;
  unsigned int new_size;

    argv0 = argv[0];
#if defined(__unix) || defined(__unix__)
#if !defined(I_KNOW_ALL_ABOUT_SUDO)
    if (getenv ("SUDO_COMMAND"))
    {	fprintf (stderr,":-( %s is being executed under sudo, "
			"aborting!\n",argv[0]);
	fprintf (stderr,"    See NOTES paragraph in growisofs "
			"manual page for further details.\n");
	exit(FATAL_START(EACCES));
    }
#endif
    /*
     * This is a set-root-uid "entry point" for listed operations. User
     * can't trick this code to unmount arbitrary file system, as [s]he
     * has to pass opened file descriptor to the mounted device. As for
     * file descriptor passed by this program itself, I rely upon the
     * fact that it was appropriately audited at open time in platform-
     * specific setup_fds above...
     */
    if (*argv[0] == '-')
    { int		fd;
      struct stat	fdst;
      unsigned int	cap2k=0;

	chdir ("/");
	if (argc < 3)	exit (EINVAL);
	fd=atoi (argv[1]);
	if (!strcmp(argv[0],"-umount"))
	{   if (fumount (fd)) exit (errno);
	    exit (0);
	}
	else if ( (!strcmp(argv[0],"-reload") && (no_reload=0,1)) ||
		  (!strcmp(argv[0],"-eject") && (no_reload=-1,1)) )
	{   if (fstat (fd,&fdst) < 0)
		perror (":-( unable to fstat"),	exit (1);

	    if (argc > 3) cap2k = (unsigned int)strtoul (argv[3],NULL,0);

	    close (fd);

	    if (media_reload (argv[2],&fdst,cap2k))
		perror (":-( unable to reload tray"), exit (1);
	    exit (0);
	}
	exit(1);
    }

    /*
     * Ignore return values as we might be running as mortals
     */
    nice(-20);
    /* I'd rather do it right after I allocate ring buffer and fire off
     * threads, but I'm likely to be running without extra privileges
     * by then:-( */
    do {
# ifdef RLIMIT_MEMLOCK
	struct rlimit	rlim;

	if (getrlimit(RLIMIT_MEMLOCK,&rlim)) break;

	/* those who want to increase beyond DEFAULT_BUF_SIZE_MB have
	 * to 'limit memorylocked unlimited' or whatever appropriate
	 * at command prompt or in wrapper script */
	if (rlim.rlim_cur < (DEFAULT_BUF_SIZE_MB+16)*1024*1024)
	{   rlim.rlim_cur = (DEFAULT_BUF_SIZE_MB+16)*1024*1024;
	    if (rlim.rlim_max < rlim.rlim_cur) rlim.rlim_max = rlim.rlim_cur;
	    if (setrlimit(RLIMIT_MEMLOCK,&rlim)) break;
	}
# endif
# ifdef __FreeBSD__
	syscall(SYS_mlockall,3);
# else
	mlockall(MCL_CURRENT|MCL_FUTURE);
# endif
    } while (0);

# if (defined(__sun) || defined(sun)) && defined(setppriv)
    if (basic_priv && getuid() != 0)
    {	/* this is done to allow secure -Z /dev/dvd=image.iso processing */
	setppriv (PRIV_SET,PRIV_EFFECTIVE,basic_priv);
	/* this is done to allow secure mkisofs startup */
	setppriv (PRIV_SET,PRIV_INHERITABLE,basic_priv);
	if ((permitted_priv=priv_allocset()))
	    getppriv (PRIV_PERMITTED,permitted_priv);
    }
# endif
#endif

    mkisofs_argv = malloc ((argc+3)*sizeof(char *));
    if (mkisofs_argv == NULL)
        fprintf (stderr,":-( unable to allocate %lu bytes: ",
			(unsigned long)((argc+3)*sizeof(char *))),
	perror (NULL), exit (FATAL_START(errno));

#if defined(__unix) || defined(__unix__)
    env = getenv ("MKISOFS");
    mkisofs_argv[0] = (env?env:"mkisofs");
#elif defined(_WIN32)
    /*
     * On Windows I insist on mkisofs.exe to reside in very same
     * directory as growisofs.exe. Unlike Unix that is...
     */
    { char *backslash,*borrow = (char *)saved_descriptors;

	GetModuleFileName (NULL,borrow,sizeof(saved_descriptors));
	backslash = strrchr(borrow,'\\');
	if (backslash)	backslash++;
	else		backslash = borrow;
	strcpy (backslash,"mkisofs.exe");
	mkisofs_argv[0] = strdup(borrow);
	memset (saved_descriptors,0,sizeof(saved_descriptors));
    }
#endif

    mkisofs_argc = 1;
    growisofs_argc=0;

    _argc=argc,	_argv=argv;

    for (i=1;i<argc;i++)
    { int len=strlen(argv[i]);
      char *opt;

	dev_found = '\0';
	if (argv[i][0] == '-')
	{   opt = argv[i] + ((argv[i][1]=='-')?(len--,1):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(opt,"-prev-session",13))
	    {	if (len > 13) in_device = opt+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(opt,"-zero-session",13))
	    {	if (len > 13) in_device = opt+13;
		else          in_device = argv[++i];
		dev_found = 'Z';
	    }
	    else if (argv[i][1] == 'F')
	    {	if (len > 2) in_device = argv[i]+2;
		else	     in_device = argv[++i];
		dev_found = 'F';
		dry_run = 1;
	    }
	    else if (!strncmp(opt,"-free-space",11))
	    {	if (len > 11) in_device = opt+11;
		else          in_device = argv[++i];
		dev_found = 'F';
		dry_run = 1;
	    }
	    else if (!strcmp(opt,"-poor-man"))
	    {	if (poor_man<0) poor_man = 1;
		continue;
	    }
	    else if (!strncmp(opt,"-speed",6))
	    { char *s;
		if (len>6)	(s=strchr(opt,'='))?s++:s;
		else		s=argv[++i];
		if (s)		speed_factor=atof(s);
		if (speed_factor<=0)
		    fprintf (stderr,"-speed=%.1f: insane speed factor.\n",
				    speed_factor),
		    exit(FATAL_START(EINVAL));
		continue;
	    }
	    else if (!strcmp(opt,"-dvd-compat"))
	    {	if (poor_man<0) poor_man = 1;
		dvd_compat++;
		continue;
	    }
	    else if (!strcmp(opt,"-overburn"))
	    {	overburn = 1;
		continue;
	    }
	    else if (argv[i][1] == 'o')
	    {	if (!strchr(argv[i]+2,'-'))	/* somewhat opportunistic... */
		    fprintf (stderr,"%s: -o[utput] option "
				    "is not permitted.\n",argv[0]),
		    exit(FATAL_START(EINVAL));
	    }
	    else if (!strncmp(opt,"-use-the-force-luke",19))
	    { char *s=strchr (opt,'='),*o;

		if (s == NULL)	/* backward compatibility */
		    no_tty_check = 1;
		else
		{   s++;
		    if (strstr(s,"tty"))	no_tty_check = 1;
		    if (strstr(s,"dummy"))	test_write   = 1;
		    if (strstr(s,"notray"))	no_reload    = 1;
		    if (strstr(s,"noload"))	no_reload    = -1;
		    if (strstr(s,"wrvfy"))	wrvfy        = 1;
		    if (strstr(s,"4gms"))	no_4gb_check = 1;
		    if (strstr(s,"noopc"))	no_opc       = 1;
		    if (strstr(s,"moi"))
		    {	quiet=-1; mkisofs_argv[mkisofs_argc++] = "-quiet";   }
		    if ((o=strstr(s,"dao")))
		    {	dvd_compat  += 256;
			/*  vvvvvvvvvvv tracksize option takes precedence! */
			if (dao_size==0 && (o[3]==':' || o[3]=='='))
			{   dao_size=strtol(o+4,0,0);
			    if (dao_size<=0)
				fprintf (stderr,":-( insane dao%c%d option\n",
						o[3],dao_size),
				exit(FATAL_START(EINVAL));
			}
		    }
		    if ((o=strstr(s,"tracksize")))
		    {	if (o[9]==':' || o[9]=='=')
			{   dao_size=strtol(o+10,0,0);
			    if (dao_size<=0)
				fprintf (stderr,":-( insane tracksize%c%d option\n",
						o[9],dao_size),
				exit(FATAL_START(EINVAL));
			}
		    }
		    if ((o=strstr(s,"break")))
		    {	if (o[5]==':' || o[5]=='=')
			{   layer_break=strtol(o+6,0,0);
			    if (layer_break<=0 || layer_break%16)
				fprintf (stderr,":-( insane break%c%d option\n",
						o[5],layer_break),
				exit(FATAL_START(EINVAL));
			}
		    }
		    if ((o=strstr(s,"seek")) && next_session<0)
		    {	if (o[4]==':' || o[4]=='=')
			{   next_session=strtol(o+5,0,0);
			    if (next_session<0 || next_session%16)
				fprintf (stderr,":-( insane seek%c%d option\n",
						o[4],next_session),
				exit(FATAL_START(EINVAL));
			}
		    }
		    if ((o=strstr(s,"bufsize")))
		    {	if (o[7]==':' || o[7]=='=')
			{   the_buffer_size=strtol(o+8,&o,0);
			    if (*o=='M' || *o=='m')
				the_buffer_size*=1024*1024;
			    else if (*o=='K' || *o=='k')
				the_buffer_size*=1024;
			}
		    }
		    if ((o=strstr(s,"spare")))
		    {	spare=1;
		    	if (o[5]==':' || o[5]=='=')
			{   if      (!strncmp(o+6,"none",4))	spare=-1;
			    else if (!strncmp(o+6,"min",3))	spare=0;
			}
		    }
		}
		continue;
	    }
	    else if (!strcmp(opt,"-dvd-video"))
	    {	if (poor_man<0) poor_man = 1;
		dvd_compat++,	growisofs_argc++;
	    }
	    else if (!strcmp(opt,"-quiet"))
		quiet++,			growisofs_argc++;
	    else if (argv[i][1] == 'C' || !strncmp(opt,"-cdrecord-params",16))
	    { char *s=argv[i+1];
	      int   i1,i2;
		if (argv[i][1]=='C' && len>2)	s=argv[i]+2;
		else				i++;
		if (sscanf (s,"%d,%d",&i1,&i2) == 2)
		    alleged_next_session=i2;
		continue;
	    }
	    else if (argv[i][1] == '#' || !strcmp(opt,"-dry-run"))
	    {	dry_run = 1;
		continue;
	    }
	    else if (argv[i][1] == '?' || !strcmp(opt,"-help"))
	    {	PRINT_VERSION (argv[0]);
		printf ("- usage: %s [-dvd-compat] [-overburn] [-speed=1] \\\n"
			"         -[ZM] /dev/dvd <mkisofs options>\n",argv[0]);
		printf ("  for <mkisofs options> see 'mkisofs %s'\n",opt);
		exit (FATAL_START(EINVAL));
	    }
	    else if (strstr (opt,"-version"))
	    {	PRINT_VERSION (argv[0]);
		printf ("  front-ending to %s: ",mkisofs_argv[0]);
		fflush (stdout);
#if defined(__unix) || defined(__unix__)
		setuid(getuid());
		{ char *argv[3];
		    argv[0] = mkisofs_argv[0];
		    argv[1] = "-version";
		    argv[2] = NULL;
		    execvp (mkisofs_argv[0],argv);
		}
#elif defined(_WIN32)
		if (_spawnl (_P_WAIT,mkisofs_argv[0],
		    "mkisofs.exe","-version",NULL) != -1)
		    exit(0);
#endif
		fprintf (stderr,"\n- unable to execute %s: ",
				mkisofs_argv[0]),
		perror (NULL), exit (FATAL_START(errno));
	    }
	}

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

	    if (1 || dev_found == 'Z')
	    {	if ((in_image = strchr(in_device,'=')))
		{
#if defined(__unix) || defined(__unix__) 
		    uid_t euid=geteuid();
		    seteuid (getuid());	/* get real for parsing -[ZM] a=b */
#endif
		    while (in_image)
		    {	*in_image='\0';
			set_errno(0);
#ifdef _WIN32		/* have to treat d:=image.iso separately:-( */
			if (in_image[-1] == ':') break;
#endif
			if (access (in_device,F_OK)==0 || errno!=ENOENT)
			    break;
			*in_image='=',
			in_image=strchr(in_image+1,'=');
		    }

		    if (errno)
			fprintf (stderr,":-( \"%s\": unexpected errno:",
					in_device),
			perror (NULL), exit (FATAL_START(errno));

		    if (in_image)
		    {	in_image++;

			if (sscanf(in_image,"/dev/fd/%u",&imgfd) == 1)
			    imgfd = dup (imgfd); /* validate descriptor */
#ifdef OPEN_DIRECTIO
			else if ((imgfd = OPEN_DIRECTIO(in_image,O_RDONLY))<0)
#else
			else
#endif
			    imgfd = open64(in_image,O_RDONLY);

			if (imgfd < 0)
			    fprintf (stderr,":-( unable to open64(\"%s\","
					    "O_RDONLY): ",in_image),
			    perror (NULL), exit(FATAL_START(errno));

			if (!strcmp(in_image,"/dev/zero"))
			    zero_image=1;
		    }
#if defined(__unix) || defined(__unix__) 
		    seteuid (euid);	/* revert to saved [set-]uid */
#endif
		}
	    }

	    /*
	     * Sets up in_fd, out_fd, ioctl_handle and poor_man variable.
	     * This procedure is platform-specific. If the program
	     * has to be installed set-root-uid, then this procedure
	     * is the one to drop privileges [if appropriate].
	     */
	    out_device=setup_fds (in_device);

	    *(long *)saved_descriptors[0].type = 0;	/* redundant:-)	*/
	    if (mmc_profile&0x10000)		/* blank media	*/
		n=0, set_errno(EIO);
	    else
	    {	n=pread64 (in_fd,saved_descriptors,2048,VOLDESC_OFF*CD_BLOCK);
		if (n==0) set_errno(EIO);	/* end-of-file reached? */
	    }
	    if (n!=2048 && dev_found=='M')
		perror (":-( unable to pread64(2) primary volume descriptor"),
		fprintf (stderr,"    you most likely want to use -Z option.\n"), 
		exit (FATAL_START(errno));

	    if (dev_found=='M' ||
		((dev_found=='F' && !(mmc_profile&0x10000)) && memcmp(saved_descriptors[0].type,"\0\0\0\0\0\0",6)))
	    {	if (memcmp (saved_descriptors[0].type,"\1CD001",6))
	            fprintf (stderr,":-( %s doesn't look like isofs...\n",
		    		in_device), exit(FATAL_START(EMEDIUMTYPE));

		next_session=setup_C_parm(C_parm,saved_descriptors);

		if (imgfd>=0)
		{   if (zero_image)
		    { off64_t off=(atoi(C_parm)-16)*CD_BLOCK;

			dup2(in_fd,imgfd);	/* kludge! */
		    	if (lseek64 (imgfd,off,SEEK_SET) == (off64_t)-1)
			    fprintf (stderr,":-( %s: unable to lseek(%"LLD"): ",
					    in_device,off),
			    perror (NULL), exit(FATAL_START(errno));
		    } else if (alleged_next_session!=next_session)
			fprintf (stderr,"%s: -C argument is %s.\n",
					argv[0],alleged_next_session>=0?
					"insane":"undefined"),
			exit(FATAL_START(EINVAL));
		}
		else if (next_session > (0x200000-0x5000)) /* 4GB/2K-40MB/2K */
		    if (/*(mmc_profile&0xFFFF)<0x20 ||*/
			((mmc_profile&0xFFFF)<0x40 && !no_4gb_check))
			fprintf (stderr,":-( next session would cross 4GB "
					"boundary, aborting...\n"),
			exit (FATAL_START(ENOSPC));

		mkisofs_argv[mkisofs_argc++] = "-C";
		mkisofs_argv[mkisofs_argc++] = C_parm;
#ifdef CANNOT_PASS_DEV_FD_N_TO_MKISOFS
# ifdef PASS_STDIN_TO_MKISOFS
		M_parm = "-";
# else
		M_parm = get_M_parm (in_fd,in_device);
# endif
#else
# ifdef PASS_STDIN_TO_MKISOFS
		M_parm = "/dev/fd/0";
# else
		sprintf (M_parm,"/dev/fd/%d",in_fd);
# endif
#endif
		mkisofs_argv[mkisofs_argc++] = "-M";
		mkisofs_argv[mkisofs_argc++] = M_parm;
		len = 3 + strlen(C_parm) + 3 + strlen(M_parm);
		growisofs_argc += 4;
	    }
	    else
	    {	if (!memcmp (saved_descriptors[0].type,"\1CD001",6))
		    warn_for_isofs = 1;
		if (next_session<0) next_session = 0;
		continue;
	    }
	    *(long *)saved_descriptors[0].type = 0;
	}
	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 (FATAL_START(EINVAL));

    if (imgfd<0)
    {	if (mkisofs_argc==1 && dev_found!='F')
	    fprintf (stderr,"%s: no mkisofs options specified, "
			    "aborting...\n",argv[0]),
	    exit (FATAL_START(EINVAL));
    }
    else if ((mkisofs_argc-growisofs_argc)>1)
	fprintf (stderr,"%s: no mkisofs options are permitted with =, "
			"aborting...\n",argv[0]),
	exit (FATAL_START(EINVAL));

    mkisofs_argv[mkisofs_argc] = NULL;

    assert (next_session!=-1);
    assert (in_fd!=-1);
    assert (out_fd!=-1);

    /*
     * ensure highest_ecc_block is properly aligned
     * to ensure access atomicity
     */
    assert ((((size_t)&highest_ecc_block)&(sizeof(highest_ecc_block)-1))==0);

    /*
     * Ensure the_buffer_size is degree of 2
     */
    if (!dry_run)
    { unsigned int shift=0,sz=the_buffer_size;

	while (sz>>=1) shift++;
	if (shift<20) shift=20;	/* 1MB is minumum */
	sz = 1<<shift;
	if (sz < the_buffer_size) sz<<=1;
	the_buffer_size = sz;
    }
    else
	the_buffer_size = 2*DVD_BLOCK;

#if defined(__unix) || defined(__unix__)

#if defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES)
    if (the_buffer_size>1024*1024)
    { size_t phys_mem = (size_t)sysconf(_SC_PHYS_PAGES) *
			(size_t)sysconf(_SC_PAGESIZE);

	if (phys_mem)
	{   phys_mem /= 2;  /* normally PHYS is a bit smaller than
			     * sheer 2**n amount of RAM cells, so
			     * we commonly land on 1/4 RAM */
	    while (the_buffer_size > phys_mem) the_buffer_size /= 2;
	}
    }
#endif

    /* mmap buffer so that we can use it with /dev/raw/rawN */
#   if defined(MAP_ANONYMOUS) && !(defined(__sun) || defined(sun))
    the_buffer = mmap (NULL,the_buffer_size,PROT_READ|PROT_WRITE,
			MAP_SHARED|MAP_ANONYMOUS,-1,(off_t)0);
#   else
    { int fd;

	if ((fd=open ("/dev/zero",O_RDWR)) < 0)
	    perror (":-( unable to open \"/dev/zero\"?"),
	    exit(FATAL_START(errno));
	the_buffer = mmap (NULL,the_buffer_size,PROT_READ|PROT_WRITE,
			MAP_SHARED,fd,0);
	close (fd);
    }
#   endif
    if (the_buffer == MAP_FAILED)
	fprintf (stderr,":-( unable to anonymously mmap %d: ",the_buffer_size),
	perror (NULL), exit (FATAL_START(errno));

#elif defined(_WIN32)

    if (the_buffer_size>1024*1024)
    { MEMORYSTATUSEX mem;

	mem.dwLength = sizeof(mem);
	if (GlobalMemoryStatusEx (&mem))
	{   mem.ullAvailPhys /= 2;
	    while (the_buffer_size > mem.ullAvailPhys) the_buffer_size /= 2;
	}
    }
    the_buffer = VirtualAlloc (NULL,the_buffer_size,MEM_COMMIT,PAGE_READWRITE);
    if (the_buffer == NULL)
	fprintf (stderr,":-( unable to VirtualAlloc %d: ",the_buffer_size),
	perror (NULL), exit (FATAL_START(errno));

#endif

    /* never finalize disc at multi-sessioning DVD±R recordings...	*/
    { int profile = mmc_profile&0xFFFF;
	if (next_session>0 &&
	    (profile==0x2B || profile==0x1B ||
	     profile==0x11 || profile==0x15 || profile==0x16))
						dvd_compat=0;
	/* ... except when filling the media up:-)			*/
	if (next_session>0 && zero_image)	dvd_compat=1;
    }

    if (warn_for_isofs)
    { int fd=-1;
#if defined(__unix) || defined(__unix__)
	fd=open("/dev/tty",O_RDONLY);
#elif defined(_WIN32)
	fd=_open("CONIN$",O_RDONLY);
#endif
	if (fd>=0)
	{   if (isatty (fd)) warn_for_isofs |= 2;
	    close (fd);
	}
	else if (isatty (0)) warn_for_isofs |= 2;

	if (no_tty_check || (warn_for_isofs&2))
	{
	    fprintf (stderr,"WARNING: %s already carries isofs!\n",in_device);
		/* we cannot re-write a DVD-RW media in Sequential mode */
		if ((int)(mmc_profile&0xFFFF) == 0x14)
			fprintf (stderr,"FATAL: DVD-RW medium is in Sequential mode, you "
				            "need to blank it before writing again.\n"),
			exit(FATAL_START(EBUSY));
	    printf ("About to execute '");
	}
	else
	    fprintf (stderr,"FATAL: %s already carries isofs!\n",in_device),
	    exit(FATAL_START(EBUSY));
    }
    else
	printf ("Executing '");

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

    if ((warn_for_isofs&2) && !dry_run && !no_tty_check)
    {	fprintf (stderr,"Sleeping for 5 sec...\a"),	poll (NULL,0,1000);
	fprintf (stderr,"\b\b\b\b\b\b\b\b4 sec...\a"),	poll (NULL,0,1000);
	fprintf (stderr,"\b\b\b\b\b\b\b\b3 sec...\a"),	poll (NULL,0,1000);
	fprintf (stderr,"\b\b\b\b\b\b\b\b2 sec...\a"),	poll (NULL,0,1000);
	fprintf (stderr,"\b\b\b\b\b\b\b\b1 sec...\a"),	poll (NULL,0,1000);
	fprintf (stderr,"\b\b\b\b\b\b\b\b0 sec...\r");
    }

#if defined(__unix) || defined(__unix__)

#define CLOSEONEXEC(fd)	if (fd>=0) do { int f;	\
	if ((f=fcntl ((fd),F_GETFD)) < 0) f=0;	\
	fcntl ((fd),F_SETFD,f|FD_CLOEXEC);	} while (0)

    CLOSEONEXEC(in_fd);
    CLOSEONEXEC(out_fd);
#if !(defined(__APPLE__) && defined(__MACH__))
    CLOSEONEXEC(ioctl_fd);
#endif
#undef CLOSEONEXEC

#endif

    if (poor_man)
    {	out_device=in_device;
	if (!ioctl_device) ioctl_device=out_device;

	switch (mmc_profile&0xFFFF)
	{   case 0x11:	/* DVD-R Sequential		*/
	    case 0x12:	/* DVD-RAM			*/
	    case 0x13:	/* DVD-RW Restricted Overwrite	*/
	    case 0x14:	/* DVD-RW Sequential		*/
	    case 0x15:	/* DVD-R Dual Layer Sequential	*/
#if 0	    /* reserved for now... */
	    case 0x16:	/* DVD-R Dual Layer Jump	*/
#endif
	    case 0x1A:	/* DVD+RW			*/
	    case 0x1B:	/* DVD+R			*/
	    case 0x2A:	/* DVD+RW Double Layer		*/
	    case 0x2B:	/* DVD+R Double Layer		*/
	    case 0x41:	/* BD-R SRM			*/
#if 0	    /* I haven't seen RRM-capable unit... */
	    case 0x42:	/* BD-R RRM			*/
#endif
	    case 0x43:	/* BD-RE			*/
		break;
	    default:
		fprintf (stderr,":-( %s: mounted media doesn't appear to be "
	    			"[supported] DVD%sRW, DVD%sR or Blu-ray Disc\n",
				out_device,plusminus_locale(),plusminus_locale()),
		exit(FATAL_START(EMEDIUMTYPE));
		break;
	}
    }

    if (dev_found == 'F')	/* is it optimal spot? */
    {	off64_t capacity = 0;

	printf("next_session=%"LLD"\n", next_session*CD_BLOCK);
	if (ioctl_handle!=INVALID_HANDLE)
	    capacity = get_capacity (ioctl_handle);
	printf("capacity=%"LLD"\n", capacity);
	exit(0);
    }

    if (imgfd>=0)
    {	quiet--;
	if (builtin_dd (imgfd,out_fd,next_session*CD_BLOCK) < 0)
	{ int err = errno;
	    set_errno(err&FATAL_MASK); /* they might be passing FATAL_START */
	    perror (":-( write failed"), exit (err);
	}
	if (alleged_next_session <= 0)
	    set_errno(0), exit (0);
    }
    else
	pipe_mkisofs_up (mkisofs_argv,in_fd,out_fd,next_session*CD_BLOCK);

    /*
     * Recall that second 32KB written in this session can be found
     * in saved_descriptors[]! And now note that poor_man_rewritable()
     * fills the first 32KB of the_buffer with volume descriptor set
     * from the beginning of the volume, if appropriate that is! The
     * latter is a workaround hook for DVD-RW Restricted Overwrite
     * interfering with DVD+RW kernel patch... Is this mode ugly or
     * is it ugly? G-r-r-r...
     */
    if (next_session!=0 &&
	(!poor_man || poor_man_rewritable (ioctl_handle,the_buffer)))
    { struct iso_primary_descriptor *descr =
				(struct iso_primary_descriptor *)the_buffer;

	if (memcmp (saved_descriptors[0].type,"\1CD001",6))
	    fprintf (stderr,":-( %s:%d doesn't look like isofs!\n",
				out_device,next_session),
	    exit (set_errno(EMEDIUMTYPE));

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

	new_size = from_733(saved_descriptors[0].volume_space_size) +
		   next_session;

	if (!poor_man && (set_errno(0), pread64 (out_fd,descr,DVD_BLOCK,
				  	    VOLDESC_OFF*CD_BLOCK)) != DVD_BLOCK)
	    set_errno(errno?errno:EIO),
	    perror (":-( unable to pread64(2) volume descriptors set"),
	    exit (errno);

	memcpy (descr+0,&saved_descriptors[0],sizeof(*descr));
	to_733(descr[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 (saved_descriptors[i].type[0] == (unsigned char)255)	break;
	    if (memcmp (saved_descriptors[i].id,"CD001",5))		break;

	    if (saved_descriptors[i].type[0] != descr[i].type[0])
	    {	fprintf (stderr,":-? volume descriptor mismatch, did you "
				"use same mkisofs options?\n");
		break;
	    }
	    memcpy (descr+i,&saved_descriptors[i],sizeof(*descr));
	    to_733(descr[i].volume_space_size,new_size);
	}

	if ((*pwrite64_method) (out_fd,descr,DVD_BLOCK,
		VOLDESC_OFF*CD_BLOCK) != DVD_BLOCK)
            perror (":-( unable to pwrite64(2) volume descriptors set"),
	    exit (errno);
    }

    set_errno(0);
    exit (0);
}
