
/* xorriso - creates, loads, manipulates and burns ISO 9660 filesystem images.

   Copyright 2007-2010 Thomas Schmitt, <scdbackup@gmx.net>

   Provided under GPL version 2 or later.

   This file contains functions which operate on drives and media.
*/

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

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


#include "xorriso.h"
#include "xorriso_private.h"

#include "lib_mgt.h"
#include "drive_mgt.h"
#include "iso_img.h"
#include "sort_cmp.h"
#include "write_run.h"
#include "read_run.h"


static const char *un0(const char *text)
{
 if(text == NULL)
   return("");
 return(text);
}




int Xorriso_auto_driveadr(struct XorrisO *xorriso, char *adr, char *result,
                          int flag)
{
 int ret, is_known_mmc= 0;
 char *path_pt, libburn_adr[BURN_DRIVE_ADR_LEN + SfileadrL];
 char *abs_pt, abs_adr[SfileadrL];

 path_pt= adr;
 if(strncmp(adr, "stdio:", 6) == 0)
   path_pt= adr + 6;
 else if(strncmp(adr, "mmc:", 4) == 0)
   path_pt= adr + 4;


 /* <<< replace by Xorriso_normalize_img_path() ? */;

 if(path_pt[0] != '/') {
   abs_pt= getcwd(abs_adr, SfileadrL - 1);
   if(abs_pt == NULL) {
     Xorriso_msgs_submit(xorriso, 0,
              "Relative drive path given. Cannot determine working directory.",
               errno, "FAILURE", 0);
     return(-1);
   }
   ret= Sfile_add_to_path(abs_adr, path_pt, 0);
   if(ret <= 0)
     return(-1);
 }

 is_known_mmc= burn_drive_convert_fs_adr(path_pt, libburn_adr);
 Xorriso_process_msg_queues(xorriso,0);

 ret= Xorriso_is_in_patternlist(xorriso, xorriso->drive_whitelist, path_pt, 0);
 if(ret > 0)
   goto ok;
 ret= Xorriso_is_in_patternlist(xorriso, xorriso->drive_blacklist, path_pt, 0);
 if(ret < 0)
   return(ret);
 if(ret) {
   strcpy(xorriso->info_text, "Drive address ");
   Text_shellsafe(adr, xorriso->info_text, 1);
   strcat(xorriso->info_text,
          " rejected because: -drive_class 'banned' ");
   Text_shellsafe(Xorriso_get_pattern(xorriso, xorriso->drive_blacklist,
                                      ret - 1, 0),
                  xorriso->info_text, 1);
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
   return(0);
 }
 /* if in greylist and not MMC and not stdio prefix: reject */
 if(is_known_mmc < 0)
   return(ret);
 if(adr == path_pt && !is_known_mmc) { /* no prefix, no MMC */
   ret= Xorriso_is_in_patternlist(xorriso, xorriso->drive_greylist, path_pt,0);
   if(ret < 0)
     return(ret);
   if(ret) {
     strcpy(xorriso->info_text, "Drive address ");
     Text_shellsafe(adr, xorriso->info_text, 1);
     strcat(xorriso->info_text,
            " rejected because: not MMC and -drive_class 'risky' ");
     Text_shellsafe(Xorriso_get_pattern(xorriso,xorriso->drive_greylist,
                                        ret - 1, 0),
                    xorriso->info_text, 1);
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
     Xorriso_msgs_submit(xorriso, 0,
                   "If the address is a legitimate target, prepend \"stdio:\"",
                   0, "HINT", 0);
     return(0);
   }
 }
ok:;
 if(strncmp(adr, "mmc:", 4) == 0) {
   if(Sfile_str(result, path_pt, 0) <= 0)
     return(0);
 } else if(adr == path_pt && is_known_mmc <= 0) {
   Sfile_str(result, "stdio:", 0);
   if(Sfile_str(result, adr, 1) <= 0)
     return(0);
 } else {
   if(Sfile_str(result, adr, 0) <= 0)
     return(0);
 }
 if(strncmp(result, "stdio:", 6)==0) {
   if(xorriso->ban_stdio_write) {
     strcpy(xorriso->info_text, "Drive address banned by -ban_stdio_write : ");
     Text_shellsafe(result, xorriso->info_text, 1);
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
     return(0);
   }
 }
 return(1);
}


/* @param flag bit0= aquire as isoburn input drive
               bit1= aquire as libburn output drive (as isoburn drive if bit0)
               bit2= regard overwriteable media as blank
               bit3= if the drive is a regular disk file: truncate it to
                     the write start address
               bit5= do not print toc
               bit6= do not calm down drive after aquiring it
   @return <=0 failure , 1= ok
           2=success, but not writeable with bit1
           3=success, but not blank and not ISO with bit0
*/
int Xorriso_aquire_drive(struct XorrisO *xorriso, char *adr, int flag)
{
 int ret, hret, not_writeable= 0, has_what, aquire_flag, load_lba, ext;
 int lba, track, session, params_flag, adr_mode, read_ret;
 uint32_t size, offst;
 struct burn_drive_info *dinfo= NULL, *out_dinfo, *in_dinfo;
 struct burn_drive *drive= NULL, *out_drive, *in_drive;
 enum burn_disc_status state;
 IsoImage *volset = NULL;
 IsoNode *root_node;
 struct isoburn_read_opts *ropts= NULL;
 char libburn_adr[SfileadrL], *boot_fate, *sev;
 size_t value_length;
 char *value= NULL;
 double num;
 char volid[33], adr_data[163], *adr_pt;

 if((flag&3)==0) { 
   sprintf(xorriso->info_text, 
         "XORRISOBURN program error : Xorriso_aquire_drive bit0+bit1 not set");
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FATAL", 0);
   return(-1);
 }
 ret= Xorriso_give_up_drive(xorriso, (flag&3)|8);
 if(ret<=0)
   return(ret);
 if(flag & 1) 
   xorriso->isofs_st_out= time(0) - 1;

 ret= Xorriso_auto_driveadr(xorriso, adr, libburn_adr, 0);
 if(ret <= 0)
   return(ret);
 if(strcmp(libburn_adr,"stdio:/dev/fd/1")==0) {
   if(xorriso->dev_fd_1<0) {
     sprintf(xorriso->info_text,
     "-*dev \"stdio:/dev/fd/1\" was not a start argument. Cannot use it now.");
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
     {ret= 0; goto ex;}
   } else {
     sprintf(libburn_adr, "stdio:/dev/fd/%d", xorriso->dev_fd_1);
   }
 }

 if((flag&3)==1 && xorriso->out_drive_handle!=NULL) {
   ret= Xorriso_get_drive_handles(xorriso, &out_dinfo, &out_drive,
                         "on attempt to compare new indev with outdev", 2);
   if(ret<=0)
     goto ex;
   ret= burn_drive_equals_adr(out_drive, libburn_adr, 1);
   if(ret==1) {
     dinfo= out_dinfo;
     xorriso->indev_is_exclusive= xorriso->outdev_is_exclusive;
   }
 } else if((flag&3)==2 && xorriso->in_drive_handle!=NULL) {
   ret= Xorriso_get_drive_handles(xorriso, &in_dinfo, &in_drive,
                         "on attempt to compare new outdev with indev", 0);
   if(ret<=0)
     goto ex;
   ret= burn_drive_equals_adr(in_drive, libburn_adr, 1);
   if(ret==1) {
     dinfo= in_dinfo;
     xorriso->outdev_is_exclusive= xorriso->indev_is_exclusive;
   }
 }

 if(dinfo==NULL) {
   aquire_flag= 1 | ((flag&(8|4))>>1) | ((xorriso->toc_emulation_flag & 3)<<3);
   if(xorriso->toc_emulation_flag & 4)
     aquire_flag|= 128;
   if(!(xorriso->do_aaip & 1))
     aquire_flag|= 32;
   if((xorriso->ino_behavior & (1 | 2)) && !(xorriso->do_aaip & (4 | 32)))
     aquire_flag|= 64;
   burn_preset_device_open(xorriso->drives_exclusive, 0, 0);
   ret= isoburn_drive_aquire(&dinfo, libburn_adr, aquire_flag);
   burn_preset_device_open(1, 0, 0);
   Xorriso_process_msg_queues(xorriso,0);
   if(ret<=0) {
     sprintf(xorriso->info_text,"Cannot aquire drive '%s'", adr); 
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
     ret= 0; goto ex;
   }

   state= isoburn_disc_get_status(dinfo[0].drive);
   ret= isoburn_get_img_partition_offset(dinfo[0].drive, &offst);
   if((state == BURN_DISC_APPENDABLE || state == BURN_DISC_FULL) && ret == 1) {
     sprintf(xorriso->info_text,
             "ISO image bears MBR with  -boot_image any partition_offset=%lu",
             (unsigned long) offst);
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0);
   }

   if(flag&1)
     if(xorriso->image_start_mode&(1<<31)) /* used up setting */
       xorriso->image_start_mode= 0; /* no need to perform auto setting */
   if(flag & 1)
     xorriso->indev_is_exclusive= xorriso->drives_exclusive;
   if(flag & 2)
     xorriso->outdev_is_exclusive= xorriso->drives_exclusive;
 }
 drive= dinfo[0].drive;
 state= isoburn_disc_get_status(drive);
 Xorriso_process_msg_queues(xorriso,0);
 if(flag&1) {
   if(xorriso->image_start_mode&(1<<31)) /* used up setting */
     xorriso->image_start_mode&= ~0xffff; /* perform auto setting */
   if((xorriso->image_start_mode&(1<<30))) { /* if enabled at all */
     adr_pt= xorriso->image_start_value;
     adr_mode= xorriso->image_start_mode & 0xffff;
     if(adr_mode == 4 && strlen(adr_pt) <= 80) {
       /* Convert volid search expression into lba */
       params_flag= 0;
       ret= Xorriso__bourne_to_reg(xorriso->image_start_value, adr_data, 0);
       if(ret == 1)
         params_flag|= 4;
       ret= isoburn_get_mount_params(drive, 4, adr_data, &lba, &track,
                                     &session, volid, params_flag);
       Xorriso_process_msg_queues(xorriso,0);
       if(ret <= 0)
         goto ex;
       if(session <= 0 || track <= 0 || ret == 2) {
         Xorriso_msgs_submit(xorriso, 0,
                "-load : Given address does not point to an ISO 9660 session",
                0, "FAILURE", 0);
         ret= 0; goto ex;
       }
       sprintf(volid, "%d", lba);
       adr_pt= volid;
       adr_mode= 3;
     }
     ret= isoburn_set_msc1(drive, adr_mode, adr_pt,
                           !!(xorriso->image_start_mode & (1<<16)));
     if(ret<=0)
       goto ex;
     if(xorriso->image_start_mode&(1<<31))
       xorriso->image_start_mode= 0; /* disable msc1 setting completely */
     else
       xorriso->image_start_mode|= (1<<31); /* mark as used up */
   }
 }
 if(flag&1) {
   volset= isoburn_get_attached_image(drive);
   if(volset != NULL) { /* The image object is already created */
     iso_image_unref(volset);
   }
 }

 if(flag&2) {
   xorriso->out_drive_handle= dinfo;
   if(Sfile_str(xorriso->outdev, adr, 0)<=0)
     {ret= -1; goto ex;}
   if(state != BURN_DISC_BLANK && state != BURN_DISC_APPENDABLE) {
     sprintf(xorriso->info_text, "Disc status unsuitable for writing");
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0);
     not_writeable= 1;
   }
 }
 if(flag&1) {
   xorriso->in_drive_handle= dinfo;
   if(Sfile_str(xorriso->indev, adr, 0)<=0)
     {ret= -1; goto ex;}
 } else if(flag&2) {
   if(xorriso->in_volset_handle==NULL) {
     /* No volume loaded: create empty one */
     ret= Xorriso_create_empty_iso(xorriso, 0);
     if(ret<=0)
       goto ex;
   } else {
     iso_image_ref((IsoImage *) xorriso->in_volset_handle);
     ret= isoburn_attach_image(drive, (IsoImage *) xorriso->in_volset_handle);
     if(ret<=0) {
       sprintf(xorriso->info_text,
               "Failed to attach ISO image object to outdev");
       Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FATAL", 0);
       {ret= -1; goto ex;}
     }
   }
   if(!(flag&32))
     Xorriso_toc(xorriso, 1 | 2 | 8);
   {ret= 1+not_writeable; goto ex;}
 }

 if(xorriso->in_volset_handle!=NULL)
   iso_image_unref((IsoImage *) xorriso->in_volset_handle);
 xorriso->in_volset_handle= NULL;
 Sectorbitmap_destroy(&(xorriso->in_sector_map), 0);
 Xorriso_destroy_hln_array(xorriso, 0);
 Xorriso_destroy_di_array(xorriso, 0);
 xorriso->boot_count= 0;

 /* check for invalid state */
 if(state != BURN_DISC_BLANK && state != BURN_DISC_APPENDABLE &&
    state != BURN_DISC_FULL) {
   sprintf(xorriso->info_text,
           "Disc status not blank and unsuitable for reading");
   sev= "FAILURE";
   if(xorriso->img_read_error_mode==2)
     sev= "FATAL";
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, sev, 0);
   Xorriso_give_up_drive(xorriso, 1|((flag&32)>>2));
   ret= 3; goto ex;
 }
 /* fill read opts */ 
 ret= isoburn_ropt_new(&ropts, 0);
 if(ret<=0)
   goto ex;

 ext= isoburn_ropt_noiso1999;
 if((xorriso->ino_behavior & (1 | 2)) && !(xorriso->do_aaip & (1 | 4 | 32))
    && !(xorriso->do_md5 & 1)) 
   ext|= isoburn_ropt_noaaip; 
 if(!(xorriso->do_aaip & 1)) 
   ext|= isoburn_ropt_noacl; 
 if(!(xorriso->do_aaip & 4))
   ext|= isoburn_ropt_noea; 
 if(xorriso->ino_behavior & 1)
   ext|= isoburn_ropt_noino;

