
/*
   cc -g -c \
      -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE=1 -D_LARGEFILE64_SOURCE \
      burn_wrap.c
*/
/* libburn wrappers for libisoburn 

   Copyright 2007 - 2010  Thomas Schmitt, <scdbackup@gmx.net> 
   Provided under GPL version 2 or later.
*/

#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif

/* <<< A70929 : hardcoded CD-RW with fabricated -msinfo 
#define Hardcoded_cd_rW 1
#define Hardcoded_cd_rw_c1     12999
#define Hardcoded_cd_rw_nwA   152660 
*/

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <regex.h>

#ifndef Xorriso_standalonE

#include <libburn/libburn.h>
#include <libisofs/libisofs.h>
#ifdef Xorriso_with_libjtE
#include <libjte/libjte.h>
#endif

#else /* ! Xorriso_standalonE */

#include "../libisofs/libisofs.h"
#include "../libburn/libburn.h"
#ifdef Xorriso_with_libjtE
#include "../libjte/libjte.h"
#endif

#endif /* Xorriso_standalonE */


#include "libisoburn.h"
#include "isoburn.h"


/* The global list of isoburn objects. Usually there is only one. */
extern struct isoburn *isoburn_list_start; /* in isoburn.c */

/* Default values for application provided msgs_submit methods.
   To be attached to newly aquired drives.
   Storage location is isoburn.c
*/
extern int (*libisoburn_default_msgs_submit)
    (void *handle, int error_code, char msg_text[],
                 int os_errno, char severity[], int flag);
extern void *libisoburn_default_msgs_submit_handle;
extern int libisoburn_default_msgs_submit_flag;


static int isoburn_emulate_toc(struct burn_drive *d, int flag);


int isoburn_initialize(char msg[1024], int flag)
{
 int major, minor, micro, bad_match= 0, no_iso_init= 0;


/* First the ugly compile time checks for header version compatibility.
   If everthing matches, then they produce no C code. In case of mismatch,
   intentionally faulty C code will be inserted.
*/

#ifdef iso_lib_header_version_major
/* The minimum requirement of libisoburn towards the libisofs header
   at compile time is defined in libisoburn/libisoburn.h :
     isoburn_libisofs_req_major
     isoburn_libisofs_req_minor
     isoburn_libisofs_req_micro
   It gets compared against the version macros in libisofs/libisofs.h :
     iso_lib_header_version_major
     iso_lib_header_version_minor
     iso_lib_header_version_micro
   If the header is too old then the following code shall cause failure of
   libisoburn compilation rather than to allow production of a program with
   unpredictable bugs or memory corruption.
   The compiler messages supposed to appear in this case are:
      error: 'LIBISOFS_MISCONFIGURATION' undeclared (first use in this function)
      error: 'INTENTIONAL_ABORT_OF_COMPILATION__HEADERFILE_libisofs_dot_h_TOO_OLD__SEE_libisoburn_dot_h_AND_burn_wrap_dot_h' undeclared (first use in this function)
      error: 'LIBISOFS_MISCONFIGURATION_' undeclared (first use in this function)
*/
/* The indendation is an advise of man gcc to help old compilers ignoring */
 #if isoburn_libisofs_req_major > iso_lib_header_version_major
 #define Isoburn_libisofs_dot_h_too_olD 1
 #endif
 #if isoburn_libisofs_req_major == iso_lib_header_version_major && isoburn_libisofs_req_minor > iso_lib_header_version_minor
 #define Isoburn_libisofs_dot_h_too_olD 1
 #endif
 #if isoburn_libisofs_req_minor == iso_lib_header_version_minor && isoburn_libisofs_req_micro > iso_lib_header_version_micro
 #define Isoburn_libisofs_dot_h_too_olD 1
 #endif

#ifdef Isoburn_libisofs_dot_h_too_olD
LIBISOFS_MISCONFIGURATION = 0;
INTENTIONAL_ABORT_OF_COMPILATION__HEADERFILE_libisofs_dot_h_TOO_OLD__SEE_libisoburn_dot_h_AND_burn_wrap_dot_c = 0;
LIBISOFS_MISCONFIGURATION_ = 0;
#endif

#endif /* iso_lib_header_version_major */

/* The minimum requirement of libisoburn towards the libburn header
   at compile time is defined in libisoburn/libisoburn.h :
     isoburn_libburn_req_major
     isoburn_libburn_req_minor
     isoburn_libburn_req_micro
   It gets compared against the version macros in libburn/libburn.h :
     burn_header_version_major
     burn_header_version_minor
     burn_header_version_micro
   If the header is too old then the following code shall cause failure of
   cdrskin compilation rather than to allow production of a program with
   unpredictable bugs or memory corruption.
   The compiler messages supposed to appear in this case are:
      error: 'LIBBURN_MISCONFIGURATION' undeclared (first use in this function)
      error: 'INTENTIONAL_ABORT_OF_COMPILATION__HEADERFILE_libburn_dot_h_TOO_OLD__SEE_libisoburn_dot_h_and_burn_wrap_dot_h' undeclared (first use in this function)
      error: 'LIBBURN_MISCONFIGURATION_' undeclared (first use in this function)
*/

/* The indendation is an advise of man gcc to help old compilers ignoring */
 #if isoburn_libburn_req_major > burn_header_version_major
 #define Isoburn_libburn_dot_h_too_olD 1
 #endif
 #if isoburn_libburn_req_major == burn_header_version_major && isoburn_libburn_req_minor > burn_header_version_minor
 #define Isoburn_libburn_dot_h_too_olD 1
 #endif
 #if isoburn_libburn_req_minor == burn_header_version_minor && isoburn_libburn_req_micro > burn_header_version_micro
 #define Isoburn_libburn_dot_h_too_olD 1
 #endif

#ifdef Isoburn_libburn_dot_h_too_olD
LIBBURN_MISCONFIGURATION = 0;
INTENTIONAL_ABORT_OF_COMPILATION__HEADERFILE_libburn_dot_h_TOO_OLD__SEE_libisoburn_dot_h_and_burn_wrap_dot_h = 0;
LIBBURN_MISCONFIGURATION_ = 0;
#endif


#ifdef Xorriso_with_libjtE

/* The minimum requirement of libisoburn towards the libjte header
   at compile time is the same as the one of a usable libisfs itowards libjte.
   So the requirement is defined in libisofs/libisofs.h :
     iso_libjte_req_major , iso_libjte_req_minor , iso_libjte_req_micro
*/
 /* The indendation is an advise of man gcc to help old compilers ignoring */
 #if iso_libjte_req_major > LIBJTE_VERSION_MAJOR
 #define Libisofs_libjte_dot_h_too_olD 1
 #endif
 #if iso_libjte_req_major == LIBJTE_VERSION_MAJOR && iso_libjte_req_minor > LIBJTE_VERSION_MINOR
 #define Libisofs_libjte_dot_h_too_olD 1
 #endif
 #if iso_libjte_req_minor == LIBJTE_VERSION_MINOR && iso_libjte_req_micro > LIBJTE_VERSION_MICRO
 #define Libisofs_libjte_dot_h_too_olD 1
 #endif

#ifdef Libisofs_libjte_dot_h_too_olD
LIBJTE_MISCONFIGURATION = 0;
INTENTIONAL_ABORT_OF_COMPILATION__HEADERFILE_libjte_dot_h_TOO_OLD__SEE_libisofs_dot_h_AND_burn_wrap.c_c = 0;
LIBJTE_MISCONFIGURATION_ = 0;
#endif

#endif /* Xorriso_with_libjtE */


/* End of ugly compile time tests (scroll up for explanation) */


 msg[0]= 0;

#ifdef Xorriso_with_libjtE

 libjte__version(&major, &minor, &micro);
 sprintf(msg + strlen(msg), "libjte-%d.%d.%d ", major, minor, micro);
 if (libjte__is_compatible(LIBJTE_VERSION_MAJOR, LIBJTE_VERSION_MINOR,
                                LIBJTE_VERSION_MICRO, 0)) {
   sprintf(msg+strlen(msg), "ok, ");
 } else {
   sprintf(msg + strlen(msg),
           "- TOO OLD -, need at least libjte-%d.%d.%d ,\n",
           LIBJTE_VERSION_MAJOR, LIBJTE_VERSION_MINOR,
           LIBJTE_VERSION_MICRO);
   bad_match= 1;
   no_iso_init= 1; /* iso_init() will fail anyway */
 }

#endif /* Xorriso_with_libjtE */

 if(!no_iso_init) {
   if(iso_init()<0) {
     sprintf(msg+strlen(msg), "Cannot initialize libisofs\n");
     return(0);
   }
 }
 iso_lib_version(&major, &minor, &micro);
 sprintf(msg+strlen(msg), "libisofs-%d.%d.%d ", major, minor, micro);
#ifdef iso_lib_header_version_major
 if(iso_lib_is_compatible(iso_lib_header_version_major,
                          iso_lib_header_version_minor,
                          iso_lib_header_version_micro)) {
   sprintf(msg+strlen(msg), "ok, ");
 } else {
   sprintf(msg+strlen(msg),"- TOO OLD -, need at least libisofs-%d.%d.%d ,\n",
           iso_lib_header_version_major, iso_lib_header_version_minor,
           iso_lib_header_version_micro);
   bad_match= 1;
 }
#else
 if(iso_lib_is_compatible(isoburn_libisofs_req_major,
                          isoburn_libisofs_req_minor,
                          isoburn_libisofs_req_micro)) {
   sprintf(msg+strlen(msg), "suspicious, ");
 } else {
   sprintf(msg+strlen(msg),"- TOO OLD -, need at least libisofs-%d.%d.%d ,\n",
           isoburn_libisofs_req_major, isoburn_libisofs_req_minor,
           isoburn_libisofs_req_micro);
   bad_match= 1;
 }
#endif /* ! iso_lib_header_version_major */

 if(!burn_initialize()) {
   sprintf(msg+strlen(msg), "Cannot initialize libburn\n");
   return(0);
 }
 burn_version(&major, &minor, &micro);
 sprintf(msg+strlen(msg), "libburn-%d.%d.%d ", major, minor, micro);
 if(major > burn_header_version_major
    || (major == burn_header_version_major
        && (minor > burn_header_version_minor
            || (minor == burn_header_version_minor
                && micro >= burn_header_version_micro)))) {
   sprintf(msg+strlen(msg), "ok, ");
 } else {
   sprintf(msg+strlen(msg), "- TOO OLD -, need at least libburn-%d.%d.%d ,\n",
           burn_header_version_major, burn_header_version_minor,
           burn_header_version_micro);
   bad_match= 1;
 }

 isoburn_version(&major, &minor, &micro);
 sprintf(msg+strlen(msg), "for libisoburn-%d.%d.%d", major, minor, micro);
 if(bad_match)
   return(0);

 isoburn_destroy_all(&isoburn_list_start, 0); /* isoburn_list_start= NULL */
 return(1);
}


