
/* 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;
 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;
   }
   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;
 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 */
   }
 }
 state= isoburn_disc_get_status(drive);
 Xorriso_process_msg_queues(xorriso,0);
 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);

 /* 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));
 
 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;

 /* <<< can be removed as soon as libisofs-0.6.24 is mandatory
 */
 if(strcmp(un0(iso_image_get_copyright_file_id(volset)), "_") == 0 &&
    strcmp(un0(iso_image_get_abstract_file_id(volset)), "_") == 0 &&
    strcmp(un0(iso_image_get_biblio_file_id(volset)), "_") == 0) {
   /* This is bug output from libisofs <= 0.6.23 . The texts mean file names
      and should have been empty to indicate that there are no such files.
      It is obvious that not all three roles can be fulfilled by one file "_"
      so that one cannot spoil anything by assuming them empty now.
      Modern versions of libisofs are supposed to do this themselves.
    */
   iso_image_set_copyright_file_id(volset, "");
   iso_image_set_abstract_file_id(volset, "");
   iso_image_set_biblio_file_id(volset, "");
 }

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

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

/* @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;
 struct stat stbuf;
 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;

   if(job->abort_file_path[0]) {
     if(post_read_time - last_abort_file_time >= 0.1) {
       if(stat(job->abort_file_path, &stbuf) != -1) {
         if(stbuf.st_mtime >= xorriso->start_time) {
           sprintf(xorriso->info_text,
                   "-check_media: Found fresh abort_file=%s",
                   job->abort_file_path);
           Xorriso_msgs_submit(xorriso, 0, xorriso->info_text, 0, "NOTE", 0);
           goto abort_check;
         }
       }
       last_abort_file_time= post_read_time;
     }
   }
   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;
 char *toc_info= NULL;
 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;

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