#ifdef isoburn_ropt_nomd5
 if(!(xorriso->do_md5 & 1))
   ext|= isoburn_ropt_nomd5;
#endif

 isoburn_ropt_set_extensions(ropts, ext);

 isoburn_ropt_set_default_perms(ropts, (uid_t) 0, (gid_t) 0, (mode_t) 0555);
 isoburn_ropt_set_input_charset(ropts, xorriso->in_charset);
 isoburn_ropt_set_auto_incharset(ropts, !!(xorriso->do_aaip & 512));
 isoburn_ropt_set_displacement(ropts, xorriso->displacement,
                                      xorriso->displacement_sign);
 
 Xorriso_set_image_severities(xorriso, 1); /* No DEBUG messages */
 Xorriso_pacifier_reset(xorriso, 0);
 isoburn_set_read_pacifier(drive, Xorriso__read_pacifier, (void *) xorriso);

 /* <<< Trying to work around too much tolerance on bad image trees.
        Better would be a chance to instruct libisofs what to do in
        case of image read errors. There is a risk to mistake other SORRYs.
 */
 if(xorriso->img_read_error_mode>0)
   iso_set_abort_severity("SORRY");

 if(state != BURN_DISC_BLANK) {
   ret= isoburn_disc_get_msc1(drive, &load_lba);
   if(ret > 0) {
     sprintf(xorriso->info_text,
             "Loading ISO image tree from LBA %d", load_lba);
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0); 
   }
   ret= Xorriso_assert_volid(xorriso, load_lba, 0);
   if(ret <= 0)
     goto ex;
 }

 read_ret= ret= isoburn_read_image(drive, ropts, &volset);

 /* <<< Resetting to normal thresholds */
 if(xorriso->img_read_error_mode>0)
   Xorriso_set_abort_severity(xorriso, 0);

 if(ret<=0) {
   Xorriso_process_msg_queues(xorriso,0);
   Xorriso_set_image_severities(xorriso, 0);
   Xorriso_give_up_drive(xorriso, 1|((flag&32)>>2));
   sprintf(xorriso->info_text,"Cannot read ISO image tree");
   sev= "FAILURE";
   if(xorriso->img_read_error_mode==2)
     sev= "FATAL";
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, sev, 0);
   if(read_ret == ISO_SB_TREE_CORRUPTED && (xorriso->do_md5 & 1)) {
     Xorriso_msgs_submit(xorriso, 0,
           "You might get a questionable ISO image tree by option -md5 'off'.",
            0, "HINT", 0);
   } else if(xorriso->img_read_error_mode!=0) {
     Xorriso_msgs_submit(xorriso, 0, "You might get a partial or altered ISO image tree by option -error_behavior 'image_loading' 'best_effort' if -abort_on is set to be tolerant enough.",
                         0, "HINT", 0);
   }


   ret= 3; goto ex;
 }
 Xorriso_pacifier_callback(xorriso, "nodes read", xorriso->pacifier_count, 0,
                           "", 1); /* report end count */
 xorriso->in_volset_handle= (void *) volset;
 xorriso->in_sector_map= NULL;
 Xorriso_set_image_severities(xorriso, 0);

 Xorriso_update_volid(xorriso, 0);
 strncpy(xorriso->application_id,
         un0(iso_image_get_application_id(volset)), 128);
 xorriso->application_id[128]= 0;
 strncpy(xorriso->publisher, un0(iso_image_get_publisher_id(volset)), 128);
 xorriso->publisher[128]= 0;
 strncpy(xorriso->system_id, un0(iso_image_get_system_id(volset)), 32);
 xorriso->system_id[32]= 0;
 strncpy(xorriso->volset_id, un0(iso_image_get_volset_id(volset)), 128);
 xorriso->volset_id[128]= 0;
 strncpy(xorriso->copyright_file,
         un0(iso_image_get_copyright_file_id(volset)), 37);
 xorriso->copyright_file[37]= 0;
 strncpy(xorriso->biblio_file, un0(iso_image_get_biblio_file_id(volset)), 37);
 xorriso->biblio_file[37]= 0;
 strncpy(xorriso->abstract_file,
         un0(iso_image_get_abstract_file_id(volset)), 37);
 xorriso->abstract_file[37]= 0;

 if(xorriso->out_drive_handle != NULL &&
    xorriso->out_drive_handle != xorriso->in_drive_handle) {
   ret= Xorriso_get_drive_handles(xorriso, &out_dinfo, &out_drive,
                         "on attempt to attach ISO image volset to outdev", 2);
   if(ret<=0)
     goto ex;
   iso_image_ref((IsoImage *) xorriso->in_volset_handle);
   isoburn_attach_image(out_drive, xorriso->in_volset_handle);
 }
 Xorriso_process_msg_queues(xorriso,0);
 isoburn_ropt_get_size_what(ropts, &size, &has_what);
 if(has_what & isoburn_ropt_has_el_torito) {
   if(xorriso->boot_image_bin_path[0])
     boot_fate= "replaced by new boot image";
   else if(xorriso->patch_isolinux_image & 1)
     boot_fate= "patched at boot info table";
   else if(xorriso->keep_boot_image)
     boot_fate= "kept unchanged";
   else
     boot_fate= "discarded";
   sprintf(xorriso->info_text,
         "Detected El-Torito boot information which currently is set to be %s",
         boot_fate);
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0); 
   Xorriso_record_boot_info(xorriso, 0);
 }

 if(flag & 1) {
   /* Look for isofs.st and put it into xorriso->isofs_st_in */;
   root_node= (IsoNode *) iso_image_get_root(volset);
   ret= iso_node_lookup_attr(root_node, "isofs.st", &value_length, &value, 0);
   if(ret > 0) {
     if(value_length > 0) {
       sscanf(value, "%lf", &num);
       if(num > 0)
         xorriso->isofs_st_in= num;
     }
     free(value);
   }
 }

 if(!(flag&32)) {
   Xorriso_toc(xorriso, 1 | 8);
   if(xorriso->loaded_volid[0]!=0) {
     sprintf(xorriso->info_text,"Volume id    : '%s'\n",
             xorriso->loaded_volid);
     Xorriso_info(xorriso, 0);
     if(strcmp(xorriso->loaded_volid, xorriso->volid) != 0 &&
        !xorriso->volid_default) {
       sprintf(xorriso->info_text, "New volume id: '%s'\n", xorriso->volid);
       Xorriso_info(xorriso, 0);
     }
   }
 }

 ret= 1+not_writeable;
ex:
 Xorriso_process_msg_queues(xorriso,0);
 if(ret<=0) {
   hret= Xorriso_give_up_drive(xorriso, (flag&3)|((flag&32)>>2));
   if(hret<ret)
     ret= hret;
 } else {
   if(drive != NULL && (xorriso->do_calm_drive & 1) && !(flag & 64))
     burn_drive_snooze(drive, 0); /* No need to make noise from start */
 }
 if(ropts!=NULL)
   isoburn_ropt_destroy(&ropts, 0);
 return(ret);
}