/* API @since 0.1.0 */
int isoburn_libisofs_req(int *major, int *minor, int *micro)
{
 *major= iso_lib_header_version_major;
 *minor= iso_lib_header_version_minor;
 *micro= iso_lib_header_version_micro;
 return(1);
}


/* API @since 0.1.0 */
int isoburn_libburn_req(int *major, int *minor, int *micro)
{
 *major= burn_header_version_major;
 *minor= burn_header_version_minor;
 *micro= burn_header_version_micro;
 return(1);
}


/* API @since 0.6.4 */
int isoburn_libjte_req(int *major, int *minor, int *micro)
{
#ifdef Xorriso_with_libjtE
 *major= LIBJTE_VERSION_MAJOR;
 *minor= LIBJTE_VERSION_MINOR;
 *micro= LIBJTE_VERSION_MICRO;
#else
 *major= *minor= *micro= 0;
#endif /* ! Xorriso_with_libjtE */
 return(1);
}


int isoburn_set_msgs_submit(int (*msgs_submit)(void *handle, int error_code,
                                               char msg_text[], int os_errno,
                                               char severity[], int flag),
                               void *submit_handle, int submit_flag, int flag)
{
 libisoburn_default_msgs_submit= msgs_submit;
 libisoburn_default_msgs_submit_handle= submit_handle;
 libisoburn_default_msgs_submit_flag= submit_flag;
 return(1);
}


int isoburn_is_intermediate_dvd_rw(struct burn_drive *d, int flag)
{
 int profile, ret= 0, format_status, num_formats;
 char profile_name[80];
 enum burn_disc_status s;
 off_t format_size= -1;
 unsigned bl_sas;

 s= isoburn_disc_get_status(d);
 ret= burn_disc_get_profile(d, &profile, profile_name);
 if(ret>0 && profile==0x13)
   ret= burn_disc_get_formats(d, &format_status, &format_size,
                              &bl_sas, &num_formats);
 if(ret>0 && profile==0x13 && s==BURN_DISC_BLANK &&
    format_status==BURN_FORMAT_IS_UNKNOWN)
   return(1);
 return(0);
}


/** Examines the media and sets appropriate emulation if needed.
    @param flag bit0= pretent blank on overwriteable media
                bit3= if the drive reports a -ROM profile then try to read 
                      table of content by scanning for ISO image headers.
                bit4= do not emulate TOC on overwriteable media
                bit5= ignore ACL from external filesystems
                bit6= ignore POSIX Extended Attributes from external filesystems
                bit7= pretend -ROM and scan for table of content
*/ 
static int isoburn_welcome_media(struct isoburn **o, struct burn_drive *d,
                                 int flag)
{
 int ret, lba, nwa, profile, readonly= 0;
 struct burn_multi_caps *caps= NULL;
 struct isoburn_toc_entry *t;
 char profile_name[80];
 struct isoburn_toc_disc *disc= NULL;
 struct isoburn_toc_session **sessions;
 struct isoburn_toc_track **tracks;
 int num_sessions= 0, num_tracks= 0, track_count= 0, session_no= 0;
 char msg[80];
 enum burn_disc_status s;

 s= burn_disc_get_status(d);
 profile_name[0]= 0;
 ret= burn_disc_get_profile(d, &profile, profile_name);
 if(ret<=0)
   profile= 0x00;
 ret= burn_disc_get_multi_caps(d, BURN_WRITE_NONE, &caps, 0);
 if(ret<0) /* == 0 is read-only media, but it is too early to reject it here */
   goto ex;
 if(ret==0 || (flag & 128))
   readonly= 1;
 if(flag & 128)
   flag = (flag & ~ 16) | 8;
   
 ret= isoburn_new(o, 0);
 if(ret<=0)
   goto ex;
 (*o)->drive= d;
 (*o)->msgs_submit= libisoburn_default_msgs_submit;
 (*o)->msgs_submit_handle= libisoburn_default_msgs_submit_handle;
 (*o)->msgs_submit_flag= libisoburn_default_msgs_submit_flag;
 iso_image_set_ignore_aclea((*o)->image, (flag >> 5 ) & 3);

#ifdef Hardcoded_cd_rW
 /* <<< A70929 : hardcoded CD-RW with fabricated -msinfo */
 caps->start_adr= 0;
 (*o)->fabricated_disc_status= BURN_DISC_APPENDABLE;
#endif

 if(caps->start_adr)
   (*o)->emulation_mode= 1;
 if(caps->start_adr && !readonly) {       /* set emulation to overwriteable */
   ret= isoburn_is_intermediate_dvd_rw(d, 0);
   if(ret>0) {
     (*o)->min_start_byte= 0;
     (*o)->nwa= 0;
     (*o)->zero_nwa= 0;
   }
   if(flag&1) {
     (*o)->nwa= (*o)->zero_nwa;
     (*o)->fabricated_disc_status= BURN_DISC_BLANK;
   } else {
     ret= isoburn_start_emulation(*o, 0);
     if(ret<=0) {
       (*o)->emulation_mode= -1;
       goto ex;
     }
     /* try to read emulated toc */
     ret= isoburn_emulate_toc(d, flag & 16);
     if(ret<0) {
       (*o)->emulation_mode= -1;
       goto ex;
     }
   }
 } else {

    /* >>> recognize unsuitable media (but allow read-only media) */;

   if(readonly && s != BURN_DISC_EMPTY) {
     (*o)->fabricated_disc_status= BURN_DISC_FULL;
     /* This might be overwriteable media in a -ROM drive.
        Pitfall:
        Multi-session media which bear a xorriso image for overwriteables
        in their first session would get a TOC of that first image rather
        than of the media.
        It is not possible to distinguish a BD-RE from a single session
        BD-R with an image for overwriteables. But as soon as the media
        bears 2 logical tracks it cannot be overwriteable.
        So count the number of tracks first.
     */
     disc= isoburn_toc_drive_get_disc(d);
     if(disc != NULL) {
       sessions= isoburn_toc_disc_get_sessions(disc, &num_sessions);
       for(session_no= 0; session_no < num_sessions; session_no++) {
         tracks= isoburn_toc_session_get_tracks(sessions[session_no],
                                                &num_tracks);
         track_count+= num_tracks;
       }
       isoburn_toc_disc_free(disc);
     }

     sprintf(msg, "ROM media has libburn track count = %d", track_count);
     isoburn_msgs_submit(*o, 0x00060000, msg, 0, "DEBUG", 0);

     if((flag & 16) || track_count >= 2) {
       ret= 0; /* toc emulation off, or not overwriteable */
     } else {
       ret= isoburn_emulate_toc(d, 1);
       if(ret<0)
         goto ex;
       else if(ret > 0)
         (*o)->emulation_mode= 1;
     }
     if(ret == 0 && (profile != 0x08 || (flag & 128)) && (flag & 8)) {
       /* This might also be multi-session media which do not
          get shown with a decent TOC.
          CD-R TOC (profile 0x08) can be trusted. Others not.
          Do a scan search of ISO headers.
       */
       ret= isoburn_emulate_toc(d, 1|2);
       if(ret<0)
         goto ex;
       if(ret>0) { /* point msc1 to last session */
         if((*o)->toc!=NULL) {
           for(t= (*o)->toc; t->next!=NULL; t= t->next);
            (*o)->fabricated_msc1= t->start_lba;
         }
       }
     }
   }
#ifdef Hardcoded_cd_rW
   (*o)->nwa= Hardcoded_cd_rw_nwA;
#else
   ret= burn_disc_track_lba_nwa(d, NULL, 0, &lba, &nwa);
   if(ret>0)
     (*o)->nwa= nwa;
#endif

 }

 ret= 1;
ex:
 if(caps!=NULL)
   burn_disc_free_multi_caps(&caps);
 return(ret);
}