/* @param flag bit0=input drive
               bit1=output drive
               bit2=eject
               bit3=no info message or toc
*/
int Xorriso_give_up_drive(struct XorrisO *xorriso, int flag)
{
 int in_is_out_too, ret, do_eject;
 struct burn_drive_info *dinfo;
 struct burn_drive *drive;
 char sfe[5*SfileadrL];
 
 in_is_out_too= (xorriso->in_drive_handle == xorriso->out_drive_handle);
 if((flag&4) && in_is_out_too && (flag&(1|2))) {
   if((flag&3)!=3) {
     sprintf(xorriso->info_text,"Giving up for -eject whole -dev %s",
               Text_shellsafe(xorriso->indev, sfe, 0));
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0);
   }
   flag|= 3; /* give up in/out drive to eject it */
 }
   
 if((flag&1) && xorriso->in_drive_handle != NULL) {
   Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                             "on attempt to give up drive", 0);

   if(!in_is_out_too) {
     do_eject= !!(flag&4);
     if((flag & 4) && !xorriso->indev_is_exclusive) {
       sprintf(xorriso->info_text,
               "Will not eject media in non-exclusively aquired input drive.");
       Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "WARNING", 0);
       do_eject= 0;
     }
     if(drive!=NULL)
       isoburn_drive_release(drive, do_eject);
     if(dinfo!=NULL)
       burn_drive_info_free(dinfo);
   }
   xorriso->in_drive_handle= NULL;
   xorriso->indev[0]= 0;

   if(xorriso->in_volset_handle!=NULL)
     iso_image_unref((IsoImage *) xorriso->in_volset_handle);
   xorriso->in_volset_handle= NULL;
   Sectorbitmap_destroy(&(xorriso->in_sector_map), 0);
   Xorriso_destroy_di_array(xorriso, 0);
   Xorriso_destroy_hln_array(xorriso, 0);
   xorriso->loaded_volid[0]= 0;
   xorriso->isofs_st_out= time(0) - 1;
   xorriso->isofs_st_in= 0;
   xorriso->volset_change_pending= 0;
   xorriso->no_volset_present= 0; 
   xorriso->loaded_boot_bin_lba= 0;
   xorriso->loaded_boot_cat_path[0]= 0;
   xorriso->boot_count= 0;
   in_is_out_too= 0;
 }
 if((flag&2) && xorriso->out_drive_handle!=NULL) {
   do_eject= !!(flag&4);
   if((flag & 4) && !xorriso->outdev_is_exclusive) {
     sprintf(xorriso->info_text,
             "Will not eject media in non-exclusively aquired drive.");
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "WARNING", 0);
     do_eject= 0;
   }
   Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                             "on attempt to give up drive", 2);
   if(!in_is_out_too) {
     if(drive!=NULL)
       isoburn_drive_release(drive, do_eject);
     if(dinfo!=NULL)
       burn_drive_info_free(dinfo);
   }
   xorriso->out_drive_handle= NULL;
   xorriso->outdev[0]= 0;
 } else if((flag&1) && xorriso->out_drive_handle!=NULL) {
   ret= Xorriso_create_empty_iso(xorriso, 0);
   if(ret<=0)
     return(ret);
   if(!(flag&8)) {
     sprintf(xorriso->info_text,
             "Only the output drive remains. Created empty ISO image.\n");
     Xorriso_info(xorriso, 0);
     Xorriso_toc(xorriso, 1 | 2 | 8);
   }
 }
 Xorriso_process_msg_queues(xorriso,0);
 return(1);
}


int Xorriso_may_burn(struct XorrisO *xorriso, int flag)
{

 if(xorriso->outdev_is_exclusive)
   return(1);
 sprintf(xorriso->info_text, "The output drive was not aquired exclusively.");
 Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
 sprintf(xorriso->info_text, "A possible remedy is: -osirrox 'o_excl_on'");
 if(xorriso->outdev[0]) {
   strcat(xorriso->info_text," -outdev ");
   Text_shellsafe(xorriso->outdev, xorriso->info_text, 1);
 }
 Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "HINT", 0);
 return(0);
}


/* @param flag bit2=do not try to read ISO heads
*/
int Xorriso_toc_to_string(struct XorrisO *xorriso, char **toc_text, int flag)
{
 int ret, stack_handle, toc_ret, l;
 struct Xorriso_lsT *results= NULL, *infos= NULL, *lpt;

 *toc_text= NULL;
 ret= Xorriso_push_outlists(xorriso, &stack_handle, 1);
 if(ret <= 0)
   goto ex;
 toc_ret= Xorriso_toc(xorriso, flag & 4);
 ret= Xorriso_pull_outlists(xorriso, stack_handle, &results, &infos, 0);
 if(ret <= 0)
   goto ex;
 if(toc_ret <= 0)
   {ret= toc_ret; goto ex;}
 l= 0;
 for(lpt= results; lpt != NULL; lpt= Xorriso_lst_get_next(lpt, 0))
   l+= strlen(Xorriso_lst_get_text(lpt, 0));
 *toc_text= calloc(l + 1, 1);
 l= 0;
 for(lpt= results; lpt != NULL; lpt= Xorriso_lst_get_next(lpt, 0)) {
   strcpy((*toc_text) + l, Xorriso_lst_get_text(lpt, 0));
   l+= strlen(Xorriso_lst_get_text(lpt, 0));
 }
ex:;
 Xorriso_lst_destroy_all(&results, 0);
 Xorriso_lst_destroy_all(&infos, 0);
 return(ret);
}


/* @param flag bit0+1= what to aquire after giving up outdev
                       0=none, 1=indev, 2=outdev, 3=both
*/
int Xorriso_reaquire_outdev(struct XorrisO *xorriso, int flag)
{
 int ret, aq_flag;
 char drive_name[SfileadrL], sfe[5*SfileadrL];

 aq_flag= flag&3;
 strcpy(drive_name, xorriso->outdev);
 Xorriso_give_up_drive(xorriso, aq_flag);
 if(aq_flag==0) { 
   sprintf(xorriso->info_text,"Gave up -outdev %s",
           Text_shellsafe(xorriso->outdev, sfe, 0));
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0);
   return(1);
 }
 sprintf(xorriso->info_text,"Re-aquiring -outdev %s",
         Text_shellsafe(drive_name, sfe, 0));
 Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0);
 ret= Xorriso_aquire_drive(xorriso, drive_name, aq_flag); 
 if(ret<=0) {
   sprintf(xorriso->info_text,"Could not re-aquire -outdev %s",
           Text_shellsafe(xorriso->outdev, sfe, 0));
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
   return(ret);
 }
 return(1);
}


/* @param flag
               bit3=report to info channel (else to result channel)
*/
int Xorriso_toc_line(struct XorrisO *xorriso, int flag)
{
 if(!(flag & 8)) {
   Xorriso_result(xorriso,0);
   return(1);
 }
 strcpy(xorriso->info_text, xorriso->result_line);
 Xorriso_info(xorriso, 0);
 return(1);
}


/* @param flag
               bit1=report about output drive
               bit3=report to info channel (else to result channel)
               bit4=do no report failure if no drive aquired
*/
int Xorriso_media_product(struct XorrisO *xorriso, int flag)
{
 int ret, profile_no;
 struct burn_drive_info *dinfo;
 struct burn_drive *drive;
 char *product_id= NULL, *media_code1= NULL, *media_code2= NULL;
 char *book_type= NULL, *manuf= NULL, profile_name[80], *respt;

 respt= xorriso->result_line;

 ret= Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                                "on attempt to print media product info",
                                 flag & (2 | 16));
 if(ret <= 0)
   return(ret);
 ret= burn_disc_get_media_id(drive, &product_id, &media_code1, &media_code2,
                             &book_type, 0);
 if(ret > 0) {
   ret= burn_disc_get_profile(drive, &profile_no, profile_name);
   if(ret <= 0)
     return(ret);
   sprintf(respt, "Media product: %s , ", product_id);
   manuf= burn_guess_manufacturer(profile_no, media_code1, media_code2, 0);
   if(manuf != NULL) {
     if(strncmp(manuf, "Unknown ", 8) == 0)
       sprintf(respt + strlen(respt), "(not found in manufacturer list)\n");
     else
       sprintf(respt + strlen(respt), "%s\n", manuf);
   } else
     sprintf(respt + strlen(respt), "(error during manufacturer lookup)\n");
   free(product_id);
   free(media_code1);
   free(media_code2);
   if(book_type != NULL)
     free(book_type);
   if(manuf != NULL)
     free(manuf);
   Xorriso_toc_line(xorriso, flag & 8);
 }
 Xorriso_process_msg_queues(xorriso,0);

 return(1);
}