/**
    @param flag bit0= load
                bit1= regard overwriteable media as blank
                bit2= if the drive is a regular disk file: truncate it to
                      the write start address
                bit3= if the drive reports a -ROM profile then try to read 
                      table of content by scanning for ISO image headers.
                      (depending on media type and drive state this might 
                       help or it might make the resulting toc even worse)
                bit4= do not emulate TOC on overwriteable media
                bit5= ignore ACL from external filesystems
                bit6= ignore POSIX Extended Attributes from external filesystems
                bit7= pretend -ROM profile and scan for table of content
*/
int isoburn_drive_aquire(struct burn_drive_info *drive_infos[],
                         char *adr, int flag)
{
 int ret, drive_grabbed= 0;
 struct isoburn *o= NULL;

#ifndef NIX

/* <<< should be obsolete by new drive addressing of libburn-0.5.2 */
/* >>> but helps with kernel 2.4 to use /dev/sr */

 int conv_ret;
 char libburn_drive_adr[BURN_DRIVE_ADR_LEN];

 conv_ret= burn_drive_convert_fs_adr(adr, libburn_drive_adr);
 if(conv_ret<=0)
   strcpy(libburn_drive_adr, adr);
 ret= burn_drive_scan_and_grab(drive_infos, libburn_drive_adr, flag&1);

#else

 ret= burn_drive_scan_and_grab(drive_infos, adr, flag & 1);

#endif /* ! NIX */

 if(ret<=0)
   goto ex;
 drive_grabbed= 1;
 ret= isoburn_welcome_media(&o, (*drive_infos)[0].drive,
                            (flag & (8 | 16 | 32 | 64 | 128)) | !!(flag&2));
 if(ret<=0)
   goto ex;

 if(flag&4) {
   ret= isoburn_find_emulator(&o, (*drive_infos)[0].drive, 0);
   if(ret>0 && o!=NULL)
     o->truncate= 1;
 }

 ret= 1;
ex:
 if(ret<=0) {
   if(drive_grabbed)
     burn_drive_release((*drive_infos)[0].drive, 0);
   isoburn_destroy(&o, 0);
 }
 return(ret);
}


int isoburn_drive_scan_and_grab(struct burn_drive_info *drive_infos[],
                                char *adr, int load)
{
 int ret;

 ret= isoburn_drive_aquire(drive_infos, adr, !!load);
 return(ret);
}


int isoburn_drive_grab(struct burn_drive *drive, int load)
{
 int ret;
 struct isoburn *o= NULL;

 ret= burn_drive_grab(drive, load);
 if(ret<=0)
   goto ex;
 ret= isoburn_welcome_media(&o, drive, 0);
 if(ret<=0)
   goto ex;
 
 ret= 1;
ex:
 if(ret<=0)
   isoburn_destroy(&o,0);
 return(ret);
}


/** Retrieve media emulation and eventual isoburn emulator of drive.
    @return -1 unsuitable media, 0 generic media, 1 emulated media.
*/
int isoburn_find_emulator(struct isoburn **pt,
                                 struct burn_drive *drive, int flag)
{
 int ret;

 ret= isoburn_find_by_drive(pt, drive, 0);
 if(ret<=0)
   return(0);
 if((*pt)->emulation_mode==-1) {
   isoburn_msgs_submit(*pt, 0x00060000,
                    "Unsuitable drive and media state", 0, "FAILURE", 0);
   return(-1);
 }
 if((*pt)->emulation_mode==0)
   return(0);
 return(1);
} 


enum burn_disc_status isoburn_disc_get_status(struct burn_drive *drive)
{
 int ret;
 struct isoburn *o;

 ret= isoburn_find_emulator(&o, drive, 0);
 if(ret<0)
   return(BURN_DISC_UNSUITABLE);
 if(o!=NULL)
   if(o->fabricated_disc_status!=BURN_DISC_UNREADY)
     return(o->fabricated_disc_status);
 if(ret==0)
   return(burn_disc_get_status(drive));

 /* emulated status */
 if(o->emulation_mode==-1)
   return(BURN_DISC_UNSUITABLE);
 if(o->nwa>o->zero_nwa)
   return(BURN_DISC_APPENDABLE);
 return(BURN_DISC_BLANK);
}


int isoburn_disc_erasable(struct burn_drive *d)
{
 int ret;
 struct isoburn *o;

 ret= isoburn_find_emulator(&o, d, 0);
 if(ret>0)
   if(o->emulation_mode==1)
     return(1);
 return burn_disc_erasable(d);
}


void isoburn_disc_erase(struct burn_drive *drive, int fast)
{
 int ret, do_pseudo_blank= 0;
 struct isoburn *o;
 enum burn_disc_status s;
 char zero_buffer[Libisoburn_target_head_sizE];
 struct burn_multi_caps *caps= NULL;

 ret= isoburn_find_emulator(&o, drive, 0);
 if(ret>0) {
   if(o->emulation_mode==-1) {
     /* To cause a negative reply with burn_drive_wrote_well() */
     burn_drive_cancel(drive);
     goto ex;
   }

   if(o->emulation_mode > 0) { /* might be readonly with emulated sessions */
     ret= burn_disc_get_multi_caps(drive, BURN_WRITE_NONE, &caps, 0);
     if(ret > 0 && caps->start_adr)
       do_pseudo_blank= 1;
   }
   if(do_pseudo_blank) {
     s= isoburn_disc_get_status(drive);
     if(s==BURN_DISC_FULL) { /* unknown data format in first 64 kB */
       memset(zero_buffer, 0, Libisoburn_target_head_sizE);
       ret= burn_random_access_write(drive, (off_t) 0, zero_buffer,
                                     (off_t) Libisoburn_target_head_sizE, 1);
     } else {
       ret= isoburn_invalidate_iso(o, 0);
     }
     if(ret<=0)
       burn_drive_cancel(drive); /* mark run as failure */
     goto ex;
   }
 }
 burn_disc_erase(drive, fast);
ex:;
 if(caps!=NULL)
   burn_disc_free_multi_caps(&caps);
}


off_t isoburn_disc_available_space(struct burn_drive *d,
                                   struct burn_write_opts *opts)
{
 int ret;
 struct isoburn *o;
 struct burn_write_opts *eff_opts= NULL, *local_opts= NULL;
 enum burn_disc_status s;
 off_t avail;