/* @param flag bit0=short report form
               bit1=report about output drive
               bit2=do not try to read ISO heads
               bit3=report to info channel (else to result channel)
               bit4=do no report failure if no drive aquired
               bit5=only report "Drive current" and "Drive type"
               bit6=report "Media product" with bit0
               bit7=only report "Drive current"
*/
int Xorriso_toc(struct XorrisO *xorriso, int flag)
{
 int num_sessions= 0, num_tracks= 0, lba= 0, nwa= -1, pmin, psec, pframe, ret;
 int track_count= 0, session_no, track_no, profile_no= -1, track_size;
 int last_track_start= 0, last_track_size= -1, num_data= 0, is_data= 0;
 int is_inout_drive= 0, drive_role, status, num_formats, emul_lba;
 int num_payload= 0, num_wasted= 0, num_nondata= 0, not_reconizable= 0;
 char profile_name[80],*respt,*devadr, *typetext= "";
 struct burn_toc_entry toc_entry;
 struct burn_drive_info *dinfo;
 struct burn_drive *drive;
 enum burn_disc_status s;
 char mem_text[80];
 off_t start_byte= 0, num_free= 0, size;
 unsigned dummy;
 struct isoburn_toc_disc *disc= NULL;
 struct isoburn_toc_session **sessions;
 struct isoburn_toc_track **tracks;
 int image_blocks= 0;
 char volume_id[33];
 struct burn_toc_entry next_toc_entry;

 ret= Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                                "on attempt to print Table Of Content",
                                 flag & (2 | 16));
 if(ret<=0)
   {ret= 0; goto ex;}

 respt= xorriso->result_line;

 if(strcmp(xorriso->indev, xorriso->outdev)==0)
   is_inout_drive= 1;
 if(flag&2)
   devadr= xorriso->outdev;
 else
   devadr= xorriso->indev;
 sprintf(respt, "Drive current: %s '%s'\n",
         (is_inout_drive ? "-dev" : (flag&2 ? "-outdev" : "-indev")),
         devadr);
 Xorriso_toc_line(xorriso, flag & 8);
 if(flag & 128)
   {ret= 1; goto ex;}
 sprintf(respt, "Drive type   : vendor '%s' product '%s' revision '%s'\n",
        dinfo[0].vendor, dinfo[0].product, dinfo[0].revision);
 if((flag & 32) | !(flag & 1))
   Xorriso_toc_line(xorriso, flag & 8);
 if(flag & 32)
   {ret= 1; goto ex;}
 
 ret= burn_disc_get_profile(drive, &profile_no, profile_name);
 s= isoburn_disc_get_status(drive);
 if(profile_no == 0x0002 && s == BURN_DISC_EMPTY)
   profile_no= 0;
 sprintf(respt, "Media current: ");
 if (profile_no > 0 && ret > 0) {
   if (profile_name[0])
     sprintf(respt+strlen(respt), "%s", profile_name);
   else
     sprintf(respt+strlen(respt), "%4.4Xh", profile_no);
   drive_role= burn_drive_get_drive_role(drive);
   if(drive_role==2)
     sprintf(respt+strlen(respt), ", overwriteable");
   else if(drive_role==0 || drive_role==3)
     sprintf(respt+strlen(respt), ", sequential");
   strcat(respt, "\n");
 } else {
    sprintf(respt+strlen(respt), "is not recognizable\n");
    not_reconizable= 1;
 }
 Xorriso_toc_line(xorriso, flag & 8);

 if((flag & 64) || !(flag & 1)) {
   Xorriso_media_product(xorriso, flag & (2 | 8 | 16));
   if(xorriso->request_to_abort)
     {ret= 1; goto ex;}
 }

 sprintf(respt, "Media status : ");
 if (s == BURN_DISC_FULL) {
   if(not_reconizable)
     sprintf(respt+strlen(respt), "is not recognizable\n");
   else
     sprintf(respt+strlen(respt), "is written , is closed\n");
 } else if (s == BURN_DISC_APPENDABLE) {
   sprintf(respt+strlen(respt), "is written , is appendable\n");
 } else if (s == BURN_DISC_BLANK) {
   sprintf(respt+strlen(respt), "is blank\n");
 } else if (s == BURN_DISC_EMPTY)
   sprintf(respt+strlen(respt), "is not present\n");
 else
   sprintf(respt+strlen(respt), "is not recognizable\n");
 Xorriso_toc_line(xorriso, flag & 8);

 if(s == BURN_DISC_BLANK) {
   sprintf(respt, "Media summary: 0 sessions, 0 data blocks, 0 data");
   num_free= isoburn_disc_available_space(drive, NULL); 
   Sfile_scale((double) num_free, mem_text,5,1e4,1);
   sprintf(respt+strlen(respt), ", %s free\n", mem_text);
   Xorriso_toc_line(xorriso, flag & 8);
 }
 if(s != BURN_DISC_FULL && s != BURN_DISC_APPENDABLE)
   {ret= 1; goto ex;}
 if(xorriso->request_to_abort)
   {ret= 1; goto ex;}

 if(!(flag & 2))
   Xorriso_show_boot_info(xorriso, 1 | (flag & 8) | ((flag & 1) << 1));

 disc= isoburn_toc_drive_get_disc(drive);
 if(flag & 4)
   sprintf(respt, "TOC layout   : %3s , %9s , %10s\n",
           "Idx", "sbsector", "Size");
 else
   sprintf(respt, "TOC layout   : %3s , %9s , %10s , %s\n",
           "Idx", "sbsector", "Size", "Volume Id");
 if(!(flag&1))
   Xorriso_toc_line(xorriso, flag & 8);

 if (disc==NULL) {
   Xorriso_process_msg_queues(xorriso,0);
   ret= isoburn_get_min_start_byte(drive, &start_byte, 0);
   nwa= start_byte / 2048;
   if(ret<=0) {
     Xorriso_process_msg_queues(xorriso,0);
     if(flag&1)
       {ret= 0; goto ex;}
     sprintf(xorriso->info_text, "Cannot obtain Table Of Content");
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "SORRY", 0);
     {ret= 0; goto ex;}
   }

   /* fabricate TOC */
   typetext= "Other session";
   if(flag & 4) {
     ret= 0;
     typetext= "Session      ";
   } else
     ret= isoburn_read_iso_head(drive, 0, &image_blocks, volume_id, 1);
   if(ret>0) {
     sprintf(respt, "ISO session  : %3d , %9d , %9ds , %s\n",
             1, 0, image_blocks, volume_id);
     nwa= image_blocks;
   } else {
     nwa= 0;
     ret= burn_disc_get_formats(drive, &status, &size, &dummy,
                                &num_formats);
     if(ret>0 && status==BURN_FORMAT_IS_FORMATTED)
       nwa= size/2048;
     sprintf(respt, "%13s: %3d , %9d , %9ds , \n",
             typetext, 1, 0, nwa);
   } 
   if(!(flag&1))
     Xorriso_toc_line(xorriso, flag & 8);
   last_track_start= lba;
   num_payload= num_data= last_track_size= nwa;
   num_sessions= 1;
 } else {
   sessions= isoburn_toc_disc_get_sessions(disc, &num_sessions);
   for (session_no= 0; session_no<num_sessions && !(xorriso->request_to_abort);
        session_no++) {
     tracks= isoburn_toc_session_get_tracks(sessions[session_no], &num_tracks);
     if (tracks==NULL)
   continue;
     for(track_no= 0; track_no<num_tracks && !(xorriso->request_to_abort);
         track_no++) {
       track_count++;
       is_data= 0;
       isoburn_toc_track_get_entry(tracks[track_no], &toc_entry);
       if (toc_entry.extensions_valid & 1) {
         /* DVD extension valid */
         lba= toc_entry.start_lba;
         track_size= toc_entry.track_blocks;
       } else {
         lba= burn_msf_to_lba(toc_entry.pmin, toc_entry.psec,
                              toc_entry.pframe);
         if(track_no==num_tracks-1) {
           isoburn_toc_session_get_leadout_entry(sessions[session_no],
                                                 &next_toc_entry);
         } else {
           isoburn_toc_track_get_entry(tracks[track_no+1], &next_toc_entry);
         }
         track_size= burn_msf_to_lba(next_toc_entry.pmin, next_toc_entry.psec,
                                     next_toc_entry.pframe) - lba;
       }
       if(flag&(1|4))
         ret= 0;
       else {
         ret= isoburn_toc_track_get_emul(tracks[track_no], &emul_lba,
                                         &image_blocks, volume_id, 0);
         if(ret <= 0)
           ret= isoburn_read_iso_head(drive, lba, &image_blocks, volume_id, 1);
         if(image_blocks > track_size) {
           sprintf(xorriso->info_text,
              "Session %d bears ISO image size %ds larger than track size %ds",
              session_no + 1, image_blocks, track_size);
           Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "WARNING",
                               0);
           image_blocks= track_size;
         }
       }
       if(ret>0 && track_no==0) {
         sprintf(respt, "ISO session  : %3d , %9d , %9ds , %s\n",
                 session_no+1, lba, image_blocks , volume_id);
       } else if(ret>0) {
         sprintf(respt, "ISO track    : %3d , %9d , %9ds , %s\n",
                 track_count, lba, image_blocks , volume_id);
       } else if(track_no==0) {
         typetext= "Other session";
         if(flag & 4)
           typetext= "Session      ";
         sprintf(respt, "%13s: %3d , %9d , %9ds , \n",
                 typetext, session_no+1, lba, track_size);
       } else {
         typetext= "Other track  ";
         if(flag & 4)
           typetext= "Track        ";
         sprintf(respt, "%13s: %3d , %9d , %9ds , \n",
                 typetext, track_count, lba, track_size);
       } 
       if(!(flag&1))
         Xorriso_toc_line(xorriso, flag & 8);
       if(track_no>0)
         num_payload+= lba - last_track_start;
       last_track_start= lba;
       if((toc_entry.control&7)>=4) /* data track */
         is_data= 1;
     }
     isoburn_toc_session_get_leadout_entry(sessions[session_no], &toc_entry);
     if (toc_entry.extensions_valid & 1) {
       lba= toc_entry.start_lba;
       burn_lba_to_msf(lba, &pmin, &psec, &pframe);
     } else {
       lba= burn_msf_to_lba(pmin, psec, pframe);
       lba= burn_msf_to_lba(toc_entry.pmin, toc_entry.psec, toc_entry.pframe);
     }
     last_track_size= lba - last_track_start;
     num_payload+= last_track_size;
     if(is_data)
       num_data+= last_track_size;
   }
 }
 if(xorriso->request_to_abort)
   {ret= 1; goto ex;}
 num_wasted= lba - num_payload;
 num_nondata= lba - num_data;
 Sfile_scale(((double) num_data) * 2048.0, mem_text,5,1e4,1);

 sprintf(respt, "Media summary: %d session%s, %d data blocks, %s data",
         num_sessions, (num_sessions==1 ? "" : "s"), num_data, mem_text);
 num_free= isoburn_disc_available_space(drive, NULL); 
 Sfile_scale((double) num_free, mem_text,5,1e4,1);
 sprintf(respt+strlen(respt), ", %s free", mem_text);

 sprintf(respt+strlen(respt), "\n");
 Xorriso_toc_line(xorriso, flag & 8);

 if (s==BURN_DISC_APPENDABLE && nwa!=0) {
   ret= isoburn_disc_track_lba_nwa(drive, NULL, 0, &lba, &nwa);
   if(ret>0) {
     sprintf(respt, "Media nwa    : %ds\n", nwa);
     if(!(flag&1))
       Xorriso_toc_line(xorriso, flag & 8);
   }
 }

 if (disc!=NULL)
   isoburn_toc_disc_free(disc);
 Xorriso_process_msg_queues(xorriso,0);
 ret= 1;
ex:;
 return(ret);
}


int Xorriso_show_devices(struct XorrisO *xorriso, int flag)
{
 char adr[BURN_DRIVE_ADR_LEN];
 int i, j, max_dev_len= 1, pad;
 struct burn_drive_info *drive_list= NULL;
 unsigned int drive_count;
 char *respt, perms[8];
 struct stat stbuf;

 sprintf(xorriso->info_text, "Beginning to scan for devices ...\n");
 Xorriso_info(xorriso,0);

 burn_drive_clear_whitelist(); 
 while(!burn_drive_scan(&drive_list, &drive_count)) {
   Xorriso_process_msg_queues(xorriso,0);
   usleep(100000);
 }
 Xorriso_process_msg_queues(xorriso,0);
 if(drive_count <= 0) {

   /* >>> was a drive_list created at all ? */
   /* >>> must it be freed ? */

   sprintf(xorriso->info_text, "No drives found");
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "SORRY", 0);
   return(0);
 }
 sprintf(xorriso->info_text, "Full drive scan done\n");
 Xorriso_info(xorriso,0);

 sprintf(xorriso->info_text, "-----------------------------------------------------------------------------\n");
 Xorriso_info(xorriso,0);
 respt= xorriso->result_line;
 for(i= 0; i < drive_count && !(xorriso->request_to_abort); i++) {
   if(burn_drive_get_adr(&(drive_list[i]), adr)<=0)
     strcpy(adr, "-get_adr_failed-");
   Xorriso_process_msg_queues(xorriso,0);
   if(strlen(adr)>max_dev_len)
     max_dev_len= strlen(adr);
 }
 for(i= 0; i < drive_count && !(xorriso->request_to_abort); i++) {
   if(burn_drive_get_adr(&(drive_list[i]), adr)<=0)
     strcpy(adr, "-get_adr_failed-");
   Xorriso_process_msg_queues(xorriso,0);
   if(stat(adr,&stbuf)==-1) {
     sprintf(perms,"errno=%d",errno);
   } else { 
     strcpy(perms,"------");
     if(stbuf.st_mode&S_IRUSR) perms[0]= 'r';
     if(stbuf.st_mode&S_IWUSR) perms[1]= 'w';
     if(stbuf.st_mode&S_IRGRP) perms[2]= 'r';
     if(stbuf.st_mode&S_IWGRP) perms[3]= 'w';
     if(stbuf.st_mode&S_IROTH) perms[4]= 'r';
     if(stbuf.st_mode&S_IWOTH) perms[5]= 'w';
   }
   sprintf(respt, "%d  -dev '%s' ", i, adr);
   pad= max_dev_len-strlen(adr);
   if(pad>0)
     for(j= 0; j<pad; j++)
       strcat(respt, " ");
   sprintf(respt+strlen(respt), "%s :  '%-8.8s' '%s' \n",
           perms, drive_list[i].vendor, drive_list[i].product);
   Xorriso_result(xorriso,0);
 }
 sprintf(xorriso->info_text, "-----------------------------------------------------------------------------\n");
 Xorriso_info(xorriso,0);

 burn_drive_info_free(drive_list);
 Xorriso_process_msg_queues(xorriso,0);
 return(1);
}


int Xorriso_tell_media_space(struct XorrisO *xorriso,
                             int *media_space, int *free_space, int flag)
{
 int ret;
 struct burn_drive_info *dinfo;
 struct burn_drive *drive;
 struct burn_write_opts *burn_options;

 (*free_space)= (*media_space)= 0;

 ret= Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                                "on attempt to -tell_media_space", 2);
 if(ret<=0)
   return(0);

 ret= Xorriso_make_write_options(xorriso, drive, &burn_options, 0);
 if(ret<=0)
   return(-1);
 (*free_space)= (*media_space)=
              isoburn_disc_available_space(drive, burn_options) / (off_t) 2048;
 burn_write_opts_free(burn_options);

 if(xorriso->volset_change_pending) {
   ret= Xorriso_write_session(xorriso, 1);
   if(ret>0)
     (*free_space)-= ret;
 }
 Xorriso_process_msg_queues(xorriso,0);
 return(1);
}


/* @return <=0 error, 1 success
*/
int Xorriso_list_formats(struct XorrisO *xorriso, int flag)
{
 int ret, i, status, num_formats, profile_no, type, alloc_blocks, free_blocks;
 off_t size;
 unsigned dummy;
 char status_text[80], profile_name[90], *respt;
 struct burn_drive_info *dinfo;
 struct burn_drive *drive;

 respt= xorriso->result_line;

 ret= Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                         "on attempt to obtain format descriptor list", 1 | 2);
 if(ret<=0)
   return(0);
 if(ret == 2)
   goto ex;
 ret = burn_disc_get_formats(drive, &status, &size, &dummy,
                             &num_formats);
 if(ret<=0) {
   sprintf(xorriso->info_text, "Cannot obtain format list info");
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
   ret= 0; goto ex;
 }
 ret= Xorriso_toc(xorriso, 3);
 if(ret<=0)
   goto ex;
 ret= burn_disc_get_profile(drive, &profile_no, profile_name);
 if(ret<=0)
   goto ex;

 if(status == BURN_FORMAT_IS_UNFORMATTED)
   sprintf(status_text, "unformatted, up to %.1f MiB",
                        ((double) size) / 1024.0 / 1024.0);
 else if(status == BURN_FORMAT_IS_FORMATTED) {
   if(profile_no==0x12 || profile_no==0x13 || profile_no==0x1a ||
      profile_no==0x43)
     sprintf(status_text, "formatted, with %.1f MiB",
                         ((double) size) / 1024.0 / 1024.0);
   else
     sprintf(status_text, "written, with %.1f MiB",
                         ((double) size) / 1024.0 / 1024.0);
 } else if(status == BURN_FORMAT_IS_UNKNOWN) {
   if (profile_no > 0)
     sprintf(status_text, "intermediate or unknown");
   else
     sprintf(status_text, "no media or unknown media");
 } else
   sprintf(status_text, "illegal status according to MMC-5");
 sprintf(respt, "Format status: %s\n", status_text);
 Xorriso_result(xorriso,0);
 ret= burn_disc_get_bd_spare_info(drive, &alloc_blocks, &free_blocks, 0);
 if(ret == 1) {
   sprintf(respt, "BD Spare Area: %d blocks consumed, %d blocks available\n",
           alloc_blocks - free_blocks, free_blocks);
   Xorriso_result(xorriso,0);
 }

 for (i= 0; i < num_formats; i++) {
   ret= burn_disc_get_format_descr(drive, i, &type, &size, &dummy);
   if (ret <= 0)
 continue;
   sprintf(respt, "Format idx %-2d: %2.2Xh , %.fs , %.1f MiB\n",
          i, type, ((double) size) / 2048.0, ((double) size) / 1024.0/1024.0);
   Xorriso_result(xorriso,0);
 }
 ret= 1;
ex:;
 return(ret);
}


/* @param flag bit0= cdrecord style
               bit1= obtain outdrive, else indrive
   @return <=0 error, 1 success
*/
int Xorriso_list_profiles(struct XorrisO *xorriso, int flag)
{
 int ret, i;
 struct burn_drive_info *dinfo;
 struct burn_drive *drive;
 int num_profiles, profiles[64];
 char is_current[64], profile_name[90], *respt;

 respt= xorriso->result_line;

 ret= Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                          "on attempt to obtain profile list", 1 | (flag & 2));
 if(ret<=0)
   return(0);
 burn_drive_get_all_profiles(drive, &num_profiles, profiles, is_current);
 for(i= 0; i < num_profiles; i++) {
   ret= burn_obtain_profile_name(profiles[i], profile_name);
   if(ret <= 0)
     strcpy(profile_name, "unknown");
   sprintf(respt, "%s 0x%4.4X (%s)%s\n",
           flag & 1 ? "Profile:" : "Profile      :",
           (unsigned int) profiles[i],
           profile_name, is_current[i] ? " (current)" : "");
   Xorriso_result(xorriso,0);
 }
 return(1);
}


/* @param flag bit0= -inq
               bit1= -checkdrive
*/
int Xorriso_atip(struct XorrisO *xorriso, int flag)
{
 int ret, profile_number= 0;
 char *respt, profile_name[80];
 double x_speed_max, x_speed_min= -1.0;
 struct burn_drive_info *dinfo;
 struct burn_drive *drive;
 enum burn_disc_status s;
 char *manuf= NULL, *media_code1= NULL, *media_code2= NULL;
 char *book_type= NULL, *product_id= NULL;

 ret= Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                           "on attempt to print drive and media info", 2);
 if(ret<=0)
   return(0);
 respt= xorriso->result_line;
 sprintf(respt, "Device type    : ");
 ret= burn_drive_get_drive_role(drive);
 if(ret==0)
   sprintf(respt+strlen(respt), "%s\n", "Emulated (null-drive)");
 else if(ret==2)
   sprintf(respt+strlen(respt), "%s\n",
           "Emulated (stdio-drive, 2k random read-write)");
 else if(ret==3)
   sprintf(respt+strlen(respt), "%s\n",
           "Emulated (stdio-drive, sequential write-only)");
 else if(ret!=1)
   sprintf(respt+strlen(respt), "%s\n","Emulated (stdio-drive)");
 else
   sprintf(respt+strlen(respt), "%s\n","Removable CD-ROM");
 sprintf(respt+strlen(respt), "Vendor_info    : '%s'\n",dinfo->vendor);
 sprintf(respt+strlen(respt), "Identifikation : '%s'\n",dinfo->product);
 sprintf(respt+strlen(respt), "Revision       : '%s'\n",dinfo->revision);
 Xorriso_result(xorriso,1);
 if(flag&1)
   return(1);
 sprintf(respt, "Driver flags   : BURNFREE\n");
 sprintf(respt+strlen(respt), "Supported modes: SAO TAO\n");
 Xorriso_result(xorriso,1);
 if(flag&2)
   return(1);

 s= burn_disc_get_status(drive);
 ret= burn_disc_get_profile(drive,&profile_number,profile_name);
 if(ret<=0) {
   profile_number= 0;
   strcpy(profile_name, "-unidentified-");
 }
 if(s != BURN_DISC_UNSUITABLE) {
   ret= burn_disc_read_atip(drive);
   if(ret>0) {
     ret= burn_drive_get_min_write_speed(drive);
     x_speed_min= ((double) ret)/176.4;
   }
 }
 if(s==BURN_DISC_EMPTY) {
   sprintf(respt, "Current: none\n");
   Xorriso_result(xorriso,1);
   return(1);
 } else 
   sprintf(respt, "Current: %s\n",profile_name);
 Xorriso_result(xorriso,1);
 Xorriso_list_profiles(xorriso, 1 | 2);
 if(strstr(profile_name,"BD")==profile_name) {
   printf("Mounted Media: %2.2Xh, %s\n", profile_number, profile_name);
 } else if(strstr(profile_name,"DVD")==profile_name) {
   sprintf(respt, "book type:     %s (emulated booktype)\n", profile_name);
   Xorriso_result(xorriso,1);
   if(profile_number == 0x13) {
     sprintf(respt, "xorriso: message for sdvdbackup: \"(growisofs mode Restricted Overwrite)\"\n");
     Xorriso_result(xorriso,1);
   }
 } else {
   sprintf(respt, "ATIP info from disk:\n");
   Xorriso_result(xorriso,1);
   if(burn_disc_erasable(drive))
     sprintf(respt, "  Is erasable\n");
   else
     sprintf(respt, "  Is not erasable\n");
   Xorriso_result(xorriso,1);
   { int start_lba,end_lba,min,sec,fr;
     ret= burn_drive_get_start_end_lba(drive,&start_lba,&end_lba,0);
     if(ret>0) {
       burn_lba_to_msf(start_lba,&min,&sec,&fr);
       sprintf(respt, "  ATIP start of lead in:  %d (%-2.2d:%-2.2d/%-2.2d)\n",
              start_lba,min,sec,fr);
       Xorriso_result(xorriso,1);
       burn_lba_to_msf(end_lba,&min,&sec,&fr);
       sprintf(respt, "  ATIP start of lead out: %d (%-2.2d:%-2.2d/%-2.2d)\n",
              end_lba,min,sec,fr);
       Xorriso_result(xorriso,1);
     }
   }
   ret= burn_drive_get_write_speed(drive);
   x_speed_max= ((double) ret)/176.4;
   if(x_speed_min<0)
     x_speed_min= x_speed_max;
   sprintf(respt,
          "  1T speed low:  %.f 1T speed high: %.f\n",x_speed_min,x_speed_max);
   Xorriso_result(xorriso,1);
 }

 ret= burn_disc_get_media_id(drive, &product_id, &media_code1, &media_code2,
                                &book_type, 0);
 if(ret > 0 && media_code1 != NULL && media_code2 != NULL)
   manuf= burn_guess_manufacturer(profile_number, media_code1, media_code2, 0);
 if(product_id != NULL) {
   sprintf(respt, "Product Id:    %s\n", product_id);
   Xorriso_result(xorriso,1);
 }
 if(manuf != NULL) {
   sprintf(respt, "Producer:      %s\n", manuf);
   Xorriso_result(xorriso, 1);
 }
 if(profile_number == 0x09 || profile_number == 0x0a) {
   sprintf(respt, "Manufacturer: %s\n", manuf);
   Xorriso_result(xorriso, 1);
 } else if(product_id != NULL && media_code1 != NULL && media_code2 != NULL){
   free(product_id);
   free(media_code1);
   free(media_code2);
   if(book_type != NULL)
     free(book_type);
   product_id= media_code1= media_code2= book_type= NULL;
   ret= burn_disc_get_media_id(drive, &product_id, &media_code1, &media_code2,
                               &book_type, 1);
   if(ret > 0) {
     sprintf(respt, "Manufacturer:  '%s'\n", media_code1);
     Xorriso_result(xorriso, 1);
     if(media_code2[0]) {
       sprintf(respt, "Media type:    '%s'\n", media_code2);
       Xorriso_result(xorriso, 1);
     }
   }
 }
 if(manuf != NULL)
   free(manuf);
 if(media_code1 != NULL)
   free(media_code1);
 if(media_code2 != NULL)
   free(media_code2);
 if(book_type != NULL)
   free(book_type);
 if(product_id != NULL)
   free(product_id);
 return(1);
}