 eff_opts= opts;
 ret= isoburn_find_emulator(&o, d, 0);
 if(ret>0 && o!=NULL)
   if(o->emulation_mode!=0) {
     s= isoburn_disc_get_status(d);
     if(s==BURN_DISC_FULL) /* unknown data format in first 64 kB */
       return((off_t) 0);
     local_opts= burn_write_opts_new(d);
     eff_opts= local_opts;
     burn_write_opts_set_start_byte(eff_opts, ((off_t) o->nwa) * (off_t) 2048);
   }
 avail= burn_disc_available_space(d, eff_opts);
 if(local_opts!=NULL)
   burn_write_opts_free(local_opts);
 local_opts= NULL;
 return(avail);
}


int isoburn_disc_get_msc1(struct burn_drive *d, int *start_lba)
{
 int ret;
 struct isoburn *o;

#ifdef Hardcoded_cd_rW
 /* <<< A70929 : hardcoded CD-RW with fabricated -msinfo */
 *start_lba= Hardcoded_cd_rw_c1;
 return(1);
#endif

 if(isoburn_disc_get_status(d)!=BURN_DISC_APPENDABLE &&
    isoburn_disc_get_status(d)!=BURN_DISC_FULL) {
   isoburn_msgs_submit(NULL, 0x00060000,
                       "Media contains no recognizable data", 0, "SORRY", 0);
   return(0);
 }
 ret= isoburn_find_emulator(&o, d, 0);
 if(ret<0)
   return(0);
 if(o->fabricated_msc1>=0) {
   *start_lba= o->fabricated_msc1;
   return(1);
 }
 if(ret>0) if(o->emulation_mode>0) {
   *start_lba= 0;
   return(1);
 }
 return(burn_disc_get_msc1(d, start_lba));
}


int isoburn_disc_track_lba_nwa(struct burn_drive *d,
                               struct burn_write_opts *opts,
                               int trackno, int *lba, int *nwa)
{
 int ret;
 struct isoburn *o;

#ifdef Hardcoded_cd_rW
 /* <<< A70929 : hardcoded CD-RW with fabricated -msinfo */
 *lba= Hardcoded_cd_rw_c1;
 *nwa= Hardcoded_cd_rw_nwA;
 return(1);
#endif

 *nwa= *lba= 0;
 ret= isoburn_find_emulator(&o, d, 0);
 if(ret<0)
   return(0);
 if(ret>0) if(o->emulation_mode>0) {
   *lba= 0;
   *nwa= o->nwa;
   return(1);
 }
 if(burn_drive_get_drive_role(d) != 1)
   return(1);
 return(burn_disc_track_lba_nwa(d, opts, trackno, lba, nwa));
}


int isoburn_get_msc2(struct isoburn *o,
                     struct burn_write_opts *opts, int *msc2, int flag)
{
 int ret, lba, nwa;

 if(o->fabricated_msc2>=0)
   *msc2= o->fabricated_msc2;
 else {
   ret= isoburn_disc_track_lba_nwa(o->drive, opts, 0, &lba, &nwa);
   if(ret<=0)
     return(ret);
   *msc2= nwa;
 }
 return(1);
}


void isoburn_disc_write(struct burn_write_opts *opts, struct burn_disc *disc)
{
 int ret;
 off_t nwa= 0;
 struct isoburn *o;
 struct burn_drive *drive;
 char reasons[BURN_REASONS_LEN],msg[160+BURN_REASONS_LEN];
 char adr[BURN_DRIVE_ADR_LEN];
 enum burn_write_types write_type;
 struct stat stbuf;

 drive= burn_write_opts_get_drive(opts);
 ret= isoburn_find_emulator(&o, drive, 0);
 if(ret<0)
   return;
 if(o!=NULL) {
   o->wrote_well= -1;
   if(o->emulation_mode!=0) {
     burn_write_opts_set_multi(opts, 0);
     if(o->emulation_mode>0 && o->nwa >= 0) {
       nwa= o->nwa;

       /* This caters for unwritten formatted DVD-RW. They need to be written
          sequentially on the first use. Only written areas are random access.
          If the first session is not written to LBA 0, then re-opening of
          formatting and padding is needed. 
          This can be done. But when the track gets closed after padding,
          this lasts a long time. There is a high risk that an app will not
          poll the message queue while waiting for  isoburn_disc_write()  to
          return. The pacifier loop usually happens only afterwards.
          So automatic formatting might cause a nervous clueless user.
       */
       ret= isoburn_is_intermediate_dvd_rw(drive, 0);
       if(ret>0 && nwa>0 && nwa <= o->zero_nwa) {
         /* actually this should not happen since such media get recognized
            by isoburn_welcome_media and o->zero_nwa gets set to 0
         */
         sprintf(msg,
        "DVD-RW insufficiently formatted. (Intermediate State, size unknown)");
         isoburn_msgs_submit(o, 0x00060000, msg, 0, "FAILURE", 0);
         sprintf(msg,
                "It might help to first deformat it and then format it again");
         isoburn_msgs_submit(o, 0x00060000, msg, 0, "HINT", 0);
         burn_drive_cancel(drive); /* mark run as failure */
         return;
       }
       /* end of DVD-RW oriented check */

       burn_write_opts_set_start_byte(opts, nwa * (off_t) 2048);
     }
   }
 }

 write_type= burn_write_opts_auto_write_type(opts, disc, reasons, 0);
 if (write_type == BURN_WRITE_NONE) {
    sprintf(msg, "Failed to find a suitable write mode:\n%s", reasons);
    isoburn_msgs_submit(o, 0x00060000, msg, 0, "FAILURE", 0);
    if(o!=NULL)
      o->wrote_well= 0;
    /* To cause a negative reply with burn_drive_wrote_well() */
    burn_drive_cancel(drive);
    return;
 }

 sprintf(reasons, "%d", (int) write_type);
 sprintf(msg, "Write_type = %s\n",
              (write_type == BURN_WRITE_SAO ? "SAO" :
              (write_type == BURN_WRITE_TAO ? "TAO" : reasons)));
 isoburn_msgs_submit(o, 0x00060000, msg, 0, "DEBUG", 0);

#ifdef Hardcoded_cd_rW
 /* <<< A70929 : hardcoded CD-RW with fabricated -msinfo */
 fprintf(stderr, "Setting write address to LBA %d\n", Hardcoded_cd_rw_nwA);
 burn_write_opts_set_start_byte(opts,
				 ((off_t) Hardcoded_cd_rw_nwA) * (off_t) 2048);
#endif

 if(o->truncate) {
   ret= burn_drive_get_drive_role(drive);
   if(ret==2) {
     ret= burn_drive_d_get_adr(drive, adr);
     if(ret>0) {
       ret= lstat(adr, &stbuf);
       if(ret!=-1)
         if(S_ISREG(stbuf.st_mode))
           ret= truncate(adr, nwa * (off_t) 2048);
           /* (result of truncate intentionally ignored) */
     }
   }
 }

 burn_disc_write(opts, disc);
}


void isoburn_drive_release(struct burn_drive *drive, int eject)
{
 int ret;
 struct isoburn *o;

 ret= isoburn_find_emulator(&o, drive, 0);
 if(ret<0)
   return;
 if(o!=NULL) {
   isoburn_destroy(&o, 0);
 }
 burn_drive_release(drive, eject);
}


void isoburn_finish(void)
{
 isoburn_destroy_all(&isoburn_list_start, 0);
 burn_finish();
 iso_finish();
}


int isoburn_needs_emulation(struct burn_drive *drive)
{
 int ret;
 struct isoburn *o;
 enum burn_disc_status s;

 s= isoburn_disc_get_status(drive);
 if(s!=BURN_DISC_BLANK && s!=BURN_DISC_APPENDABLE)
   return(-1);
 ret= isoburn_find_emulator(&o, drive, 0);
 if(ret<0)
   return(-1);
 if(ret>0)
   if(o->emulation_mode>0)
     return(1);
 return(0);  
}


int isoburn_set_start_byte(struct isoburn *o, off_t value, int flag)
{
 int ret;
 struct burn_drive *drive = o->drive;
 struct burn_multi_caps *caps= NULL;
 
 ret= burn_disc_get_multi_caps(drive, BURN_WRITE_NONE, &caps, 0);
 if(ret<=0)
   goto ex;
 if(!caps->start_adr) {
   isoburn_msgs_submit(o, 0x00060000,
                       "Cannot set start byte address with this type of media",
                       0, "FAILURE", 0);
   {ret= 0; goto ex;}
 }
 o->min_start_byte= value;
 if(value % caps->start_alignment)
   value+= caps->start_alignment - (value % caps->start_alignment);
 o->nwa= value/2048;
 /* If suitable for media alignment, round up to Libisoburn_nwa_alignemenT */
 if((o->nwa % Libisoburn_nwa_alignemenT) &&
     ((Libisoburn_nwa_alignemenT*2048) % caps->start_alignment)==0 )
   o->nwa+= Libisoburn_nwa_alignemenT - (o->nwa % Libisoburn_nwa_alignemenT);
 ret= 1;
ex:
 if(caps!=NULL)
   burn_disc_free_multi_caps(&caps);
 return(ret);
}


int isoburn_get_min_start_byte(struct burn_drive *d, off_t *start_byte,
                               int flag)
{
 int ret;
 struct isoburn *o;

 ret= isoburn_find_emulator(&o, d, 0);
 if(ret<0)
   return(-1);
 if(ret==0) 
   return(0);
 *start_byte= o->min_start_byte;
 if(o->min_start_byte<=0)
   return(0);
 return(1);
}


int isoburn_drive_wrote_well(struct burn_drive *d)
{
 int ret;
 struct isoburn *o;
 
 ret= isoburn_find_emulator(&o, d, 0);
 if(ret<0)
   return(-1);
 if(o!=NULL)
   if(o->wrote_well>=0)
     return(o->wrote_well);
 ret= burn_drive_wrote_well(d);
 return ret;
}


int isoburn_get_fifo_status(struct burn_drive *d, int *size, int *free_bytes,
			    char **status_text)
{
 int ret;
 struct isoburn *o;
 size_t hsize= 0, hfree_bytes= 0;

 ret= isoburn_find_emulator(&o, d, 0);
 if(ret<0)
   return(-1);

 if(o==NULL)
   return(-1);
 if(o->iso_source==NULL)
   return(-1);
 ret= iso_ring_buffer_get_status(o->iso_source, &hsize, &hfree_bytes);
 if(hsize > 1024*1024*1024)
   *size= 1024*1024*1024;
 else
   *size= hsize;
 if(hfree_bytes > 1024*1024*1024)
   *free_bytes= 1024*1024*1024;
 else
   *free_bytes= hfree_bytes;
 *status_text= "";
 if(ret==0)
   *status_text= "standby";
 else if(ret==1)
   *status_text= "active";
 else if(ret==2)
   *status_text= "ending";
 else if(ret==3)
   *status_text= "failing";
 else if(ret==4)
   *status_text= "unused";
 else if(ret==5)
   *status_text= "abandoned";
 else if(ret==6)
   *status_text= "ended";
 else if(ret==7)
   *status_text= "aborted";
 return(ret);
}


/* @param flag bit0= -reserved-
               bit1= this is a libburn severity
*/
int isoburn__sev_to_text(int severity, char **severity_name,
                         int flag)
{
 int ret;
   
 ret= iso_sev_to_text(severity, severity_name);
 if(ret>0)
   return(ret);
 ret= burn_sev_to_text(severity, severity_name, 0);
 return(ret);
}


int isoburn__text_to_sev(char *severity_name, int *severity_number, int flag)
{
 int ret= 1;

 ret= iso_text_to_sev(severity_name, severity_number);
 if(ret>0)
   return(ret);
 ret= burn_text_to_sev(severity_name, severity_number, 0);
 return(ret);
}


int isoburn_report_iso_error(int iso_error_code, char msg_text[], int os_errno,
                             char min_severity[], int flag)
{
 int error_code, iso_sev, min_sev, ret;
 char *sev_text_pt, *msg_text_pt= NULL;

 error_code= iso_error_get_code(iso_error_code);
 if(error_code < 0x00030000 || error_code >= 0x00040000)
   error_code= (error_code & 0xffff) | 0x00050000;

 if(iso_error_code<0)
   msg_text_pt= (char *) iso_error_to_msg(iso_error_code);
 if(msg_text_pt==NULL)
   msg_text_pt= msg_text;
 iso_sev= iso_error_get_severity(iso_error_code);
 sev_text_pt= min_severity;
 isoburn__text_to_sev(min_severity, &min_sev, 0);
 if(min_sev < iso_sev) 
   isoburn__sev_to_text(iso_sev, &sev_text_pt, 0);
 ret= iso_msgs_submit(error_code, msg_text_pt, os_errno, sev_text_pt, 0);
 return(ret);
}


/* @param flag bit0-7: info return mode
                 0= do not return anything in info (do not even touch it)
                 1= copy volume id to info (info needs 33 bytes)
                 2= do not touch info (caller will copy 64 kB header to it)
               bit14= -reserved -
               bit15= -reserved-
   @return 1 seems to be a valid ISO image , 0 format not recognized, <0 error
*/
int isoburn_read_iso_head_parse(struct burn_drive *d, unsigned char *data,
                                int *image_blocks, char *info, int flag)
{
 int i, info_mode;

 /* is this an ISO image ? */
 if(data[0]!=1)
   return(0);
 if(strncmp((char *) (data+1),"CD001",5)!=0)
   return(0);
 /* believe so */

 *image_blocks= data[80] | (data[81]<<8) | (data[82]<<16) | (data[83]<<24);
 info_mode= flag&255;
 if(info_mode==0) {
   ;
 } else if(info_mode==1) {
   strncpy(info, (char *) (data+40), 32);
   info[32]= 0;
   for(i= strlen(info)-1; i>=0; i--)
     if(info[i]!=' ')
   break;
     else
       info[i]= 0;
 } else if(info_mode==2) {
   ;
 } else {
   isoburn_msgs_submit(NULL, 0x00060000,
               "Program error: Unknown info mode with isoburn_read_iso_head()",
               0, "FATAL", 0);
   return(-1);
 }
 return(1);
}
                         

/* API
   @param flag bit0-7: info return mode
                 0= do not return anything in info (do not even touch it)
                 1= copy volume id to info (info needs 33 bytes)
                 2= copy 64 kB header to info (needs 65536 bytes)
               bit13= do not read head from media but use first 64 kB from info
               bit14= check both half buffers (not only second)
                      return 2 if found in first block
               bit15= return-1 on read error
   @return 1 seems to be a valid ISO image , 2 found in first half buffer,
           0 format not recognized, <0 error
*/
int isoburn_read_iso_head(struct burn_drive *d, int lba,
                          int *image_blocks, char *info, int flag)
{
 unsigned char buffer[64*1024];
 int ret, info_mode, capacity, role;
 off_t data_count, to_read;

 info_mode= flag&255;
 *image_blocks= 0;
 if(flag&(1<<13)) {
   memcpy(buffer, info, 64*1024);
 } else {
   role = burn_drive_get_drive_role(d);
   ret = burn_get_read_capacity(d, &capacity, 0);
   if (ret <= 0 && role == 2) {
     /* Might be a block device on a system where libburn cannot determine its
        size.  Try to read anyway. */
     capacity = 0x7ffffff0;
     ret = 1;
   }
   memset(buffer, 0, 64 * 1024);
   to_read= (off_t) capacity * ((off_t) 2048);
   if(ret > 0 && to_read >= (off_t) (36 * 1024)) {
     if(to_read >= (off_t) (64 * 1024))
       to_read= 64 * 1024;
     ret = burn_read_data(d, ((off_t) lba) * (off_t) 2048, (char *) buffer,
                      to_read, &data_count, 2); /* no error messages */
   } else
     ret= 0;
   if(ret<=0)
     return(-1*!!(flag&(1<<15)));
   if(info_mode==2)
     memcpy(info, buffer, 64*1024);
 }

 if(flag&(1<<14)) {
   ret= isoburn_read_iso_head_parse(d, buffer, image_blocks, info, info_mode);
   if(ret<0)
     return(ret);
   if(ret>0)
     return(2);
 }
 ret= isoburn_read_iso_head_parse(d, buffer+32*1024, image_blocks, info,
                                  info_mode);
 if(ret<=0)
   return(ret);
 return(1);
}


int isoburn_make_toc_entry(struct isoburn *o, int *session_count, int lba,
                           int track_blocks, char *volid, int flag)
{
 int ret;
 struct isoburn_toc_entry *item;

 ret= isoburn_toc_entry_new(&item, o->toc, 0);
 if(ret<=0) {
no_memory:;
   isoburn_msgs_submit(o, 0x00060000,
                       "Not enough memory for emulated TOC entry object",
                       0, "FATAL", 0);
   return(-1);
 }
 if(o->toc==NULL)
   o->toc= item;
 (*session_count)++;
 item->session= *session_count;
 item->track_no= *session_count;
 item->start_lba= lba;
 item->track_blocks= track_blocks;
 if(volid != NULL) {
   item->volid= strdup(volid);
   if(item->volid == NULL)
     goto no_memory;
 }
 return(1);
}