/* @param flag bit1= outdev rather than indev
   @return <0 error, 0 = no profile to see , 1= ok , 2= ok, is CD profile
                                                     3= ok, is BD profile
*/
int Xorriso_get_profile(struct XorrisO *xorriso, int *profile_number, 
                        char profile_name[80], int flag)
{
 int ret;
 struct burn_drive_info *dinfo;
 struct burn_drive *drive;

 *profile_number= 0;
 profile_name[0]= 0;
 if(((flag&2) && xorriso->out_drive_handle==NULL) ||
    ((!(flag&2)) && xorriso->in_drive_handle==NULL))
   return(0);
 ret= Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                                "on attempt to determine media type", flag&2);
 if(ret<=0)
   return(0);
 ret=burn_disc_get_profile(drive, profile_number, profile_name);
 if(ret<=0)
   return(ret);
 if(*profile_number==0x08 || *profile_number==0x09 || *profile_number==0x0a)
   return(2);
 if(*profile_number == 0x40 || *profile_number == 0x41 ||
    *profile_number == 0x42 || *profile_number == 0x43)
   return(3);
 return(0);
}


/* @param flag bit0= grow_overwriteable_iso
               bit1= obtain info from outdev
               bit2= no need to obtain msc2 (NWA)
*/
int Xorriso_msinfo(struct XorrisO *xorriso, int *msc1, int *msc2, int flag)
{
 int ret, dummy;
 struct burn_drive *drive;
 struct burn_drive_info *dinfo;
 enum burn_disc_status disc_state;

 *msc1= *msc2= -1;
 ret= Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                                "on attempt to obtain msinfo", flag&2);
 if(ret<=0)
   return(ret);

 if(flag&1)
   disc_state= isoburn_disc_get_status(drive);
 else
   disc_state= burn_disc_get_status(drive);
 if(disc_state != BURN_DISC_APPENDABLE &&
    !(disc_state == BURN_DISC_FULL && (flag & 4))) {
   Xorriso_process_msg_queues(xorriso,0);
   if(!(flag & 4)) {
     sprintf(xorriso->info_text,
             "%s media is not appendable. Cannot obtain -msinfo.",
             (flag&2) ? "Output" : "Input");
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
   }
   return(0);
 }
 ret= isoburn_disc_get_msc1(drive, msc1);
 if(ret<=0) {
   Xorriso_process_msg_queues(xorriso,0);
   sprintf(xorriso->info_text, "Cannot obtain address of most recent session");
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
   return(0);
 }
 if(flag & 4)
   return(1);
 ret= isoburn_disc_track_lba_nwa(drive, NULL, 0, &dummy, msc2);
 if(ret<0) {
   Xorriso_process_msg_queues(xorriso,0);
   sprintf(xorriso->info_text, "Cannot obtain next writeable address on media");
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
   return(0);
 }
 return(1);
}


/* @param flag bit0=input drive
               bit1=output drive
               bit2= wake up rather than calm down
*/
int Xorriso_drive_snooze(struct XorrisO *xorriso, int flag)
{
 int in_is_out_too, ret;
 struct burn_drive_info *dinfo;
 struct burn_drive *drive;
 
 in_is_out_too= (xorriso->in_drive_handle == xorriso->out_drive_handle);
 if((flag & 1) && xorriso->in_drive_handle != NULL) {
   Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                             "on attempt to calm drive", 0);
   burn_drive_snooze(drive, !!(flag & 4));
   if(in_is_out_too)
     {ret= 1; goto ex;}
 }
 if((flag&2) && xorriso->out_drive_handle!=NULL) {
   Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                             "on attempt to calm drive", 2);
   burn_drive_snooze(drive, !!(flag & 4));
 }
 ret= 1;
ex:;
 Xorriso_process_msg_queues(xorriso,0);
 return(ret);
}


/* @param flag bit0= enable SCSI command logging to stderr */
int Xorriso_scsi_log(struct XorrisO *xorriso, int flag)
{
 if(flag == 0)
   burn_set_scsi_logging(0);
 else
   burn_set_scsi_logging(2|4);
 return(1);
}


int Xorriso_check_md5_range(struct XorrisO *xorriso, off_t start_lba,
                            off_t end_lba, char md5[16], int flag)
{
 int ret;
 struct burn_drive_info *dinfo= NULL;
 struct burn_drive *drive= NULL;
 off_t pos, data_count, to_read;
 char data[64 * 1024], data_md5[16];
 void *ctx = NULL;

 ret= Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                                "on attempt to check session MD5 checksum", 0);
 if(ret <= 0)
   goto ex;
 ret= iso_md5_start(&ctx);
 if(ret <= 0) {
   Xorriso_no_malloc_memory(xorriso, NULL, 0);
   goto ex;
 }
 for(pos= start_lba; pos < end_lba; pos+= 32) {
   to_read= 32;
   if(pos + to_read > end_lba)
     to_read= end_lba - pos; 
   ret= burn_read_data(drive, pos * (off_t) 2048, data,
                       to_read * (off_t) 2048, &data_count, 0);
   if(ret <= 0)
     goto ex;
   iso_md5_compute(ctx, data, (int) data_count);
   xorriso->pacifier_count+= data_count; 
   xorriso->pacifier_byte_count+= data_count;
   Xorriso_pacifier_callback(xorriso, "content bytes read",
                             xorriso->pacifier_count, 0, "", 0);
 }
 iso_md5_end(&ctx, data_md5);
 ret= 1;
 if(! iso_md5_match(md5, data_md5))
   ret= 0;
ex:;
 Xorriso_process_msg_queues(xorriso,0);
 if(ctx != NULL)
   iso_md5_end(&ctx, data_md5);
 return(ret);
}


int Xorriso_check_session_md5(struct XorrisO *xorriso, char *severity,
                              int flag)
{
 int ret, i;
 IsoImage *image;
 uint32_t start_lba, end_lba;
 char md5[16], md5_text[33];

 ret= Xorriso_get_volume(xorriso, &image, 0);
 if(ret<=0)
   return(ret);
 ret= iso_image_get_session_md5(image, &start_lba, &end_lba, md5, 0);
 Xorriso_process_msg_queues(xorriso,0);
 if(ret < 0)
   return(ret);
 if(ret == 0) {
   sprintf(xorriso->info_text,
           "No session MD5 is recorded with the loaded session");
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "WARNING", 0);
   return(0);
 }

 sprintf(xorriso->info_text, "Checking loaded session by its recorded MD5.\n"); 
 Xorriso_info(xorriso, 0);
 for(i= 0; i < 16; i++)
   sprintf(md5_text + 2 * i, "%2.2x", ((unsigned char *) md5)[i]);
 sprintf(xorriso->result_line,
         "Session MD5 %s , LBA %.f , %.f blocks\n",
         md5_text, (double) start_lba, (double) end_lba - start_lba);
 Xorriso_result(xorriso,0);
 ret= Xorriso_check_md5_range(xorriso, (off_t) start_lba, (off_t) end_lba,
                              md5, 0);
 return(ret);
}

 
int Xorriso_check_for_abort(struct XorrisO *xorriso,
                            char *abort_file_path,
                            double post_read_time,
                            double *last_abort_file_time, int flag)
{
 struct stat stbuf;

 if(abort_file_path[0] == 0)
   return(0);
 if(post_read_time - *last_abort_file_time >= 0.1) {
   if(stat(abort_file_path, &stbuf) != -1) {
     if(stbuf.st_mtime >= xorriso->start_time) {
       sprintf(xorriso->info_text,
               "-check_media: Found fresh abort_file=%s", abort_file_path);
       Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0);
       return(1);
     }
   }
   *last_abort_file_time= post_read_time;
 }
 return(0);
}


/* @param flag bit0= this is a follow-up session (i.e. on CD: TAO)
               bit1= no pacifier messages
               bit2= compute stream MD5 and look out for checksum tag
   @return <=0 error, 1= done, 2= aborted due to limit
*/
int Xorriso_check_interval(struct XorrisO *xorriso, struct SpotlisT *spotlist,
                           struct CheckmediajoB *job,
                           int from_lba, int block_count, int read_chunk,
                           int md5_start, int flag)
{
 int i, j, ret, total_count= 0, sectors= -1, sector_size= -1, skip_reading;
 int prev_quality= -1, quality= -1, retry= 0, profile_no, is_cd= 0;
 int start_sec, end_sec, first_value, fret;
 char profile_name[80];
 int start_lba= 0;
 struct burn_drive *drive;
 struct burn_drive_info *dinfo;
 char data[64*1024], sfe[5*SfileadrL];
 off_t data_count, to_read, read_count= 0, write_amount;
 double pre_read_time, post_read_time, time_diff, total_time_diff= 0;
 double last_abort_file_time= 0;
 void *ctx= NULL, *cloned_ctx= NULL;
 char md5[16], tag_md5[16];
 uint32_t pos, range_start, range_size, next_tag= 0, lba, md5_spot_lba= 0;
 int md5_spot_value= Xorriso_read_quality_untesteD, chain_broken= 0;
 int tag_type= 0, valid, was_sb_tag= 0, in_track_gap= 0;
 char *comparison= "", *sev_text= "DEBUG", *tag_type_name= "";

 ret= Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                                "on attempt to check media readability",
                                2 * !!job->use_dev);
 if(ret<=0)
   goto ex;
 ret= burn_disc_get_profile(drive, &profile_no, profile_name);
 if(ret > 0)
   if(profile_no >= 0x08 && profile_no <= 0x0a)
     is_cd= 1;

 if(job->sector_map != NULL) {
   Sectorbitmap_get_layout(job->sector_map, &sectors, &sector_size, 0);
   sector_size/= 2048;
 }

 if(job->retry > 0)
   retry= 1;
 else if(job->retry == 0 && is_cd)
   retry= 1;

 if(flag & 4) {
   ret= iso_md5_start(&ctx);
   if(ret < 0) {
     Xorriso_no_malloc_memory(xorriso, NULL, 0);
     ret= -1; goto ex;
   }
 }

 start_lba= from_lba;
 to_read= read_chunk;
 post_read_time= Sfile_microtime(0);
 for(i= 0; i < block_count; i+= to_read) {
   skip_reading= 0;
   ret= Xorriso_check_for_abort(xorriso, job->abort_file_path, post_read_time,
                                &last_abort_file_time, 0);
   if(ret == 1)
     goto abort_check;
   if(job->item_limit > 0 &&
      Spotlist_count(spotlist, 0) + 2 >= job->item_limit) {
     sprintf(xorriso->info_text, "-check_media: Reached item_limit=%d",
             job->item_limit);
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0);
     goto abort_check;
   }
   pre_read_time= Sfile_microtime(0);
   if(job->time_limit > 0
	 && job->start_time + job->time_limit < pre_read_time) {
     sprintf(xorriso->info_text, "-check_media: Reached time_limit=%d",
             job->time_limit);
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0);
abort_check:;
     if(prev_quality >= 0) {
       ret= Spotlist_add_item(spotlist, start_lba, i + from_lba - start_lba,
                              prev_quality, 0);
       if(ret <= 0)
         goto ex;
     }
     ret= Spotlist_add_item(spotlist, i + from_lba, block_count - i,
                            Xorriso_read_quality_untesteD, 0);
     if(ret > 0)
       ret= 2;
     goto ex;
   }

   to_read= read_chunk;
   if(i + to_read > block_count)
     to_read= block_count - i;
   if(is_cd && i + to_read + 2 >= block_count) {
     /* Read last 2 blocks of CD track separately, because with TAO tracks
        they are always unreadable but with SAO tracks they contain data.
     */
     if(to_read > 2)
       to_read-= 2;
     else if(to_read > 1) {
       if(flag & 1) {
         quality= Xorriso_read_quality_tao_enD;
         skip_reading= 1;
       } else
         to_read--;
     }
   }

   if(sector_size == read_chunk && from_lba % read_chunk == 0 
      && !skip_reading) {
     if(Sectorbitmap_is_set(job->sector_map, (i + from_lba) / sector_size, 0)){
       quality= Xorriso_read_quality_valiD;
       skip_reading= 1;
     }
   } else if(sector_size > 0 && !skip_reading) {
     start_sec= (i + from_lba) / sector_size;
     end_sec= (i + to_read + from_lba) / sector_size;
     first_value= Sectorbitmap_is_set(job->sector_map, start_sec, 0);
     for(j= start_sec; j < end_sec; j++)
       if(Sectorbitmap_is_set(job->sector_map, j, 0) != first_value)
     break;
     to_read= j * sector_size - i - from_lba;
     skip_reading= !!first_value;
     if(skip_reading)
       quality= Xorriso_read_quality_valiD;
   }

   if(skip_reading) {
     pre_read_time= post_read_time= Sfile_microtime(0);
   } else {
     data_count= 0;
     pre_read_time= Sfile_microtime(0);
     ret= burn_read_data(drive, ((off_t) (i + from_lba)) * (off_t) 2048, data,
                         to_read * (off_t) 2048, &data_count, 4 * !retry);
     post_read_time= Sfile_microtime(0);
     time_diff= post_read_time - pre_read_time;
     total_time_diff+= time_diff;
     total_count++;
     if(ret <= 0) {
       Xorriso_process_msg_queues(xorriso,0);
       if(data_count / 2048 < to_read) {
         if(data_count > 0 && retry) {
           if(prev_quality >= 0) {
             ret= Spotlist_add_item(spotlist, start_lba,
                                    i + from_lba - start_lba, prev_quality, 0);
              if(ret <= 0)
                goto ex;
           }
           ret= Spotlist_add_item(spotlist, i + from_lba, data_count / 2048,
                                  Xorriso_read_quality_partiaL, 0);
           if(ret <= 0)
             goto ex;
           start_lba= i + from_lba + data_count / 2048;
           prev_quality= Xorriso_read_quality_unreadablE;
         }
         quality= Xorriso_read_quality_unreadablE;
         if(retry)
           to_read= data_count / 2048 + 1;
       } else
         quality= Xorriso_read_quality_partiaL;
       fret= Xorriso_eval_problem_status(xorriso, ret, 1|2);
       if(fret<0)
         goto ex;
     } else {
       quality= Xorriso_read_quality_gooD;
       if(time_diff > job->slow_threshold_seq && job->slow_threshold_seq > 0 &&
          i > 0)
         quality= Xorriso_read_quality_sloW;
     }

     /* MD5 checksumming */
     if(ctx != NULL) {
       for(j= 0; j < to_read; j++) {
         lba=  i + j + from_lba;
         if(lba < md5_start)
       continue;
         ret= 0;
         if(lba > md5_start + 16 &&
            (next_tag == 0 || chain_broken || lba == next_tag)) {
           ret= iso_util_decode_md5_tag(data + j * 2048, &tag_type,
                                        &pos, &range_start, &range_size,
                                        &next_tag, tag_md5, !!chain_broken);
         }
         valid= (ret == 1 || ret == ISO_MD5_AREA_CORRUPTED) && pos == lba;
         if(valid && tag_type == 2 && (lba < md5_start + 32 || in_track_gap)) {
           tag_type_name= "superblock";
           was_sb_tag= 1;
           if(in_track_gap && range_start != md5_start && range_start < lba &&
              lba - range_start <= j) {
             /* Looking for next session : start computing in hindsight.
                Session start and superblock tag are supposed to be in the
                same 64 kB chunk.
             */
             iso_md5_end(&ctx, md5);
             ret= iso_md5_start(&ctx);
             if(ret < 0) {
               Xorriso_no_malloc_memory(xorriso, NULL, 0);
               ret= -1; goto ex;
             }
             iso_md5_compute(&ctx, data + (j - (lba - range_start)) * 2048,
                             (lba - range_start) * 2048);
             md5_start= range_start;
             in_track_gap= 0;
           }
         } else if(valid && tag_type == 4 && lba < 32) {
           tag_type_name= "relocated 64kB superblock";
         }else if(valid && tag_type == 3 && was_sb_tag) {
           tag_type_name= "tree";
         }else if(valid && tag_type == 1) {

           /* >>> ??? allow this without superblock and tree tag ? */

           tag_type_name= "session";
         } else {
           tag_type_name= "";
         }
         if (tag_type_name[0]) {
           if(range_start != md5_start) {
             sprintf(xorriso->info_text,
                 "Found MD5 %s tag which covers different data range",
                 tag_type_name);
             Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE",0);
               sprintf(xorriso->info_text,
                       "              Expected: %u  Found: %u",
                       (unsigned int) md5_start, range_start);
             Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE",0);
             chain_broken= 1;
             valid= 0;
           } else {
             ret= iso_md5_clone(ctx, &cloned_ctx);
             if(ret <= 0) {
               Xorriso_no_malloc_memory(xorriso, NULL, 0);
               ret= -1; goto ex;
             }
             iso_md5_end(&cloned_ctx, md5);

             if(ret == ISO_MD5_AREA_CORRUPTED) {
               comparison= "CORRUPTED";
               sev_text= "WARNING";
               md5_spot_value= Xorriso_read_quality_md5_mismatcH;
               chain_broken= 1;
             } else if(! iso_md5_match(tag_md5, md5)) {
               comparison= "NON-MATCHING";
               sev_text= "WARNING";
               md5_spot_value= Xorriso_read_quality_md5_mismatcH;
               chain_broken= 1;
             } else {
               comparison= "matching";
               sev_text= "UPDATE";
               md5_spot_value= Xorriso_read_quality_md5_matcH;
             }
             md5_spot_lba= lba;
             sprintf(xorriso->info_text,
                     "Found %s MD5 %s tag: start=%d size=%d",
                     comparison, tag_type_name, md5_start, lba - md5_start);
             Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0,
                                 sev_text, 0);
           }
           if(valid && (tag_type == 1 || 
                        (tag_type == 4 && pos == lba && lba < 32))) {
             if(md5_spot_value != Xorriso_read_quality_untesteD) {
               ret= Spotlist_add_item(spotlist, md5_start,
                                  md5_spot_lba - md5_start, md5_spot_value, 0);
               if(ret <= 0)
               goto ex;
             }
             md5_spot_value= Xorriso_read_quality_untesteD;
             md5_start = lba + 1;
             if (md5_start % 32)
                 md5_start= md5_start + (32 - (md5_start % 32));
             next_tag= 0;

             iso_md5_end(&ctx, md5);
             ret= iso_md5_start(&ctx);
             if(ret < 0) {
               Xorriso_no_malloc_memory(xorriso, NULL, 0);
               ret= -1; goto ex;
             }
             if(tag_type == 1)
               in_track_gap= 1;
       continue;
           }
         }
         iso_md5_compute(ctx, data + j * 2048, 2048);
       }
     }

     write_amount= data_count;
     if(data_count > 0) {
       read_count+= data_count;
       if(job->data_to_limit >= 0 && read_count > job->data_to_limit)
         write_amount-= (read_count - job->data_to_limit);
     }
     if(write_amount > 0) {
       if(job->data_to_fd >= 0) {
         ret= lseek(job->data_to_fd,
                 ((off_t) (i + from_lba)) * (off_t) 2048 + job->data_to_offset,
                 SEEK_SET);
         if(ret == -1) {
failed_to_write:;
           sprintf(xorriso->info_text, "Cannot write %d bytes to lba %d of %s",
                   (int) data_count, i + from_lba,
                   Text_shellsafe(job->data_to_path, sfe, 0));
           Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, errno,
                               "FAILURE", 0);
           {ret= 0; goto ex;}
         }
         ret= write(job->data_to_fd, data, write_amount);
         if(ret == -1)
           goto failed_to_write;
       }
     }
   }
   if(quality != prev_quality) {
     if(prev_quality >= 0) {
       ret= Spotlist_add_item(spotlist, start_lba, i + from_lba - start_lba,
                              prev_quality, 0);
       if(ret <= 0)
         goto ex;
     }
     start_lba= i + from_lba;
     prev_quality= quality;
   }
   if(!(flag & 2)) {
     xorriso->pacifier_count+= to_read;
     if(post_read_time - xorriso->last_update_time >=
        xorriso->pacifier_interval)
       Xorriso_pacifier_callback(xorriso, "sectors examined",
                      xorriso->pacifier_count, xorriso->pacifier_total, "", 0);
   }
 }
 if(prev_quality >= 0) {
   ret= Spotlist_add_item(spotlist, start_lba,
                          block_count + from_lba - start_lba, prev_quality, 0);
   if(ret <= 0)
     goto ex;
 }

 /* <<< for calibration of quality */
 if(total_count > 0) {
   sprintf(xorriso->info_text, "Xorriso_check_interval: %.1f s / %d = %f",
           total_time_diff, total_count, total_time_diff / total_count);
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "DEBUG", 0);
 }


 /* MD5 checksumming : register result */

 /* >>> ??? allow chain_broken to be a match ? */

 if(next_tag > 0) {
   sprintf(xorriso->info_text, "Missing announced MD5 tag: start=%d pos=%d",
           md5_start, next_tag);
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "WARNING", 0);
   md5_spot_value= Xorriso_read_quality_md5_mismatcH;
   md5_spot_lba= next_tag;
 }
 if(md5_spot_value != Xorriso_read_quality_untesteD) {
   ret= Spotlist_add_item(spotlist, md5_start, md5_spot_lba - md5_start,
                          md5_spot_value, 0);
   if(ret <= 0)
     goto ex;
 }

 ret= 1;