/* @param flag bit0= allow unemulated media
               bit1= free scanning without enclosing LBA-0-header 
               bit4= represent emulated media as one single session
                     (not with bit1)
   @return -1 severe error, 0= no neat header chain, 1= credible chain read
*/
int isoburn_emulate_toc(struct burn_drive *d, int flag)
{
 int ret, image_size= 0, lba, track_blocks, session_count= 0, read_flag= 0;
 int scan_start= 0, scan_count= 0, probe_minus_16= 0, growisofs_nwa, role;
 int with_enclosure= 0, readable_blocks= -1;
 struct isoburn *o;
 char msg[160], size_text[80], *sev, volid[33], *volid_pt= NULL;
 time_t start_time, last_pacifier, now;
 
 /* is the media emulated multi-session ? */
 ret= isoburn_find_emulator(&o, d, 0);
 if(ret<0)
   return(-1);
 if(o==NULL)
   return(-1);
 if(o->emulation_mode<=0 && !(flag&1))
   return(0);

 ret= burn_get_read_capacity(d, &readable_blocks, 0);
 if(ret <= 0) {

   role = burn_drive_get_drive_role(d);
   if (role == 2)
     /* Might be a block device on a system where libburn cannot determine its
        size.  Try to read anyway. */
     readable_blocks= 0x7ffffff0; /* try to read anyway */
   else
     readable_blocks= -1;
 }

 start_time= last_pacifier= time(NULL);
 lba= 0;
 if(!(flag&2)) {
   ret= isoburn_read_iso_head(d, lba, &image_size, NULL, 0);
   if(ret<=0)
     {ret= 0; goto failure;}
   lba= o->target_iso_head_size / 2048;
   with_enclosure= 1;
   if((flag & 16) && o->emulation_mode == 1) {
     ret= 1;
     goto failure; /* This will represent the media as single session */
   }
 }
 while(lba<image_size || (flag&2)) {
   now= time(NULL);
   if(now - last_pacifier >= 5) {
     last_pacifier= now;
     if(scan_count>=10*512)
       sprintf(size_text, "%.f MB", ((double) scan_count) / 512.0);
     else
       sprintf(size_text, "%.f kB", 2 * (double) scan_count);
     sprintf(msg, "Found %d ISO sessions by scanning %s in %.f seconds",
             session_count, size_text, (double) (now - start_time));
     isoburn_msgs_submit(o, 0x00060000, msg, 0, "UPDATE", 0);
   }
   read_flag= 1;
   if(flag&2)
     read_flag|= (1<<15)|((session_count>0)<<14);
   else {

     /* growisofs aligns to 16 rather than 32. Overwriteable TOC emulation
        relies on not accidentially seeing inter-session trash data.
        But one can safely access 16 blocks earlier because a xorriso header
        would have been overwritten with the unused 16 blocks at its start.
        If libisoburn alignment would increase, then this would not be
        possible any more.
     */

     if(probe_minus_16)
       read_flag|= (1<<14);
     probe_minus_16= 0;
   }

   ret= isoburn_read_iso_head(d, lba, &track_blocks, volid, read_flag);
   if(ret > 0) {
     volid_pt= volid;
   } else {
     volid_pt= NULL;
     if(session_count>0) {
       if(flag&2) {
         if(ret==0) {
           /* try at next 64 k block (check both 32 k halves) */
           lba+= 32;
           scan_count+= 32;
           if(lba-scan_start <= Libisoburn_toc_scan_max_gaP)
 continue;
         }
 break;
       }
       sprintf(msg,
               "Chain of ISO session headers broken at #%d, LBA %ds",
               session_count+1, lba);
       isoburn_msgs_submit(o, 0x00060000, msg, 0, "WARNING", 0);

       if(with_enclosure) {
         ret= isoburn_make_toc_entry(o, &session_count, 0, image_size, NULL,0);
         if(ret<=0)
           goto failure;
       }
 break; /* do not return failure */

     }
     {ret= 0; goto failure;}
   }
   if(ret==2) /* ISO header was found in first half block */
     lba-= 16;

   if(readable_blocks >= 0 && lba + track_blocks > readable_blocks) {
     sprintf(msg, "ISO image size %ds larger than readable size %ds",
                  lba + track_blocks, readable_blocks);
     isoburn_msgs_submit(o, 0x00060000, msg, 0, "WARNING", 0);
     track_blocks= readable_blocks - lba;
   }
   ret= isoburn_make_toc_entry(o, &session_count, lba, track_blocks, volid_pt,
                               0);
   if(ret<=0)
     goto failure;
   lba+= track_blocks;
   scan_count+= 32;

   /* growisofs aligns to 16 rather than 32 */
   growisofs_nwa= lba;
   if(growisofs_nwa % 16)
     growisofs_nwa+= 16 - (growisofs_nwa % 16);
   if(lba % Libisoburn_nwa_alignemenT)
     lba+= Libisoburn_nwa_alignemenT - (lba % Libisoburn_nwa_alignemenT);
   scan_start= lba;
   if(lba - growisofs_nwa == 16)
     probe_minus_16= 1;
 }
 if(last_pacifier != start_time)
   sev= "UPDATE";
 else
   sev= "DEBUG";
 now= time(NULL);
 if(scan_count>=10*512)
   sprintf(size_text, "%.f MB", ((double) scan_count) / 512.0);
 else
   sprintf(size_text, "%.f kB", 2 * (double) scan_count);
 sprintf(msg, "Found %d ISO sessions by scanning %s in %.f seconds",
         session_count, size_text, (double) (now - start_time));
 isoburn_msgs_submit(o, 0x00060000, msg, 0, sev, 0);
 return(1);
failure:;
 isoburn_toc_entry_destroy(&(o->toc), 1);
 if(with_enclosure && o->emulation_mode == 1) {
   if(readable_blocks >= 0 && image_size > readable_blocks) {
     sprintf(msg, "ISO image size %ds larger than readable size %ds",
                  image_size, readable_blocks);
     isoburn_msgs_submit(o, 0x00060000, msg, 0, "WARNING", 0);
     image_size= readable_blocks;
   }
   session_count= 0;
   ret= isoburn_make_toc_entry(o, &session_count, 0, image_size, NULL, 0);
 }
 return(ret);
}


int isoburn_toc_new_arrays(struct isoburn_toc_disc *o,
                           int session_count, int track_count, int flag)
{
 int i;
 int isoburn_toc_destroy_arrays(struct isoburn_toc_disc *o, int flag);

 o->sessions= calloc(session_count, sizeof(struct isoburn_toc_session));
 o->session_pointers=
                   calloc(session_count, sizeof(struct isoburn_toc_session *));
 o->tracks= calloc(track_count, sizeof(struct isoburn_toc_track));
 o->track_pointers= calloc(track_count, sizeof(struct isoburn_toc_track *));
 if(o->sessions!=NULL && o->session_pointers!=NULL &&
    o->tracks!=NULL && o->track_pointers!=NULL) {
   for(i= 0; i<session_count; i++) {
     o->sessions[i].session= NULL;
     o->sessions[i].track_pointers= NULL;
     o->sessions[i].track_count= 0;
     o->sessions[i].toc_entry= NULL;
     o->session_pointers[i]= NULL;
   }
   for(i= 0; i<track_count; i++) {
     o->tracks[i].track= NULL;
     o->tracks[i].toc_entry= NULL;
     o->track_pointers[i]= NULL;
   }
   return(1);
 }
 /* failed */
 isoburn_toc_destroy_arrays(o, 0);
 return(-1);
}


int isoburn_toc_destroy_arrays(struct isoburn_toc_disc *o, int flag)
{
 if(o->sessions!=NULL)
   free((char *) o->sessions);
 o->sessions= NULL;
 if(o->session_pointers!=NULL)
   free((char *) o->session_pointers);
 o->session_pointers= NULL;
 if(o->tracks!=NULL)
   free((char *) o->tracks);
 o->tracks= NULL;
 if(o->track_pointers!=NULL)
   free((char *) o->track_pointers);
 o->track_pointers= NULL;
 return(1);
}