ex:
 if(ctx != NULL)
   iso_md5_end(&ctx, md5);
 return(ret);
}


int Xorriso_check_media(struct XorrisO *xorriso, struct SpotlisT **spotlist,
                        struct CheckmediajoB *job, int flag)
{
 int media_blocks= 0, read_chunk= 16, ret, mode, start_lba= 0;
 int blocks, os_errno, i, j, last_track_end= -1, track_blocks, track_lba;
 int num_sessions, num_tracks, declare_untested= 0, md5_start;
 int read_capacity= -1, end_lba, hret, count, quality, profile_no;
 char *toc_info= NULL, profile_name[80];
 struct burn_drive *drive;
 struct burn_drive_info *dinfo;
 struct isoburn_toc_disc *isoburn_disc= NULL;
 struct isoburn_toc_session **isoburn_sessions;
 struct isoburn_toc_track **iso_burn_tracks;
 struct burn_toc_entry isoburn_entry;
 struct stat stbuf;
 struct burn_multi_caps *caps= NULL;

 *spotlist= NULL;

 ret= Xorriso_get_drive_handles(xorriso, &dinfo, &drive,
                                "on attempt to check media readability",
                                2 * !!job->use_dev);
 if(ret<=0)
   goto ex;
 
 /* >>> determine media type dependent blocking factor:
        32 kB for CD (with 2kB retry) and DVD, 64 kB for BD
        eventually adjust read_chunk
 */;
 if(job->min_block_size != 0)
   read_chunk= job->min_block_size;

 ret= Spotlist_new(spotlist, 0);
 if(ret <= 0)
   {ret= -1; goto ex;}

 if(job->sector_map_path[0]) {
   Sectorbitmap_destroy(&(job->sector_map), 0);
   if(stat(job->sector_map_path, &stbuf) != -1) {
     ret= Sectorbitmap_from_file(&(job->sector_map), job->sector_map_path,
                                 xorriso->info_text, &os_errno, 0);
     if(ret <= 0) {
       if(xorriso->info_text[0])
         Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, os_errno,
                             "FAILURE", 0);
       goto ex;
     }
   }
   Xorriso_toc_to_string(xorriso, &toc_info, 4 * !job->map_with_volid);
 }
 ret= Xorriso_open_job_data_to(xorriso, job, 0);
 if(ret <= 0)
   goto ex;
 Xorriso_pacifier_reset(xorriso, 0);
 job->start_time= time(NULL);
 mode= job->mode;
 if(job->min_lba > 0) {
   start_lba= job->min_lba;
   ret= Spotlist_add_item(*spotlist, 0, job->min_lba, 
                          Xorriso_read_quality_untesteD, 0);
   if(ret <= 0)
     goto ex;
 }
 ret= burn_get_read_capacity(drive, &read_capacity, 0);
 if(ret <= 0)
   read_capacity= -1;
 ret = burn_disc_get_profile(drive, &profile_no, profile_name);
 if(ret <= 0)
   profile_no= 0;

 if(job->max_lba >= 0) {
   blocks= job->max_lba + 1 - start_lba;
   xorriso->pacifier_total= blocks;
   ret= Xorriso_check_interval(xorriso, *spotlist, job, start_lba, blocks,
                               read_chunk, 0, 0);
   if(ret <= 0)
     goto ex;
   
 } else if(mode == 0) { /* track by track */
   isoburn_disc= isoburn_toc_drive_get_disc(drive);
   if(isoburn_disc == NULL)
     goto no_content_visible;
   isoburn_sessions=
                    isoburn_toc_disc_get_sessions(isoburn_disc, &num_sessions);
   for(i= 0; i < num_sessions; i++) {
     iso_burn_tracks= isoburn_toc_session_get_tracks(isoburn_sessions[i],
                                                     &num_tracks);
     for(j= 0; j < num_tracks; j++) {
       isoburn_toc_track_get_entry(iso_burn_tracks[j], &isoburn_entry);
       if(!(isoburn_entry.extensions_valid & 1)) /* should not happen */
     continue;
       track_lba= isoburn_entry.start_lba;
       track_blocks= isoburn_entry.track_blocks;

       /* The last track of an appendable BD-R reports more blocks than the
          read capacity allows. All BD-R track sizes are multiple of 64 kB.
       */
       if (i == num_sessions - 1 && 
           (track_lba + track_blocks > read_capacity &&
            track_lba + track_blocks < read_capacity + 32 &&
            (profile_no == 0x41 || profile_no == 0x40)))
         track_blocks= read_capacity - track_lba;

       md5_start= track_lba;
       if(i == 0 && j == 0) {
         if(track_lba == 32) {
           ret= burn_disc_get_multi_caps(drive, BURN_WRITE_NONE, &caps, 0);
           if(ret > 0) {
             if(caps->start_adr) {
               /* block 0 to 31 are the overall mount entry of overwriteable */
               track_lba= 0;
               track_blocks+= 32;
             }
           }
         }
       }
       if(last_track_end >= 0 && last_track_end < track_lba &&
          last_track_end >= start_lba) {
         ret= Spotlist_add_item(*spotlist, last_track_end,
                                track_lba - last_track_end,
                                Xorriso_read_quality_off_tracK, 0);
         if(ret <= 0)
           goto ex;
         xorriso->pacifier_count+= track_lba - last_track_end;
       }
       last_track_end= track_lba + track_blocks;

       if(track_lba < start_lba) {
         track_blocks-= start_lba - track_lba;
         track_lba= start_lba;
       }
       if(track_blocks <= 0)
     continue;
       if(declare_untested) {
         ret= Spotlist_add_item(*spotlist, track_lba, track_blocks,
                                Xorriso_read_quality_untesteD, 0);
         if(ret <= 0)
           goto ex;
       } else {
         ret= Xorriso_check_interval(xorriso, *spotlist, job, track_lba,
                                     track_blocks, read_chunk, md5_start,
                                     (i > 0) | (4 * (xorriso->do_md5 & 1)));
         if(ret <= 0)
           goto ex;
         if(ret == 2)
           declare_untested= 1;
       }
     }
   }

 } else if(mode == 1) { /* isoburn disc capacity */
   isoburn_disc= isoburn_toc_drive_get_disc(drive);
   if(isoburn_disc == NULL) {
no_content_visible:;
     Xorriso_process_msg_queues(xorriso,0);
     sprintf(xorriso->info_text, "No content detected on media");
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
     {ret= 0; goto ex;}
   }
   blocks= media_blocks= isoburn_toc_disc_get_sectors(isoburn_disc);
   if(start_lba >= 0)
     blocks-= start_lba;
   if(media_blocks <= 0)
     goto no_content_visible;
   xorriso->pacifier_total= blocks;
   ret= Xorriso_check_interval(xorriso, *spotlist, job, start_lba, blocks,
                               read_chunk, 0, 0);
   if(ret <= 0)
     goto ex;
 } else if(mode == 2) {

   /* >>> single sweep over libburn media capacity */;

 }

 Xorriso_pacifier_callback(xorriso, "sectors examined",
                      xorriso->pacifier_count, xorriso->pacifier_total, "", 1);
 ret= 1;
ex:;

 if(job->data_to_fd != -1)
   close(job->data_to_fd);
 job->data_to_fd= -1;
 
 if(read_capacity >= 0) {
   count= Spotlist_count(*spotlist, 0);
   end_lba= 0;
   for(i= 0; i < count; i++) {
     Spotlist_get_item(*spotlist, i, &start_lba, &blocks, &quality, 0);
     if(start_lba + blocks > end_lba)
       end_lba= start_lba + blocks;
   }
   if(read_capacity > end_lba) {
     hret= Spotlist_add_item(*spotlist, end_lba, read_capacity - end_lba, 
                             Xorriso_read_quality_untesteD, 0);
     if(hret < ret)
       ret= hret;
   }
 }

 if(ret > 0)
   ret= Xorriso_update_in_sector_map(xorriso, *spotlist, read_chunk, job, 0);
 
 if(ret > 0) {
   ret= Xorriso_spotlist_to_sectormap(xorriso, *spotlist, read_chunk,
                                    &(job->sector_map), !!job->untested_valid);
   if(ret > 0 && job->sector_map_path[0]) {
     ret= Sectorbitmap_to_file(job->sector_map, job->sector_map_path, toc_info,
                               xorriso->info_text, &os_errno, 0);
     if(ret <= 0) {
       if(xorriso->info_text[0])
         Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, os_errno,
                             "FAILURE", 0);
     }
   }
 }
 if(toc_info != NULL)
    free(toc_info);
 if(ret <= 0)
   Spotlist_destroy(spotlist, 0);
 if(caps!=NULL)
   burn_disc_free_multi_caps(&caps);
 return(ret);
}


/* @param flag
          bit0= if not MMC drive print NOTE and return 2  
          bit1= obtain outdrive, else indrive
          bit4= do not report failure
*/
int Xorriso_get_drive_handles(struct XorrisO *xorriso,
                              struct burn_drive_info **dinfo,
                              struct burn_drive **drive,
                              char *attempt, int flag)
{
 int ret;

 if(flag&2)
   *dinfo= (struct burn_drive_info *) xorriso->out_drive_handle;
 else
   *dinfo= (struct burn_drive_info *) xorriso->in_drive_handle;
 if(*dinfo==NULL && !(flag & 16)) {
   Xorriso_process_msg_queues(xorriso,0);
   sprintf(xorriso->info_text, "No %s drive aquired %s",
           (flag&2 ? "output" : "input"), attempt);
   Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "FAILURE", 0);
 }
 if(*dinfo==NULL)
   return(0);
 *drive= (*dinfo)[0].drive;
 if(flag & 1) {
   ret= burn_drive_get_drive_role(*drive);
   if(ret != 1) {
     sprintf(xorriso->info_text,
       "Output device is not an MMC drive. Desired operation does not apply.");
     Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0);
     return(2);
   }
 }
 return((*drive)!=NULL);
}