struct isoburn_toc_disc *isoburn_toc_drive_get_disc(struct burn_drive *d)
{
 int ret, session_count= 0, track_count= 0, num_tracks= 0, i, j;
 struct isoburn *o;
 struct isoburn_toc_entry *t;
 struct isoburn_toc_disc *toc_disc= NULL;
 struct burn_session **s;
 struct burn_track **tracks;

 toc_disc= calloc(1, sizeof(struct isoburn_toc_disc));
 if(toc_disc==NULL)
   return(NULL);
 toc_disc->disc= NULL;
 toc_disc->sessions= NULL;
 toc_disc->session_pointers= NULL;
 toc_disc->tracks= NULL;
 toc_disc->track_pointers= NULL;
 toc_disc->session_count= 0;
 toc_disc->track_count= 0;
 toc_disc->toc= NULL; 

 /* is the media emulated multi-session ? */
 ret= isoburn_find_emulator(&o, d, 0);
 if(ret<0)
   goto libburn;
 if(o->toc==NULL)
   goto libburn;

 /* This is an emulated TOC */
 toc_disc->toc= o->toc;
 for(t= toc_disc->toc; t!=NULL; t= t->next)
   session_count++;
 ret= isoburn_toc_new_arrays(toc_disc, session_count, session_count, 0);
 if(ret<=0)
   goto failure;
 t= toc_disc->toc;
 for(i= 0; i<session_count; i++) {
   toc_disc->sessions[i].track_pointers= toc_disc->track_pointers+i;
   toc_disc->sessions[i].track_count= 1;
   toc_disc->sessions[i].toc_entry= t;
   toc_disc->session_pointers[i]= toc_disc->sessions+i;
   toc_disc->tracks[i].toc_entry= t;
   toc_disc->track_pointers[i]= toc_disc->tracks+i;
   t= t->next;
 }
 toc_disc->session_count= session_count;
 toc_disc->track_count= session_count;
 return(toc_disc);

libburn:;
 /* This is a libburn provided TOC */
 toc_disc->disc= burn_drive_get_disc(d);
 if(toc_disc->disc == NULL) {
failure:;
   free((char *) toc_disc);
   return(NULL);
 }
 s= burn_disc_get_sessions(toc_disc->disc, &session_count);
 for(i= 0; i<session_count; i++) {
   tracks = burn_session_get_tracks(s[i], &num_tracks);
   track_count+= num_tracks;
 }
 if(session_count<=0 || track_count<=0)
   goto failure;
 ret= isoburn_toc_new_arrays(toc_disc, session_count, track_count, 0);
 if(ret<=0)
   goto failure;
 track_count= 0;
 for(i= 0; i<session_count; i++) {
   tracks = burn_session_get_tracks(s[i], &num_tracks);
   toc_disc->sessions[i].session= s[i];
   toc_disc->sessions[i].track_pointers= toc_disc->track_pointers+track_count;
   toc_disc->sessions[i].track_count= num_tracks;
   toc_disc->session_pointers[i]= toc_disc->sessions+i;
   for(j= 0; j<num_tracks; j++) {
     toc_disc->tracks[track_count+j].track= tracks[j];
     toc_disc->track_pointers[track_count+j]= toc_disc->tracks+(track_count+j);
   }
   track_count+= num_tracks;
 }
 toc_disc->session_count= session_count;
 toc_disc->track_count= track_count;
 return(toc_disc);
}


int isoburn_toc_disc_get_sectors(struct isoburn_toc_disc *disc)
{
 struct isoburn_toc_entry *t;
 int ret= 0, num_sessions, num_tracks;
 struct burn_session **sessions;
 struct burn_track **tracks;
 struct burn_toc_entry entry;

 if(disc==NULL)
   return(0);
 if(disc->toc!=NULL) {
   for(t= disc->toc; t!=NULL; t= t->next)
     ret= t->start_lba + t->track_blocks;
 } else if(disc->disc!=NULL) {
   sessions= burn_disc_get_sessions(disc->disc, &num_sessions);
   if(num_sessions > 0) {
     tracks = burn_session_get_tracks(sessions[num_sessions - 1],
                                      &num_tracks);
     if(num_tracks > 0) {
       burn_track_get_entry(tracks[num_tracks - 1], &entry);
       if(entry.extensions_valid & 1)
         ret= entry.start_lba + entry.track_blocks;
     }
   }
/*
   ret= burn_disc_get_sectors(disc->disc);
*/
 }
 return(ret);
}


struct isoburn_toc_session **isoburn_toc_disc_get_sessions(
                                      struct isoburn_toc_disc *disc, int *num)
{
 *num= disc->session_count;
 return(disc->session_pointers);
}


int isoburn_toc_session_get_sectors(struct isoburn_toc_session *s)
{
 struct isoburn_toc_entry *t;
 int count= 0, i;

 if(s==NULL)
   return(0);
 if(s->toc_entry!=NULL) {
   t= s->toc_entry;
   for(i= 0; i<s->track_count; i++) {
     count+= t->track_blocks;
     t= t->next;
   }
 } else if(s->session!=NULL)
   count= burn_session_get_sectors(s->session);
 return(count);
}


int isoburn_toc_entry_finish(struct burn_toc_entry *entry,
                             int session_no, int track_no, int flag)
{
 int pmin, psec, pframe;

 entry->extensions_valid= 1;
 entry->adr= 1;
 entry->control= 4;
 entry->session= session_no & 255;
 entry->session_msb= (session_no >> 8) & 255;
 entry->point= track_no & 255;
 entry->point_msb= (track_no >> 8) & 255;

 burn_lba_to_msf(entry->start_lba, &pmin, &psec, &pframe);
 if(pmin<=255)
   entry->pmin= pmin;
 else
   entry->pmin= 255;
 entry->psec= psec;
 entry->pframe= pframe; 
 return(1);
}


void isoburn_toc_session_get_leadout_entry(struct isoburn_toc_session *s,
                                       struct burn_toc_entry *entry)
{
 struct isoburn_toc_track *t;

 if(s==NULL)
   return;
 if(s->session!=NULL && s->toc_entry==NULL) {
   burn_session_get_leadout_entry(s->session, entry);
   return;
 }
 if(s->track_count<=0 || s->track_pointers==NULL || s->toc_entry==NULL)
   return;
 t= s->track_pointers[s->track_count-1];
 entry->start_lba= t->toc_entry->start_lba + t->toc_entry->track_blocks;
 entry->track_blocks= 0;
 isoburn_toc_entry_finish(entry, s->toc_entry->session, t->toc_entry->track_no,
                          0);
}


struct isoburn_toc_track **isoburn_toc_session_get_tracks(
                                      struct isoburn_toc_session *s, int *num)
{
 *num= s->track_count;
 return(s->track_pointers);
}


void isoburn_toc_track_get_entry(struct isoburn_toc_track *t,
                                 struct burn_toc_entry *entry)
{
 if(t==0)
   return;
 if(t->track!=NULL && t->toc_entry==NULL) {
   burn_track_get_entry(t->track, entry);
   return;
 }
 if(t->toc_entry==NULL)
   return;
 entry->start_lba= t->toc_entry->start_lba;
 entry->track_blocks= t->toc_entry->track_blocks;
 isoburn_toc_entry_finish(entry, t->toc_entry->session, t->toc_entry->track_no,
                          0);
}


int isoburn_toc_track_get_emul(struct isoburn_toc_track *t, int *start_lba,
                               int *image_blocks, char volid[33], int flag)
{
 if(t->toc_entry == NULL)
   return(0);
 if(t->toc_entry->volid == NULL)
   return(0);
 *start_lba= t->toc_entry->start_lba;
 *image_blocks= t->toc_entry->track_blocks;
 strncpy(volid, t->toc_entry->volid, 32);
 volid[32]= 0;
 return(1);
}


void isoburn_toc_disc_free(struct isoburn_toc_disc *d)
{
 if(d->disc!=NULL)
   burn_disc_free(d->disc);
 isoburn_toc_destroy_arrays(d, 0);
 free((char *) d);
}


int isoburn_get_track_lba(struct isoburn_toc_track *track, int *lba, int flag)
{
 struct burn_toc_entry entry;

 isoburn_toc_track_get_entry(track, &entry);
 if (entry.extensions_valid & 1)
   *lba= entry.start_lba;
 else
   *lba= burn_msf_to_lba(entry.pmin, entry.psec, entry.pframe);
 return(1);
}


int isoburn_drive_set_msgs_submit(struct burn_drive *d,
                            int (*msgs_submit)(void *handle, int error_code, 
                                               char msg_text[], int os_errno, 
                                               char severity[], int flag),
                            void *submit_handle, int submit_flag, int flag)
{
 struct isoburn *o;
 int ret;

 ret= isoburn_find_emulator(&o, d, 0);
 if(ret<0 || o==NULL)
   return(-1);
 o->msgs_submit= msgs_submit;
 o->msgs_submit_handle= submit_handle;
 o->msgs_submit_flag= submit_flag;
 return(1);
}

 
/* @param flag bit0= with adr_mode 3: adr_value might be 16 blocks too high
               bit1= insist in seeing a disc object with at least one session
               bit2= with adr_mode 4: use adr_value as regular expression
*/
int isoburn_set_msc1(struct burn_drive *d, int adr_mode, char *adr_value,
                     int flag)
{
 int ret, num_sessions= 0, num_tracks, adr_num, i, j, total_tracks;
 int lba, best_lba, size, re_valid= 0, track_count= 0;
 time_t start_time= 0, last_pacifier= 0, now;
 char volid[33], msg[160];
 struct isoburn *o;
 struct isoburn_toc_disc *disc= NULL;
 struct isoburn_toc_session **sessions= NULL;
 struct isoburn_toc_track **tracks= NULL;
 static char mode_names[][20]= {"auto", "session", "track", "lba", "volid"};
 static int max_mode_names= 4;
 regex_t re;
 regmatch_t match[1];

 ret= isoburn_find_emulator(&o, d, 0);
 if(ret<0)
   return(-1);
 if(o==NULL)
   return(-1);

 start_time= last_pacifier= time(NULL);
 adr_num= atoi(adr_value);
 if(adr_mode!=3 || (flag & 2)) {
   disc= isoburn_toc_drive_get_disc(d);
   if(disc==NULL) {
not_found:;
     if(adr_mode<0 || adr_mode>max_mode_names)
       goto unknown_mode;
     sprintf(msg, "Failed to find %s %s", mode_names[adr_mode],
                  strlen(adr_value)<=80 ?  adr_value : "-oversized-string-");
     isoburn_msgs_submit(o, 0x00060000, msg, 0, "FAILURE", 0);
     ret= 0; goto ex;
   }
   sessions= isoburn_toc_disc_get_sessions(disc, &num_sessions);
   if(sessions==NULL || num_sessions<=0)
     goto not_found;
 }
 if(adr_mode==0) {
   /* Set fabricated_msc1 to last session in TOC */
   tracks= isoburn_toc_session_get_tracks(sessions[num_sessions-1],
                                          &num_tracks);
   if(tracks==NULL || num_tracks<=0)
     goto not_found;
   isoburn_get_track_lba(tracks[0], &(o->fabricated_msc1), 0);

 } else if(adr_mode==1) {
   /* Use adr_num as session index (first session is 1, not 0) */
   if(adr_num<1 || adr_num>num_sessions)
     goto not_found;
   tracks= isoburn_toc_session_get_tracks(sessions[adr_num-1], &num_tracks);
   if(tracks==NULL || num_tracks<=0)
     goto not_found;
   isoburn_get_track_lba(tracks[0], &(o->fabricated_msc1), 0);

 } else if(adr_mode==2) {
   /* use adr_num as track index */
   total_tracks= 0;
   for(i=0; i<num_sessions; i++) {
     tracks= isoburn_toc_session_get_tracks(sessions[i], &num_tracks);
     if(tracks==NULL)
   continue;
     for(j= 0; j<num_tracks; j++) {
       total_tracks++;
       if(total_tracks==adr_num) {
         isoburn_get_track_lba(tracks[j], &(o->fabricated_msc1), 0);
         ret= 1; goto ex;
       }
     }
   }
   goto not_found;

 } else if(adr_mode==3) {
   o->fabricated_msc1= adr_num;
   if((flag & 1) && o->fabricated_msc1 >= 16) {
     /* adr_num is possibly 16 blocks too high */
     ret= isoburn_read_iso_head(d, o->fabricated_msc1, &size,volid, 1|(1<<14));
     if(ret==2)
       o->fabricated_msc1-= 16;
   }
 } else if(adr_mode==4) {
   /* search for volume id that is equal to adr_value */
   if(flag & 4) {
     ret= regcomp(&re, adr_value, 0);
     if(ret != 0)
       flag&= ~4;
     else
       re_valid= 1;
   }
   best_lba= -1;
   for(i=0; i<num_sessions; i++) {
     tracks= isoburn_toc_session_get_tracks(sessions[i], &num_tracks);
     if(tracks==NULL)
   continue;
     for(j= 0; j<num_tracks; j++) {
       now= time(NULL);
       if(now - last_pacifier >= 5 && track_count > 0) {
         last_pacifier= now;
         sprintf(msg,
                 "Scanned %d tracks for matching volid in %.f seconds",
                 track_count, (double) (now - start_time));
         isoburn_msgs_submit(o, 0x00060000, msg, 0, "UPDATE", 0);
       }
       track_count++;
       ret= isoburn_toc_track_get_emul(tracks[0], &lba, &size, volid, 0);
       if(ret < 0)
     continue;
       if(ret == 0) {
         isoburn_get_track_lba(tracks[0], &lba, 0);
         ret= isoburn_read_iso_head(d, lba, &size, volid, 1);
         if(ret<=0)
     continue;
       }
       if(flag & 4) {
         ret= regexec(&re, volid, 1, match, 0);
         if(ret != 0)
     continue;
       } else {
         if(strcmp(volid, adr_value)!=0)
     continue;
       }
       best_lba= lba;
     }
   }
   if(best_lba<0)
     goto not_found;
   o->fabricated_msc1= best_lba;

 } else {
unknown_mode:;
   sprintf(msg, "Program error: Unknown msc1 address mode %d", adr_mode);
   isoburn_msgs_submit(o, 0x00060000, msg, 0, "FATAL", 0);
   ret= 0; goto ex;
 }
 ret= 1;
ex:;
 if(start_time != last_pacifier && track_count > 0) {
   now= time(NULL);
   sprintf(msg,
           "Scanned %d tracks for matching volid in %.f seconds",
           track_count, (double) (now - start_time));
   isoburn_msgs_submit(o, 0x00060000, msg, 0, "UPDATE", 0);
 }
 if(disc!=NULL)
   isoburn_toc_disc_free(disc);
 if((flag & 4) && re_valid)
   regfree(&re);
 return(ret);
}


int isoburn_get_mount_params(struct burn_drive *d,
                             int adr_mode, char *adr_value,
                             int *lba, int *track, int *session,
                             char volid[33], int flag)
{
 int msc1_mem, ret, total_tracks, num_sessions, num_tracks, i, j, track_lba;
 int size, is_iso= 0;
 struct isoburn *o;
 struct isoburn_toc_disc *disc= NULL;
 struct isoburn_toc_session **sessions= NULL;
 struct isoburn_toc_track **tracks= NULL;

 *lba= *track= *session= -1;
 volid[0]= 0;
 ret= isoburn_find_emulator(&o, d, 0);
 if(ret < 0 || o == NULL)
   return(-1);
 msc1_mem= o->fabricated_msc1;
 ret= isoburn_set_msc1(d, adr_mode, adr_value, 2 | (flag & 4));
 if(ret <= 0)
   return(ret);
 *lba= o->fabricated_msc1;

 disc= isoburn_toc_drive_get_disc(d);
 if(disc==NULL) 
   {ret= -1; goto ex;} /* cannot happen because checked by isoburn_set_msc1 */
 sessions= isoburn_toc_disc_get_sessions(disc, &num_sessions);
 if(sessions==NULL || num_sessions<=0)
   {ret= -1; goto ex;} /* cannot happen because checked by isoburn_set_msc1 */
 total_tracks= 0;
 for(i=0; i<num_sessions && *session < 0; i++) {
   tracks= isoburn_toc_session_get_tracks(sessions[i], &num_tracks);
   if(tracks==NULL)
 continue;
   for(j= 0; j<num_tracks && *track < 0; j++) {
     total_tracks++;
     isoburn_get_track_lba(tracks[j], &track_lba, 0);
     if(track_lba == *lba) {
       *track= total_tracks;
       *session= i + 1;
     }
   }
 }
 ret= isoburn_read_iso_head(d, *lba, &size, volid, 1);
 if(ret <= 0)
   volid[0]= 0;
 else
   is_iso= 1;

ex:;
 o->fabricated_msc1= msc1_mem;
 return(2 - is_iso); 
}


