
/*
 cdrskin.c , Copyright 2006-2021 Thomas Schmitt <scdbackup@gmx.net>
Provided under GPL version 2 or later.

A cdrecord compatible command line interface for libburn.

This project is neither directed against original cdrecord nor does it exploit
any source code of said program. It rather tries to be an alternative method
to burn CD, DVD, or BD, which is not based on the same code as cdrecord.
See also :  http://scdbackup.sourceforge.net/cdrskin_eng.html

Interested users of cdrecord are encouraged to contribute further option
implementations as they need them. Contributions will get published under GPL
but it is essential that the authors allow a future release under LGPL.

There is a script test/cdrecord_spy.sh which may be installed between
the cdrecord command and real cdrecord in order to learn about the options
used by your favorite cdrecord frontend. Edit said script and install it
according to the instructions given inside.

The implementation of an option would probably consist of
- necessary structure members for structs CdrpreskiN and/or CdrskiN
- code in Cdrpreskin_setup() and  Cdrskin_setup() which converts
  argv[i] into CdrpreskiN/CdrskiN members (or into direct actions)
- removal of option from ignore list "ignored_partial_options" or
  "ignored_full_options" in Cdrskin_setup()
- functions which implement the option's run time functionality
- eventually calls of those functions in Cdrskin_run()
- changes to be made within Cdrskin_burn() or Cdrskin_blank() or other
  existing methods
See option blank= for an example.

------------------------------------------------------------------------------

For a more comprehensive example of the advised way to write an application
of libburn see  test/libburner.c .
 
------------------------------------------------------------------------------
This program is currently copyright Thomas Schmitt only.
The copyrights of several components of libburnia-project.org are willfully
tangled at toplevel to form an irrevocable commitment to true open source
spirit.
We have chosen the GPL for legal compatibility and clearly express that it
shall not hamper the use of our software by non-GPL applications which show
otherwise the due respect to the open source community.
See toplevel README and cdrskin/README for that commitment.

For a short time, this place showed a promise to release a BSD license on
mere request. I have to retract that promise now, and replace it by the
promise to make above commitment reality in a way that any BSD conformant
usage in due open source spirit will be made possible somehow and in the
particular special case. I will not raise public protest if you fork yourself
a BSD license from an (outdated) cdrskin.c which still bears that old promise.
Note that this extended commitment is valid only for cdrskin.[ch],
cdrfifo.[ch] and cleanup.[ch], but not for libburnia-project.org as a whole.

cdrskin is originally inspired by libburn-0.2/test/burniso.c :
(c) Derek Foreman <derek@signalmarketing.com> and Ben Jansens <xor@orodu.net>

------------------------------------------------------------------------------

Compilation within cdrskin-* :

  cd cdrskin
  cc -g -I.. -DCdrskin_build_timestamP='...' \
     -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE=1 \
     -o cdrskin cdrskin.c cdrfifo.c cleanup.c \
     -L../libburn/.libs -lburn -lpthread

or

  cd ..
  cc -g -I. -DCdrskin_build_timestamP='...' \
     -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE=1 \
     -o cdrskin/cdrskin cdrskin/cdrskin.c cdrskin/cdrfifo.c cdrskin/cleanup.c \
     libburn/async.o libburn/crc.o libburn/debug.o libburn/drive.o \
     libburn/file.o libburn/init.o libburn/lec.o \
     libburn/mmc.o libburn/options.o libburn/sbc.o libburn/sector.o \
     libburn/sg.o libburn/spc.o libburn/source.o libburn/structure.o \
     libburn/toc.o libburn/util.o libburn/write.o libburn/read.o \
     libburn/libdax_audioxtr.o libburn/libdax_msgs.o \
     -lpthread

*/


/** The official program version */
#ifndef Cdrskin_prog_versioN
#define Cdrskin_prog_versioN "1.5.4"
#endif

/** The official libburn interface revision to use.
    (May get changed further below)
*/
#ifndef Cdrskin_libburn_majoR
#define Cdrskin_libburn_majoR 1
#endif
#ifndef Cdrskin_libburn_minoR
#define Cdrskin_libburn_minoR 5
#endif
#ifndef Cdrskin_libburn_micrO
#define Cdrskin_libburn_micrO 4
#endif


/** The source code release timestamp */
#include "cdrskin_timestamp.h"
#ifndef Cdrskin_timestamP
#define Cdrskin_timestamP "-none-given-"
#endif

/** The binary build timestamp is to be set externally by the compiler */
#ifndef Cdrskin_build_timestamP
#define Cdrskin_build_timestamP "-none-given-"
#endif


#ifdef Cdrskin_libburn_versioN 
#undef Cdrskin_libburn_versioN 
#endif

#ifdef Cdrskin_libburn_1_5_4
#define Cdrskin_libburn_versioN "1.5.4"
#endif

#ifdef Cdrskin_libburn_1_5_5
#define Cdrskin_libburn_versioN "1.5.5"
#endif

#ifndef Cdrskin_libburn_versioN
#define Cdrskin_libburn_1_5_4
#define Cdrskin_libburn_versioN "1.5.4"
#endif

#ifdef Cdrskin_libburn_1_5_4
#undef Cdrskin_libburn_majoR
#undef Cdrskin_libburn_minoR
#undef Cdrskin_libburn_micrO
#define Cdrskin_libburn_majoR 1
#define Cdrskin_libburn_minoR 5
#define Cdrskin_libburn_micrO 4
#endif
#ifdef Cdrskin_libburn_1_5_5
#undef Cdrskin_libburn_majoR
#undef Cdrskin_libburn_minoR
#undef Cdrskin_libburn_micrO
#define Cdrskin_libburn_majoR 1
#define Cdrskin_libburn_minoR 5
#define Cdrskin_libburn_micrO 5
#endif


/* History of development macros.
   As of version 1.1.8 they are now unconditional, thus removing the option
   to compile a heavily restricted cdrskin for the old libburn at icculus.org.
*/

/* 0.2.2 */
/* Cdrskin_libburn_does_ejecT */
/* Cdrskin_libburn_has_drive_get_adR */
/* Cdrskin_progress_track_does_worK */
/* Cdrskin_is_erasable_on_load_does_worK */
/* Cdrskin_grab_abort_does_worK */

/* 0.2.4 */
/* Cdrskin_allow_libburn_taO */
/* Cdrskin_libburn_has_is_enumerablE */
/* Cdrskin_libburn_has_convert_fs_adR */
/* Cdrskin_libburn_has_convert_scsi_adR */
/* Cdrskin_libburn_has_burn_msgS */
/* Cdrskin_libburn_has_burn_aborT */
/* Cdrskin_libburn_has_cleanup_handleR */
/* Cdrskin_libburn_has_audioxtR */
/* Cdrskin_libburn_has_get_start_end_lbA */
/* Cdrskin_libburn_has_burn_disc_unsuitablE */
/* Cdrskin_libburn_has_read_atiP */
/* Cdrskin_libburn_has_buffer_progresS */

/* 0.2.6 */
/* Cdrskin_libburn_has_pretend_fulL */
/* Cdrskin_libburn_has_multI */
/* Cdrskin_libburn_has_buffer_min_filL */

/* 0.3.0 */
/* Cdrskin_atip_speed_is_oK */
/* Cdrskin_libburn_has_get_profilE */
/* Cdrskin_libburn_has_set_start_bytE */
/* Cdrskin_libburn_has_wrote_welL */
/* Cdrskin_libburn_has_bd_formattinG */
/* Cdrskin_libburn_has_burn_disc_formaT */

/* 0.3.2 */
/* Cdrskin_libburn_has_get_msc1 */
/* Cdrskin_libburn_has_toc_entry_extensionS */
/* Cdrskin_libburn_has_get_multi_capS */

/* 0.3.4 */
/* Cdrskin_libburn_has_set_filluP */
/* Cdrskin_libburn_has_get_spacE */
/* Cdrskin_libburn_write_mode_ruleS */
/* Cdrskin_libburn_has_allow_untested_profileS */
/* Cdrskin_libburn_has_set_forcE */

/* 0.3.6 */
/* Cdrskin_libburn_preset_device_familY */
/* Cdrskin_libburn_has_track_set_sizE */

/* 0.3.8 */
/* Cdrskin_libburn_has_set_waitinG */
/* Cdrskin_libburn_has_get_best_speeD */

/* 0.4.0 */
/* Cdrskin_libburn_has_random_access_rW */
/* Cdrskin_libburn_has_get_drive_rolE */
/* Cdrskin_libburn_has_drive_equals_adR */

/* 0.4.2 */
/* no novel libburn features but rather organizational changes */

/* 0.4.4 */
/* novel libburn features are transparent to cdrskin */

/* 0.4.6 */
/* Cdrskin_libburn_has_stream_recordinG */

/* 0.4.8 */
/* Bug fix release for  write_start_address=... on DVD-RAM and BD-RE */

/* 0.5.0 , 0.5.2 , 0.5.4 , 0.5.6 , 0.5.8 , 0.6.0 , 0.6.2 */
/* novel libburn features are transparent to cdrskin */

/* 0.6.4 */
/* Ended to mark novelties by macros.
   libburnia libburn and cdrskin are fixely in sync now.
   icculus libburn did not move for 30 months.
*/

/* 1.1.8 */
/* The code which got enabled by novelty macros was made unconditional.
*/


#ifdef Cdrskin_new_api_tesT

/* put macros under test caveat here */


#endif /* Cdrskin_new_api_tesT */


/** ts A90901
    The raw write modes of libburn depend in part on code borrowed from cdrdao.
    Since this code is not understood by the current developers and since CDs
    written with cdrskin -raw96r seem unreadable anyway, -raw96r is given up
    for now.
*/
#define Cdrskin_disable_raw96R 1 


/** A macro which is able to eat up a function call like printf() */
#ifdef Cdrskin_extra_leaN
#define ClN(x) 
#define Cdrskin_no_cdrfifO 1
#else
#define ClN(x) x
#ifdef Cdrskin_use_libburn_fifO
/* 
 # define Cdrskin_no_cdrfifO 1
*/
#endif
#endif

/** Verbosity level for pacifying progress messages */
#define Cdrskin_verbose_progresS 1

/** Verbosity level for command recognition and execution logging */
#define Cdrskin_verbose_cmD 2

/** Verbosity level for reporting of debugging messages */
#define Cdrskin_verbose_debuG 3

/** Verbosity level for fifo debugging */
#define Cdrskin_verbose_debug_fifO 4


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

#include "../libburn/libburn.h"


#define Cleanup_set_handlers burn_set_signal_handling
#define Cleanup_app_handler_T burn_abort_handler_t

/*
 # define Cdrskin_use_libburn_cleanuP 1
*/
/* May not use libburn cleanup with cdrskin fifo */
#ifndef Cdrskin_use_libburn_fifO
#ifdef Cdrskin_use_libburn_cleanuP
#undef Cdrskin_use_libburn_cleanuP
#endif
#endif

#ifdef Cdrskin_use_libburn_cleanuP
#define Cleanup_handler_funC   NULL
#define Cleanup_handler_handlE "cdrskin: "
#define Cleanup_handler_flaG 48
#else
#define Cleanup_handler_funC   (Cleanup_app_handler_T) Cdrskin_abort_handler
#define Cleanup_handler_handlE skin
#define Cleanup_handler_flaG 4
#endif /* ! Cdrskin_use_libburn_cleanuP */

/* 0= no abort going on, -1= Cdrskin_abort_handler was called
*/
static int Cdrskin_abort_leveL= 0;


/** The size of a string buffer for pathnames and similar texts */
#define Cdrskin_strleN 4096

/** The maximum length +1 of a drive address */
#define Cdrskin_adrleN BURN_DRIVE_ADR_LEN


/** If tsize= sets a value smaller than media capacity divided by this
    number then there will be a warning and gracetime set at least to 15 */
#define Cdrskin_minimum_tsize_quotienT 2048.0


/* --------------------------------------------------------------------- */

/* Imported from scdbackup-0.8.5/src/cd_backup_planer.c */

/** Macro for creation of arrays of objects (or single objects) */
#define TSOB_FELD(typ,anz) (typ *) calloc(anz, sizeof(typ));


/** Convert a text so that eventual characters special to the shell are
    made literal. Note: this does not make a text terminal-safe !
    @param in_text The text to be converted
    @param out_text The buffer for the result. 
                    It should have size >= strlen(in_text)*5+2
    @param flag Unused yet
    @return For convenience out_text is returned
*/
char *Text_shellsafe(char *in_text, char *out_text, int flag)
{
 int l,i,w=0;

 /* enclose everything by hard quotes */
 l= strlen(in_text);
 out_text[w++]= '\'';
 for(i=0;i<l;i++){
   if(in_text[i]=='\''){
     /* escape hard quote within the text */
     out_text[w++]= '\'';
     out_text[w++]= '"';
     out_text[w++]= '\'';
     out_text[w++]= '"';
     out_text[w++]= '\'';
   } else {
     out_text[w++]= in_text[i];
   }
 }
 out_text[w++]= '\'';
 out_text[w++]= 0;
 return(out_text);
}


/** Convert a text into a number of type double and multiply it by unit code
    [kmgtpe] (2^10 to 2^60) or [s] (2048). (Also accepts capital letters.)
    @param text Input like "42", "2k", "3.14m" or "-1g"
    @param flag Bitfield for control purposes:
                bit0= return -1 rathern than 0 on failure
    @return The derived double value
*/
double Scanf_io_size(char *text, int flag)
/*
 bit0= default value -1 rather than 0
*/
{
 int c;
 double ret= 0.0;

 if(flag&1)
   ret= -1.0;
 if(text[0]==0)
   return(ret);
 sscanf(text,"%lf",&ret);
 c= text[strlen(text)-1];
 if(c=='k' || c=='K') ret*= 1024.0;
 if(c=='m' || c=='M') ret*= 1024.0*1024.0;
 if(c=='g' || c=='G') ret*= 1024.0*1024.0*1024.0;
 if(c=='t' || c=='T') ret*= 1024.0*1024.0*1024.0*1024.0;
 if(c=='p' || c=='P') ret*= 1024.0*1024.0*1024.0*1024.0*1024.0;
 if(c=='e' || c=='E') ret*= 1024.0*1024.0*1024.0*1024.0*1024.0*1024.0;
 if(c=='s' || c=='S') ret*= 2048.0;
 return(ret);
}


/** Return a double representing seconds and microseconds since 1 Jan 1970 */
double Sfile_microtime(int flag)
{
 struct timeval tv;
 struct timezone tz;
 gettimeofday(&tv,&tz);
 return((double) (tv.tv_sec+1.0e-6*tv.tv_usec));
}


/** Read a line from fp and strip LF or CRLF */
char *Sfile_fgets(char *line, int maxl, FILE *fp)
{
int l;
char *ret;
 ret= fgets(line,maxl,fp);
 if(ret==NULL) return(NULL);
 l= strlen(line);
 if(l>0) if(line[l-1]=='\r') line[--l]= 0;
 if(l>0) if(line[l-1]=='\n') line[--l]= 0;
 if(l>0) if(line[l-1]=='\r') line[--l]= 0;
 return(ret);
}

#ifndef Cdrskin_extra_leaN


/** Destroy a synthetic argument array */
int Sfile_destroy_argv(int *argc, char ***argv, int flag)
{
 int i;

 if(*argc>0 && *argv!=NULL){
   for(i=0;i<*argc;i++){
     if((*argv)[i]!=NULL) 
       free((*argv)[i]);
   }
   free((char *) *argv);
 }
 *argc= 0;
 *argv= NULL;
 return(1);
}


/** Read a synthetic argument array from a list of files.
    @param progname The content for argv[0]
    @param filenames The paths of the filex from where to read
    @param filenamecount The number of paths in filenames
    @param argc Returns the number of read arguments (+1 for progname)
    @param argv Returns the array of synthetic arguments
    @param argidx Returns source file indice of argv[] items
    @param arglno Returns source file line numbers of argv[] items
    @param flag Bitfield for control purposes:
                bit0= read progname as first argument from line
                bit1= just release argument array argv and return
                bit2= tolerate failure to open file
    @return 1=ok , 0=cannot open file , -1=cannot create memory objects
*/
int Sfile_multi_read_argv(char *progname, char **filenames, int filename_count,
                          int *argc, char ***argv, int **argidx, int **arglno,
                          int flag)
{
 int ret,i,pass,maxl=0,l,argcount=0,line_no;
 char buf[Cdrskin_strleN];
 FILE *fp= NULL;

 Sfile_destroy_argv(argc,argv,0);
 if(flag&2)
   return(1);
 if((*argidx)!=NULL)
   free((char *) *argidx);
 if((*arglno)!=NULL)
   free((char *) *arglno);
 *argidx= *arglno= NULL;

 for(pass=0;pass<2;pass++) {
   if(!(flag&1)){
     argcount= 1;
     if(pass==0)
       maxl= strlen(progname)+1;
     else {
      (*argv)[0]= (char *) calloc(1, strlen(progname)+1);
      if((*argv)[0]==NULL)
         {ret= -1; goto ex;}
       strcpy((*argv)[0],progname);
     }
   } else {
     argcount= 0;
     if(pass==0)
       maxl= 1;
   }
   for(i=0; i<filename_count;i++) {
     if(strlen(filenames[i])==0)
   continue;
     fp= fopen(filenames[i],"rb");
     if(fp==NULL) {
       if(flag&4)
   continue;
       {ret= 0; goto ex;}
     }
     line_no= 0;
     while(Sfile_fgets(buf,sizeof(buf)-1,fp)!=NULL) {
       line_no++;
       l= strlen(buf);
       if(l==0 || buf[0]=='#')
     continue;
       if(pass==0){
         if(l>maxl) 
           maxl= l;
       } else {
         if(argcount >= *argc)
     break;
         (*argv)[argcount]= (char *) calloc(1, l+1);
         if((*argv)[argcount]==NULL)
           {ret= -1; goto ex;}
         strcpy((*argv)[argcount],buf);
         (*argidx)[argcount]= i;
         (*arglno)[argcount]= line_no;
       }
       argcount++;
     }
     fclose(fp); fp= NULL;
   }
   if(pass==0){
     *argc= argcount;
     if(argcount>0) {
       *argv= (char **) calloc(argcount, sizeof(char *));
       *argidx= (int *) calloc(argcount, sizeof(int));
       *arglno= (int *) calloc(argcount, sizeof(int));
       if(*argv==NULL || *argidx==NULL || *arglno==NULL)
         {ret= -1; goto ex;}
     }
     for(i=0;i<*argc;i++) {
       (*argv)[i]= NULL;
       (*argidx)[i]= -1;
       (*arglno)[i]= -1;
     }
   }     
 }

 ret= 1;
ex:;
 if(fp!=NULL)
   fclose(fp);
 return(ret);
}


/** Combine environment variable HOME with given filename
    @param filename Address relative to $HOME
    @param fileadr Resulting combined address
    @param fa_size Size of array fileadr
    @param flag Unused yet
    @return 1=ok , 0=no HOME variable , -1=result address too long
*/
int Sfile_home_adr_s(char *filename, char *fileadr, int fa_size, int flag)
{
 char *home;

 strcpy(fileadr,filename);
 home= getenv("HOME");
 if(home==NULL)
   return(0);
 if((int) (strlen(home) + strlen(filename) + 1) >= fa_size)
   return(-1);
 strcpy(fileadr,home);
 if(filename[0]!=0){
   strcat(fileadr,"/");
   strcat(fileadr,filename);
 }
 return(1);
}


#endif /* ! Cdrskin_extra_leaN */


/* -------------------------- other misc functions ----------------------- */


/* Learned from reading growisofs.c , 
   watching mkisofs, and viewing its results via od -c */
/* @return 0=no size found , 1=*size_in_bytes is valid */
int Scan_for_iso_size(unsigned char data[2048], double *size_in_bytes,
                      int flag)
{
 double sectors= 0.0;

 if(data[0]!=1)
   return(0);
 if(strncmp((char *) (data+1),"CD001",5)!=0)
   return(0);
 sectors=  data[80] | (data[81]<<8) | (data[82]<<16) | (data[83]<<24); 
 *size_in_bytes= sectors*2048.0;
 return(1);
}


int Set_descr_iso_size(unsigned char data[2048], double size_in_bytes,
                      int flag)
{
 unsigned int sectors, i;

 sectors= size_in_bytes/2048.0;
 if(size_in_bytes>((double) sectors) * 2048.0)
   sectors++;
 for(i=0;i<4;i++)
   data[87-i]= data[80+i]= (sectors >> (8*i)) & 0xff;
 return(1);
}


int Wait_for_input(int fd, int microsec, int flag)
{
 struct timeval wt;
 fd_set rds,wts,exs;
 int ready;

 FD_ZERO(&rds);
 FD_ZERO(&wts);
 FD_ZERO(&exs);
 FD_SET(fd,&rds); 
 FD_SET(fd,&exs); 
 wt.tv_sec=  microsec/1000000;
 wt.tv_usec= microsec%1000000;
 ready= select(fd+1,&rds,&wts,&exs,&wt);
 if(ready<=0)
   return(0);
 if(FD_ISSET(fd,&exs))
   return(-1);
 if(FD_ISSET(fd,&rds))
   return(1);
 return(0);
}


/* --------------------------------------------------------------------- */

/** Address translation table for users/applications which do not look
   for the output of -scanbus but guess a Bus,Target,Lun on their own.
*/

/** The maximum number of entries in the address translation table */
#define Cdradrtrn_leN 256

/** The address prefix which will prevent translation */
#define Cdrskin_no_transl_prefiX "LITERAL_ADR:"


struct CdradrtrN {
 char *from_address[Cdradrtrn_leN];
 char *to_address[Cdradrtrn_leN];
 int fill_counter;
};


#ifndef Cdrskin_extra_leaN

/** Create a device address translator object */
int Cdradrtrn_new(struct CdradrtrN **trn, int flag)
{
 struct CdradrtrN *o;
 int i;

 (*trn)= o= TSOB_FELD(struct CdradrtrN,1);
 if(o==NULL)
   return(-1);
 for(i= 0;i<Cdradrtrn_leN;i++) {
   o->from_address[i]= NULL;
   o->to_address[i]= NULL;
 }
 o->fill_counter= 0;
 return(1);
}


/** Release from memory a device address translator object */
int Cdradrtrn_destroy(struct CdradrtrN **o, int flag)
{
 int i;
 struct CdradrtrN *trn;
 
 trn= *o;
 if(trn==NULL)
   return(0);
 for(i= 0;i<trn->fill_counter;i++) {
   if(trn->from_address[i]!=NULL)
     free(trn->from_address[i]);
   if(trn->to_address[i]!=NULL)
     free(trn->to_address[i]);
 }
 free((char *) trn);
 *o= NULL;
 return(1);
}


/** Add a translation pair to the table 
    @param trn The translator which shall learn
    @param from The user side address
    @param to The cdrskin side address
    @param flag Bitfield for control purposes:
                bit0= "from" contains from+to address, to[0] contains delimiter
*/
int Cdradrtrn_add(struct CdradrtrN *trn, char *from, char *to, int flag)
{
 char buf[2*Cdrskin_adrleN+1],*from_pt,*to_pt;
 int cnt;

 cnt= trn->fill_counter;
 if(cnt>=Cdradrtrn_leN)
   return(-1);
 if(flag&1) {
   if(strlen(from)>=sizeof(buf))
     return(0);
   strcpy(buf,from);
   to_pt= strchr(buf,to[0]);
   if(to_pt==NULL)
     return(0);
   *(to_pt)= 0;
   from_pt= buf;
   to_pt++;
 } else {
   from_pt= from;
   to_pt= to;
 }
 if(strlen(from)>=Cdrskin_adrleN || strlen(to)>=Cdrskin_adrleN)
   return(0);
 trn->from_address[cnt]= calloc(1, strlen(from_pt)+1);
 trn->to_address[cnt]= calloc(1, strlen(to_pt)+1);
 if(trn->from_address[cnt]==NULL ||
    trn->to_address[cnt]==NULL)
   return(-2);
 strcpy(trn->from_address[cnt],from_pt);
 strcpy(trn->to_address[cnt],to_pt);
 trn->fill_counter++;
 return(1);
}


/** Apply eventual device address translation
    @param trn The translator 
    @param from The address from which to translate
    @param driveno With backward translation only: The libburn drive number
    @param to The result of the translation
    @param flag Bitfield for control purposes:
                bit0= translate backward
    @return <=0 error, 1=no translation found, 2=translation found,
            3=collision avoided
*/
int Cdradrtrn_translate(struct CdradrtrN *trn, char *from, int driveno,
                        char to[Cdrskin_adrleN], int flag)
{
 int i,ret= 1;
 char *adr;

 to[0]= 0;
 adr= from;
 if(flag&1)
   goto backward;

 if(strncmp(adr,Cdrskin_no_transl_prefiX,
            strlen(Cdrskin_no_transl_prefiX))==0) {
   adr= adr+strlen(Cdrskin_no_transl_prefiX);
   ret= 2;
 } else {
   for(i=0;i<trn->fill_counter;i++)
     if(strcmp(adr,trn->from_address[i])==0)
   break;
   if(i<trn->fill_counter) {
     adr= trn->to_address[i];
     ret= 2;
   }
 }
 if(strlen(adr)>=Cdrskin_adrleN)
   return(-1);
 strcpy(to,adr);
 return(ret);

backward:;
 if(strlen(from)>=Cdrskin_adrleN)
   sprintf(to,"%s%d",Cdrskin_no_transl_prefiX,driveno);
 else
   strcpy(to,from);
 for(i=0;i<trn->fill_counter;i++)
   if(strcmp(from,trn->to_address[i])==0 &&
      strlen(trn->from_address[i])<Cdrskin_adrleN)
 break;
 if(i<trn->fill_counter) {
   ret= 2;
   strcpy(to,trn->from_address[i]);
 } else {
   for(i=0;i<trn->fill_counter;i++)
     if(strcmp(from,trn->from_address[i])==0)
   break;
   if(i<trn->fill_counter)
     if(strlen(from)+strlen(Cdrskin_no_transl_prefiX)<Cdrskin_adrleN) {
       ret= 3;
       sprintf(to,"%s%s",Cdrskin_no_transl_prefiX,from);
     }
 }
 return(ret);
}

#endif /* Cdrskin_extra_leaN */


/* --------------------------------------------------------------------- */


#ifndef Cdrskin_no_cdrfifO

/* Program is to be linked with cdrfifo.c */
#include "cdrfifo.h"

#else /* ! Cdrskin_no_cdrfifO */

/* Dummy */

struct CdrfifO {
 int dummy;
};

#endif /* Cdrskin_no_cdrfifO */


/* --------------------------------------------------------------------- */

/** cdrecord pads up to 600 kB in any case. 
    libburn yields blank result on tracks <~ 600 kB
    cdrecord demands 300 sectors = 705600 bytes for -audio */
static double Cdrtrack_minimum_sizE= 300;


/** This structure represents a track and its data source */
struct CdrtracK {

 struct CdrskiN *boss;
 int trackno;
  
 char source_path[Cdrskin_strleN];
 char original_source_path[Cdrskin_strleN];
 int source_fd;
 int is_from_stdin;
 double fixed_size;
 double tao_to_sao_tsize;
 double padding;
 int set_by_padsize;
 int sector_pad_up; /* enforce single sector padding */
 int track_type;
 int mode_modifiers;
 double sector_size;
 int track_type_by_default;
 int swap_audio_bytes;
 int cdxa_conversion; /* bit0-30: for burn_track_set_cdxa_conv()
                         bit31  : ignore bits 0 to 30
                       */
 char isrc[13];

 char *index_string;

 int sao_pregap;
 int sao_postgap;

 /** Eventually detected data image size */
 double data_image_size;
 char *iso_fs_descr;  /* eventually block 16 to 31 of input during detection */
 /** Whether to demand a detected data image size and use it (or else abort) */
 int use_data_image_size; /* 0=no, 1=size not defined yet, 2=size defined */

 /* Whether the data source is a container of defined size with possible tail*/
 int extracting_container;

 /** Optional fifo between input fd and libburn. It uses a pipe(2) to transfer
     data to libburn. 
 */
 int fifo_enabled;

 /** The eventual own fifo object managed by this track object. */
 struct CdrfifO *fifo;

 /** fd[0] of the fifo pipe. This is from where libburn reads its data. */
 int fifo_outlet_fd;
 int fifo_size;

 /** The possibly external fifo object which knows the real input fd and
     the fd[1] of the pipe. */
 struct CdrfifO *ff_fifo;
 /** The index number if fifo follow up fd item, -1= own fifo */
 int ff_idx;

 struct burn_track *libburn_track;
 int libburn_track_is_own;

#ifdef Cdrskin_use_libburn_fifO
 struct burn_source *libburn_fifo;
#endif /* Cdrskin_use_libburn_fifO */

};

int Cdrtrack_destroy(struct CdrtracK **o, int flag);
int Cdrtrack_set_track_type(struct CdrtracK *o, int track_type, int flag);


/** Create a track and its data source object.
    @param track Returns the address of the new object.
    @param boss The cdrskin control object (corresponds to session)
    @param trackno The index in the cdrskin tracklist array (is not constant)
    @param flag Bitfield for control purposes:
                bit0= do not set parameters by Cdrskin_get_source()
                bit1= track is originally stdin
*/
int Cdrtrack_new(struct CdrtracK **track, struct CdrskiN *boss,
                 int trackno, int flag)
{
 struct CdrtracK *o;
 int ret,skin_track_type,fifo_start_at;
 int Cdrskin_get_source(struct CdrskiN *skin, char *source_path,
                        double *fixed_size, double *tao_to_sao_tsize,
                        int *use_data_image_size,
                        double *padding, int *set_by_padsize,
                        int *track_type, int *track_type_by_default,
                        int *mode_mods, int *swap_audio_bytes,
                        int *cdxa_conversion, int flag);
 int Cdrskin_get_fifo_par(struct CdrskiN *skin, int *fifo_enabled,
                          int *fifo_size, int *fifo_start_at, int flag);

 (*track)= o= TSOB_FELD(struct CdrtracK,1);
 if(o==NULL)
   return(-1);
 o->boss= boss;
 o->trackno= trackno;
 o->source_path[0]= 0;
 o->original_source_path[0]= 0;
 o->source_fd= -1;
 o->is_from_stdin= !!(flag&2);
 o->fixed_size= 0.0;
 o->tao_to_sao_tsize= 0.0;
 o->padding= 0.0;
 o->set_by_padsize= 0;
 o->sector_pad_up= 1;
 o->track_type= BURN_MODE1;
 o->mode_modifiers= 0;
 o->sector_size= 2048.0;
 o->track_type_by_default= 1;
 o->swap_audio_bytes= 0;
 o->cdxa_conversion= 0;
 o->isrc[0]= 0;
 o->index_string= NULL;
 o->sao_pregap= -1;
 o->sao_postgap= -1;
 o->data_image_size= -1.0;
 o->iso_fs_descr= NULL;
 o->use_data_image_size= 0;
 o->extracting_container= 0;
 o->fifo_enabled= 0;
 o->fifo= NULL;
 o->fifo_outlet_fd= -1;
 o->fifo_size= 0;
 o->ff_fifo= NULL;
 o->ff_idx= -1;
 o->libburn_track= NULL;
 o->libburn_track_is_own= 0;
#ifdef Cdrskin_use_libburn_fifO
 o->libburn_fifo= NULL;
#endif /* Cdrskin_use_libburn_fifO */

 if(flag & 1)
   return(1);

 ret= Cdrskin_get_source(boss,o->source_path,&(o->fixed_size),
                         &(o->tao_to_sao_tsize),&(o->use_data_image_size),
                         &(o->padding),&(o->set_by_padsize),&(skin_track_type),
                         &(o->track_type_by_default), &(o->mode_modifiers),
                         &(o->swap_audio_bytes), &(o->cdxa_conversion), 0);
 if(ret<=0)
   goto failed;
 strcpy(o->original_source_path,o->source_path);
 if(o->fixed_size>0.0)
   o->extracting_container= 1;
 Cdrtrack_set_track_type(o,skin_track_type,0);

#ifndef Cdrskin_extra_leaN
 ret= Cdrskin_get_fifo_par(boss, &(o->fifo_enabled),&(o->fifo_size),
                           &fifo_start_at,0);
 if(ret<=0)
   goto failed;
#endif /* ! Cdrskin_extra_leaN */

 return(1);
failed:;
 Cdrtrack_destroy(track,0);
 return(-1);
}


/** Release from memory a track object previously created by Cdrtrack_new() */
int Cdrtrack_destroy(struct CdrtracK **o, int flag)
{
 struct CdrtracK *track;

 track= *o;
 if(track==NULL)
   return(0);

#ifndef Cdrskin_no_cdrfifO
 Cdrfifo_destroy(&(track->fifo),0);
#endif

 if(track->libburn_track != NULL && track->libburn_track_is_own)
   burn_track_free(track->libburn_track);
 if(track->iso_fs_descr!=NULL)
   free((char *) track->iso_fs_descr);
 if(track->index_string != NULL)
   free(track->index_string);
 free((char *) track);
 *o= NULL;
 return(1);
}


int Cdrtrack_set_track_type(struct CdrtracK *o, int track_type, int flag)
{
 if(track_type==BURN_AUDIO) {
   o->track_type= BURN_AUDIO;
   o->sector_size= 2352.0;
 } else {
   o->track_type= BURN_MODE1;
   o->sector_size= 2048.0;
 }
 return(1);
}


int Cdrtrack_get_track_type(struct CdrtracK *o, int *track_type, 
                            int *sector_size, int flag)
{
 *track_type= o->track_type;
 *sector_size= o->sector_size;
 return(1);
}


/** 
    @param flag Bitfield for control purposes:
                bit0= size returns number of actually processed source bytes
                      rather than the predicted fixed_size (if available).
                      padding returns the difference from number of written
                      bytes.
                bit1= size returns fixed_size, padding returns tao_to_sao_tsize
*/
int Cdrtrack_get_size(struct CdrtracK *track, double *size, double *padding,
                      double *sector_size, int *use_data_image_size, int flag)
{
 off_t readcounter= 0,writecounter= 0;

 *size= track->fixed_size;
 *padding= track->padding;
 *use_data_image_size= track->use_data_image_size;
 if((flag&1) && track->libburn_track!=NULL) {
   burn_track_get_counters(track->libburn_track,&readcounter,&writecounter);
   *size= readcounter;
   *padding= writecounter-readcounter;
 } else if(flag&2)
   *padding= track->tao_to_sao_tsize;
 *sector_size= track->sector_size;
 return(1);
}


int Cdrtrack_get_iso_fs_descr(struct CdrtracK *track,
                              char **descr, double *size, int flag)
{
 *descr= track->iso_fs_descr;
 *size= track->data_image_size;
 return(*descr != NULL && *size > 0.0); 
}


int Cdrtrack_get_source_path(struct CdrtracK *track,
              char **source_path, int *source_fd, int *is_from_stdin, int flag)
{
 *source_path= track->original_source_path;
 *source_fd= track->source_fd;
 *is_from_stdin= track->is_from_stdin;
 return(1);
}


#ifdef Cdrskin_use_libburn_fifO

int Cdrtrack_get_libburn_fifo(struct CdrtracK *track,
                              struct burn_source **fifo, int flag)
{
 *fifo= track->libburn_fifo;
 return(1);
}


int Cdrtrack_report_fifo(struct CdrtracK *track, int flag)
{
 int size, free_bytes, ret;
 int total_min_fill, interval_min_fill, put_counter, get_counter;
 int empty_counter, full_counter;
 double fifo_percent;
 char *status_text;

 if(track->libburn_fifo == NULL)
   return(0);

 /* Check for open input or leftover bytes in liburn fifo */
 ret = burn_fifo_inquire_status(track->libburn_fifo, &size, &free_bytes,
                                &status_text);
 if(ret >= 0 && size - free_bytes > 1) {
                                 /* not clear why free_bytes is reduced by 1 */
   fprintf(stderr,
     "cdrskin: FATAL : Fifo still contains data after burning has ended.\n");
   fprintf(stderr,
     "cdrskin: FATAL : %d bytes left.\n", size - free_bytes - 1);
   fprintf(stderr,
     "cdrskin: FATAL : This indicates an overflow of the last track.\n");
   fprintf(stderr,
     "cdrskin: NOTE : The media might appear ok but is probably truncated.\n");
   return(-1);
 }

 burn_fifo_get_statistics(track->libburn_fifo, &total_min_fill,
                          &interval_min_fill, &put_counter, &get_counter,
                          &empty_counter, &full_counter);
 fifo_percent= 100.0*((double) total_min_fill)/(double) size;
 if(fifo_percent==0 && total_min_fill>0)
   fifo_percent= 1;
 fflush(stdout);
 fprintf(stderr,"Cdrskin: fifo had %d puts and %d gets.\n",
         put_counter,get_counter);
 fprintf(stderr,
   "Cdrskin: fifo was %d times empty and %d times full, min fill was %.f%%.\n",
             empty_counter, full_counter, fifo_percent);
 return(1);
}

#endif /* Cdrskin_use_libburn_fifO */


int Cdrtrack_get_fifo(struct CdrtracK *track, struct CdrfifO **fifo, int flag)
{
 *fifo= track->fifo;
 return(1);
}


/** Try whether automatic audio extraction is appropriate and eventually open
    a file descriptor to the raw data.
    @return -3 identified as .wav but with cdrecord-inappropriate parameters
            -2 could not open track source, no use in retrying
            -1 severe error
             0 not appropriate to extract, burn plain file content
             1 to be extracted, *fd is a filedescriptor delivering raw data
*/
int Cdrtrack_extract_audio(struct CdrtracK *track, int *fd, off_t *xtr_size,
                           int flag)
{
 int l, ok= 0;
 struct libdax_audioxtr *xtr= NULL;
 char *fmt,*fmt_info;
 int num_channels,sample_rate,bits_per_sample,msb_first,ret;

 *fd= -1;

 if(track->track_type!=BURN_AUDIO && !track->track_type_by_default)
   return(0);
 l= strlen(track->source_path);
 if(l>=4)
   if(strcmp(track->source_path+l-4,".wav")==0) 
     ok= 1;
 if(l>=3)
   if(strcmp(track->source_path+l-3,".au")==0)
     ok= 1;
 if(!ok)
   return(0);

 if(track->track_type_by_default) {
   Cdrtrack_set_track_type(track,BURN_AUDIO,0);
   track->track_type_by_default= 2;
   fprintf(stderr,"cdrskin: NOTE : Activated -audio for '%s'\n",
           track->source_path);
 }

 ret= libdax_audioxtr_new(&xtr,track->source_path,0);
 if(ret<=0)
   return(ret);
 libdax_audioxtr_get_id(xtr,&fmt,&fmt_info,
                     &num_channels,&sample_rate,&bits_per_sample,&msb_first,0);
 if((strcmp(fmt,".wav")!=0 && strcmp(fmt,".au")!=0) || 
    num_channels!=2 || sample_rate!=44100 || bits_per_sample!=16) {
   fprintf(stderr,"cdrskin: ( %s )\n",fmt_info);
   fprintf(stderr,"cdrskin: FATAL : Inappropriate audio coding in '%s'.\n",
                  track->source_path);
   {ret= -3; goto ex;}
 }
 libdax_audioxtr_get_size(xtr,xtr_size,0);
 ret= libdax_audioxtr_detach_fd(xtr,fd,0);
 if(ret<=0)
   {ret= -1*!!ret; goto ex;}
 track->swap_audio_bytes= !!msb_first;
 track->extracting_container= 1;
 fprintf(stderr,"cdrskin: NOTE : %.f %saudio bytes in '%s'\n",
                (double) *xtr_size, (msb_first ? "" : "(-swab) "),
                track->source_path);
 ret= 1;
ex:
 libdax_audioxtr_destroy(&xtr,0);
 return(ret);
}


/* @param flag bit0=set *size_used as the detected data image size
*/
int Cdrtrack_activate_image_size(struct CdrtracK *track, double *size_used,
                                 int flag)
{
 if(flag&1)
   track->data_image_size= *size_used;
 else
   *size_used= track->data_image_size;
 if(track->use_data_image_size!=1)
   return(2);
 if(*size_used<=0)
   return(0);
 track->fixed_size= *size_used;
 track->use_data_image_size= 2;
 if(track->libburn_track!=NULL)
   burn_track_set_size(track->libburn_track, (off_t) *size_used);
 /* man cdrecord prescribes automatic -pad with -isosize. 
    cdrskin obeys only if the current padding is less than that. */
 if(track->padding<15*2048) {
   track->padding= 15*2048;
   track->set_by_padsize= 0;
 }
 track->extracting_container= 1;

#ifndef Cdrskin_no_cdrfifO
 if(track->ff_fifo!=NULL)
   Cdrfifo_set_fd_in_limit(track->ff_fifo,track->fixed_size,track->ff_idx,0);
#endif

 return(1);
}


int Cdrtrack_seek_isosize(struct CdrtracK *track, int fd, int flag)
{
 struct stat stbuf;
 char secbuf[2048];
 int ret,got,i;
 double size;

 if(fstat(fd,&stbuf)==-1)
   return(0);
 if((stbuf.st_mode&S_IFMT)!=S_IFREG && (stbuf.st_mode&S_IFMT)!=S_IFBLK)
   return(2);

 if(track->iso_fs_descr!=NULL)
   free((char *) track->iso_fs_descr);
 track->iso_fs_descr= TSOB_FELD(char,16*2048);
 if(track->iso_fs_descr==NULL)
   return(-1);
 for(i=0;i<32 && track->data_image_size<=0;i++) {
   for(got= 0; got<2048;got+= ret) {
     ret= read(fd, secbuf+got, 2048-got);
     if(ret<=0)
       return(0);
   }
   if(i<16)
 continue;
   memcpy(track->iso_fs_descr+(i-16)*2048,secbuf,2048);
   if(i>16)
 continue;
   ret= Scan_for_iso_size((unsigned char *) secbuf, &size, 0);
   if(ret<=0)
 break;
   track->data_image_size= size;
   if(track->use_data_image_size) {
     Cdrtrack_activate_image_size(track,&size,1);
     track->fixed_size= size;
     track->use_data_image_size= 2;
   }
 }
 ret= lseek(fd, (off_t) 0, SEEK_SET);
 if(ret!=0) {
   fprintf(stderr,
        "cdrskin: FATAL : Cannot lseek() to 0 after -isosize determination\n");
   if(errno!=0)
     fprintf(stderr, "cdrskin: errno=%d : %s\n", errno, strerror(errno));
   return(-1);
 }
 return(track->data_image_size>0);
}


/** Deliver an open file descriptor corresponding to the source path of track.
    @param flag Bitfield for control purposes:
                bit0=debugging verbosity
                bit1=open as source for direct write: 
                     no audio extract, no minimum track size
                bit2=permission to use burn_os_open_track_src() (evtl O_DIRECT)
                bit3=This is a CD. Enforce minimum track size if not bit1.
    @return <=0 error, 1 success
*/
int Cdrtrack_open_source_path(struct CdrtracK *track, int *fd, int flag)
{
 int is_wav= 0, size_from_file= 0, ret, self_opened= 0;
 off_t xtr_size= 0;
 struct stat stbuf;
 char *device_adr,*raw_adr;
 int no_convert_fs_adr;
 int Cdrskin_get_device_adr(struct CdrskiN *skin,
           char **device_adr, char **raw_adr, int *no_convert_fs_adr,int flag);
 int Cdrskin_get_drive(struct CdrskiN *skin, struct burn_drive **drive,
           int flag);
 struct burn_drive *drive;

 if(track->source_path[0]=='-' && track->source_path[1]==0)
   *fd= 0;
 else if(track->source_path[0]=='#' && 
         (track->source_path[1]>='0' && track->source_path[1]<='9'))
   *fd= atoi(track->source_path+1);
 else {
   *fd= -1;
   self_opened= 1;

   ret= Cdrskin_get_device_adr(track->boss,&device_adr,&raw_adr,
                               &no_convert_fs_adr,0);
   if(ret <= 0) {
     fprintf(stderr,
             "cdrskin: FATAL : No drive found. Cannot prepare track.\n");
     return(0);
   }
/*    
   fprintf(stderr,
           "cdrskin: DEBUG : device_adr='%s' , raw_adr='%s' , ncfs=%d\n",
           device_adr, raw_adr, no_convert_fs_adr);
*/
   if(!no_convert_fs_adr) {
     if(flag&1)
       ClN(fprintf(stderr,
          "cdrskin_debug: checking track source for identity with drive\n"));

     ret= Cdrskin_get_drive(track->boss,&drive,0);
     if(ret<=0) {
       fprintf(stderr,
          "cdrskin: FATAL : Program error. Cannot determine libburn drive.\n");
       return(0);
     }
     if(burn_drive_equals_adr(drive,track->source_path,2)>0) {
       fprintf(stderr,
              "cdrskin: FATAL : track source address leads to burner drive\n");
       fprintf(stderr,
              "cdrskin:       : dev='%s' -> '%s' <- track source '%s'\n",
              raw_adr, device_adr, track->source_path);
       return(0);
     }
   }
/*
   fprintf(stderr,"cdrskin: EXPERIMENTAL : Deliberate abort\n");
   return(0);
*/

   if(!(flag&2))
     is_wav= Cdrtrack_extract_audio(track,fd,&xtr_size,0);
   if(is_wav==-1)
     return(-1);
   if(is_wav==-3)
     return(0);
   if(is_wav==0) {
     if(track->track_type != BURN_MODE1 ||
        (track->cdxa_conversion & 0x7fffffff))
       flag&= ~4;                  /* Better avoid O_DIRECT with odd sectors */
     if(flag & 4)
       *fd= burn_os_open_track_src(track->source_path, O_RDONLY, 0);
     else
       *fd= open(track->source_path, O_RDONLY);
   }
   if(*fd==-1) {
     fprintf(stderr,"cdrskin: failed to open source address '%s'\n",
             track->source_path);
     fprintf(stderr,"cdrskin: errno=%d , \"%s\"\n",errno,
                    errno==0?"-no error code available-":strerror(errno));
     return(0);
   }
   if(track->use_data_image_size==1 && xtr_size<=0) {
     ret= Cdrtrack_seek_isosize(track,*fd,0);
     if(ret == -1) {
       close(*fd);
       *fd= -1;
       return(-1);
     }
   } else if(track->fixed_size<=0) {

     /* >>> ??? is it intentional that tsize overrides .wav header ? */
     if(xtr_size>0) {

       track->fixed_size= xtr_size;
       if(track->use_data_image_size==1)
         track->use_data_image_size= 2; /* count this as image size found */
       size_from_file= 1;
     } else {
       if(fstat(*fd,&stbuf)!=-1) {
         if((stbuf.st_mode&S_IFMT)==S_IFREG) {
           track->fixed_size= stbuf.st_size;
           size_from_file= 1;
         } /* all other types are assumed of open ended size */
       }
     }
   }
 }

 track->source_fd= *fd;
 if(track->fixed_size < Cdrtrack_minimum_sizE * track->sector_size
    && (track->fixed_size>0 || size_from_file) && (flag & 8) && !(flag&2)) {
   if(track->track_type == BURN_AUDIO) {
     /* >>> cdrecord: We differ in automatic padding with audio:
       Audio tracks must be at least 705600 bytes and a multiple of 2352.
     */
     fprintf(stderr,
             "cdrskin: FATAL : Audio tracks must be at least %.f bytes\n",
             Cdrtrack_minimum_sizE*track->sector_size);
     return(0);
   } else {
     fprintf(stderr,
             "cdrskin: NOTE : Enforcing minimum track size of %.f bytes\n",
             Cdrtrack_minimum_sizE*track->sector_size);
     track->fixed_size= Cdrtrack_minimum_sizE*track->sector_size;
   }
 }
 return((*fd >= 0) * (1 + self_opened));
}


#ifndef Cdrskin_no_cdrfifO

/** Install a fifo object between data source and libburn.
    Its parameters are known to track.
    @param outlet_fd Returns the filedescriptor of the fifo outlet.
    @param previous_fifo Object address for chaining or follow-up attachment.
    @param flag Bitfield for control purposes:
                bit0= Debugging verbosity
                bit1= Do not create and attach a new fifo 
                      but attach new follow-up fd pair to previous_fifo
                bit2= Do not enforce fixed_size if not container extraction
                bit3= This is a CD. Enforce minimum track size.
                      (Forward to Cdrtrack_open_source_path)
    @return <=0 error, 1 success
*/
int Cdrtrack_attach_fifo(struct CdrtracK *track, int *outlet_fd, 
                         struct CdrfifO *previous_fifo, int flag)
{
 struct CdrfifO *ff;
 int source_fd,pipe_fds[2],ret;

 *outlet_fd= -1;
 if(track->fifo_size<=0)
   return(2);
 ret= Cdrtrack_open_source_path(track,&source_fd,
                    (flag & (1 | 8)) | (4 * (track->fifo_size >= 256 * 1024)));
 if(ret<=0)
   return(ret);
 if(pipe(pipe_fds)==-1)
   return(0);

 Cdrfifo_destroy(&(track->fifo),0);
 if(flag&2) {
   ret= Cdrfifo_attach_follow_up_fds(previous_fifo,source_fd,pipe_fds[1],0);
   if(ret<=0)
     return(ret);
   track->ff_fifo= previous_fifo;
   track->ff_idx= ret-1;
 } else {

   /* >>> ??? obtain track sector size and use instead of 2048 ? */

   ret= Cdrfifo_new(&ff,source_fd,pipe_fds[1],2048,track->fifo_size, flag & 1);
   if(ret<=0)
     return(ret);
   if(previous_fifo!=NULL)
     Cdrfifo_attach_peer(previous_fifo,ff,0);
   track->fifo= track->ff_fifo= ff;
   track->ff_idx= -1;
 }
 track->fifo_outlet_fd= pipe_fds[0];

 if((track->extracting_container || !(flag&4)) && track->fixed_size>0)
   Cdrfifo_set_fd_in_limit(track->ff_fifo,track->fixed_size,track->ff_idx,0);

 if(flag&1)
   printf(
        "cdrskin_debug: track %d fifo replaced source_address '%s' by '#%d'\n",
        track->trackno+1,track->source_path,track->fifo_outlet_fd);
 sprintf(track->source_path,"#%d",track->fifo_outlet_fd);
 track->source_fd= track->fifo_outlet_fd;
 *outlet_fd= track->fifo_outlet_fd;
 return(1);
}

#endif /* ! Cdrskin_no_cdrfifO */


#ifndef Cdrskin_extra_leaN

#ifdef Cdrskin_use_libburn_fifO

/** Read data into the eventual libburn fifo until either fifo_start_at bytes
    are read (-1 = no limit), it is full or or the data source is exhausted.
    @return <=0 error, 1 success
*/
int Cdrtrack_fill_libburn_fifo(struct CdrtracK *track, int fifo_start_at,
                               int flag)
{
 int ret, bs= 32 * 1024;
 int buffer_size, buffer_free;
 double data_image_size;
 char buf[64 * 1024], *buffer_text;

 if(fifo_start_at == 0)
   return(2);
 if(track->libburn_fifo == NULL)
   return(2);

 if(fifo_start_at>0 && fifo_start_at<track->fifo_size)
   printf(
      "cdrskin: NOTE : Input buffer will be initially filled up to %d bytes\n",
      fifo_start_at);
 printf("Waiting for reader process to fill input buffer ... ");
 fflush(stdout);
 ret= burn_fifo_fill(track->libburn_fifo, fifo_start_at,
                     (fifo_start_at == -1));
 if(ret < 0)
   return(0);

/** Ticket 55: check fifos for input, throw error on 0-bytes from stdin
    @return <=0 abort run, 1 go on with burning
*/
 ret= burn_fifo_inquire_status(track->libburn_fifo, &buffer_size,
                               &buffer_free, &buffer_text);
 if(track->is_from_stdin) {
   if(ret<0 || buffer_size <= buffer_free) {
     fprintf(stderr,"\ncdrskin: FATAL : (First track) fifo did not read a single byte from stdin\n");
     return(0);
   }
 }

 /* Try to obtain ISO 9660 Volume Descriptors and size from fifo.
    Not an error if there is no ISO 9660. */
 if(track->iso_fs_descr != NULL)
   free(track->iso_fs_descr);
 track->iso_fs_descr = NULL;
 if(buffer_size - buffer_free >= 64 * 1024) {
   ret= burn_fifo_peek_data(track->libburn_fifo, buf, 64 * 1024, 0);
   if(ret == 1) {
     track->iso_fs_descr = calloc(1, bs);
     if(track->iso_fs_descr == NULL)
       return(-1);
     memcpy(track->iso_fs_descr, buf + bs, bs);
     ret= Scan_for_iso_size((unsigned char *) buf + bs, &data_image_size, 0);
     if(ret > 0)
       track->data_image_size= data_image_size;
   }
 }
 return(1);
}

#endif /* Cdrskin_use_libburn_fifO */


#ifdef Cdrskin_no_cdrfifO

int Cdrtrack_fill_fifo(struct CdrtracK *track, int fifo_start_at, int flag)
{
	return(Cdrtrack_fill_libburn_fifo(track, fifo_start_at, 0));
}

#else /* Cdrskin_no_cdrfifO */

int Cdrtrack_fill_fifo(struct CdrtracK *track, int fifo_start_at, int flag)
{
 int ret,buffer_fill,buffer_space;
 double data_image_size;

 if(fifo_start_at==0)
   return(2);
 if(track->fifo==NULL) {
#ifdef Cdrskin_use_libburn_fifO
   ret= Cdrtrack_fill_libburn_fifo(track, fifo_start_at, 0);
   return(ret);
#else
   return(2);
#endif
 }
 if(fifo_start_at>0 && fifo_start_at<track->fifo_size)
   printf(
      "cdrskin: NOTE : Input buffer will be initially filled up to %d bytes\n",
      fifo_start_at);
 printf("Waiting for reader process to fill input buffer ... ");
 fflush(stdout);
 ret= Cdrfifo_fill(track->fifo,fifo_start_at,0);
 if(ret<=0)
   return(ret);

/** Ticket 55: check fifos for input, throw error on 0-bytes from stdin
    @return <=0 abort run, 1 go on with burning
*/
 if(track->is_from_stdin) {
   ret= Cdrfifo_get_buffer_state(track->fifo,&buffer_fill,&buffer_space,0);
   if(ret<0 || buffer_fill<=0) {
     fprintf(stderr,"\ncdrskin: FATAL : (First track) fifo did not read a single byte from stdin\n");
     return(0);
   }
 }
 ret= Cdrfifo_get_iso_fs_size(track->fifo,&data_image_size,0);
 if(ret>0)
   track->data_image_size= data_image_size;
 if(track->iso_fs_descr!=NULL)
   free((char *) track->iso_fs_descr);
 Cdrfifo_adopt_iso_fs_descr(track->fifo,&(track->iso_fs_descr),0);
 return(1);
}

#endif /* ! Cdrskin_no_cdrfifO */
#endif /* ! Cdrskin_extra_leaN */


int Cdrtrack_set_indice(struct CdrtracK *track, int flag)
{
 int idx= 1, adr, prev_adr= -1, ret;
 char *cpt, *ept;

 if(track->sao_pregap >= 0) {
   ret= burn_track_set_pregap_size(track->libburn_track, track->sao_pregap, 0);
   if(ret <= 0)
     return(ret);
 }
 if(track->sao_postgap >= 0) {
   ret= burn_track_set_postgap_size(track->libburn_track, track->sao_postgap,
                                    0);
   if(ret <= 0)
     return(ret);
 }

 if(track->index_string == NULL)
   return(2);

 for(ept= cpt= track->index_string; ept != NULL; cpt= ept + 1) {
     ept= strchr(cpt, ',');
     if(ept != NULL)
       *ept= 0;
     adr= -1;
     sscanf(cpt, "%d", &adr);
     if(adr < 0) {
       fprintf(stderr, "cdrskin: SORRY : Bad address number with index=\n");
       return(0);
     }
     if(idx == 1 && adr != 0) {
       fprintf(stderr,
               "cdrskin: SORRY : First address number of index= is not 0\n");
       return(0);
     }
     if(idx > 1 && adr < prev_adr) {
       fprintf(stderr,
               "cdrskin: SORRY : Backward address number with index=\n");
       return(0);
     }
     ret= burn_track_set_index(track->libburn_track, idx, adr, 0);
     if(ret <= 0)
       return(ret);
     prev_adr= adr;
     if(ept != NULL)
       *ept= ',';
     idx++;
 }

 return(1);
}     

/** Create a corresponding libburn track object and add it to the libburn
    session. This may change the trackno index set by Cdrtrack_new().
*/
int Cdrtrack_add_to_session(struct CdrtracK *track, int trackno,
                            struct burn_session *session, int flag)
/*
 bit0= debugging verbosity
 bit1= apply padding hack (<<< should be unused for now)
 bit2= permission to use O_DIRECT (if enabled at compile time)
 bit3= This is a CD. (Forward to Cdrtrack_open_source_path)
*/
{
 struct burn_track *tr;
 struct burn_source *src= NULL;
 double padding,lib_padding;
 int ret,sector_pad_up;
 double fixed_size;
 int source_fd;

#ifdef Cdrskin_use_libburn_fifO
 struct burn_source *fd_src= NULL;
#endif

 track->trackno= trackno;
 tr= burn_track_create();
 if(tr == NULL)
   {ret= -1; goto ex;}
 track->libburn_track= tr;
 track->libburn_track_is_own= 1;

 /* Note: track->track_type may get set in here */
 if(track->source_fd==-1) {
   ret= Cdrtrack_open_source_path(track, &source_fd, flag & (8 | 4 | 1));
   if(ret<=0)
     goto ex;
 }

 padding= 0.0;
 sector_pad_up= track->sector_pad_up;
 if(track->padding>0) {
   if(track->set_by_padsize || track->track_type!=BURN_AUDIO)
     padding= track->padding;
   else
     sector_pad_up= 1;
 }
 if(flag&2)
   lib_padding= 0.0;
 else
   lib_padding= padding;
 if(flag&1) {
   if(sector_pad_up) {
     ClN(fprintf(stderr,"cdrskin_debug: track %d telling burn_track_define_data() to pad up last sector\n",trackno+1));
   }
   if(lib_padding>0 || !sector_pad_up) {
     ClN(fprintf(stderr,
 "cdrskin_debug: track %d telling burn_track_define_data() to pad %.f bytes\n",
           trackno+1,lib_padding));
   }
 }
 burn_track_define_data(tr,0,(int) lib_padding,sector_pad_up,
                        track->track_type | track->mode_modifiers);
 burn_track_set_default_size(tr, (off_t) track->tao_to_sao_tsize);
 burn_track_set_byte_swap(tr,
                   (track->track_type==BURN_AUDIO && track->swap_audio_bytes));
 if(!(track->cdxa_conversion & (1 << 31)))
   burn_track_set_cdxa_conv(tr, track->cdxa_conversion & 0x7fffffff);

 fixed_size= track->fixed_size;
 if((flag&2) && track->padding>0) {
   if(flag&1)
     ClN(fprintf(stderr,"cdrskin_debug: padding hack : %.f + %.f = %.f\n",
                 track->fixed_size,track->padding,
                 track->fixed_size+track->padding));
   fixed_size+= track->padding;
 }
 src= burn_fd_source_new(track->source_fd,-1,(off_t) fixed_size);

#ifdef Cdrskin_use_libburn_fifO

 if(src != NULL && track->fifo == NULL) {
   int fifo_enabled, fifo_size, fifo_start_at, chunksize, chunks;
   int Cdrskin_get_fifo_par(struct CdrskiN *skin, int *fifo_enabled,
                            int *fifo_size, int *fifo_start_at, int flag);

   Cdrskin_get_fifo_par(track->boss, &fifo_enabled, &fifo_size, &fifo_start_at,
                        0);

   if(track->track_type == BURN_AUDIO)
     chunksize= 2352;
   else if (track->cdxa_conversion == 1)
     chunksize= 2056;
   else
     chunksize= 2048;
   chunks= fifo_size / chunksize;
   if(chunks > 1 && fifo_enabled) {
     fd_src= src;
     src= burn_fifo_source_new(fd_src, chunksize, chunks,
                               (chunksize * chunks >= 128 * 1024));
     if((flag & 1) || src == NULL)
       fprintf(stderr, "cdrskin_DEBUG: %s libburn fifo of %d bytes\n",
               src != NULL ? "installed" : "failed to install",
               chunksize * chunks);
     track->libburn_fifo= src;
     if(src == NULL) {
       src= fd_src;
       fd_src= NULL;
     }
   }
 }

#endif /* Cdrskin_use_libburn_fifO */

 if(src==NULL) {
   fprintf(stderr,
           "cdrskin: FATAL : Could not create libburn data source object\n");
   {ret= 0; goto ex;}
 }
 if(burn_track_set_source(tr,src)!=BURN_SOURCE_OK) {
   fprintf(stderr,"cdrskin: FATAL : libburn rejects data source object\n");
   {ret= 0; goto ex;}
 }
 ret= Cdrtrack_set_indice(track, 0);
 if(ret <= 0)
   goto ex;

 burn_session_add_track(session,tr,BURN_POS_END);
 ret= 1;
ex:

#ifdef Cdrskin_use_libburn_fifO
 if(fd_src!=NULL)
   burn_source_free(fd_src);
#endif

 if(src!=NULL)
   burn_source_free(src);
 return(ret);
}


/** Release libburn track information after a session is done */
int Cdrtrack_cleanup(struct CdrtracK *track, int flag)
{
 if(track->libburn_track==NULL)
   return(0);
 if(track->libburn_track_is_own)
   burn_track_free(track->libburn_track);
 track->libburn_track= NULL;
 return(1);
}


int Cdrtrack_ensure_padding(struct CdrtracK *track, int flag)
/*
flag:
 bit0= debugging verbosity
*/
{
 if(track->track_type!=BURN_AUDIO)
   return(2);
 if(flag&1)
   fprintf(stderr,"cdrskin_debug: enforcing -pad on last -audio track\n");
 track->sector_pad_up= 1;
 return(1);
}


int Cdrtrack_get_sectors(struct CdrtracK *track, int flag)
{
 return(burn_track_get_sectors(track->libburn_track));
}


#ifndef Cdrskin_no_cdrfifO

/** Try to read bytes from the track's fifo outlet and eventually discard
    them. Not to be called unless the track is completely written.
*/
int Cdrtrack_has_input_left(struct CdrtracK *track, int flag)
{
 int ready,ret;
 char buf[2];

 if(track->fifo_outlet_fd<=0)
   return(0);
 ready= Wait_for_input(track->fifo_outlet_fd, 0, 0);
 if(ready<=0)
   return(0);
 ret= read(track->fifo_outlet_fd,buf,1);
 if(ret>0)
   return(1);
 return(0);
}

#endif /* ! Cdrskin_no_cdrfifO */
 

/* --------------------------------------------------------------------- */

/** The list of startup file names */
#define Cdrpreskin_rc_nuM 4

static char Cdrpreskin_sys_rc_nameS[Cdrpreskin_rc_nuM][80]= {
 "/etc/default/cdrskin",
 "/etc/opt/cdrskin/rc",
 "/etc/cdrskin/cdrskin.conf",
 "placeholder for $HOME/.cdrskinrc"
};


/** A structure which bundles several parameters for creation of the CdrskiN
    object. It finally becomes a managed subordinate of the CdrskiN object. 
*/
struct CdrpreskiN {

 /* to be transferred into skin */
 int verbosity;
 char queue_severity[81];
 char print_severity[81];

 /** Whether to wait for available standard input data before touching drives*/
 int do_waiti;

 /** Stores eventually given absolute device address before translation */
 char raw_device_adr[Cdrskin_adrleN];

 /** Stores an eventually given translated absolute device address between
    Cdrpreskin_setup() and Cdrskin_create() .
 */
 char device_adr[Cdrskin_adrleN];

 /** The eventual address translation table */
 struct CdradrtrN *adr_trn;

 /** Memorizes the abort handling mode from presetup to creation of 
     control object. Defined handling modes are:
     0= no abort handling
     1= try to cancel, release, exit (leave signal mode as set by caller)
     2= try to ignore all signals
     3= mode 1 in normal operation, mode 2 during abort handling
     4= mode 1 in normal operation, mode 0 during abort handling
    -1= install abort handling 1 only in Cdrskin_burn() after burning started
 */
 int abort_handler;

 /** Whether to allow getuid()!=geteuid() */
 int allow_setuid;

 /** Whether to allow user provided addresses like #4 */
 int allow_fd_source;

 /** Whether to support media types which are implemented but yet untested */
 int allow_untested_media;

 /** Whether to allow libburn pseudo-drives "stdio:<path>" .
     0=forbidden, 1=seems ok,
     2=potentially forbidden (depends on uid, euid, file type)
 */
 int allow_emulated_drives;

 /** Whether an option is given which needs a full bus scan */
 int no_whitelist;

 /** Whether the translated device address shall not follow softlinks, device
     clones and SCSI addresses */
 int no_convert_fs_adr;

 /** Whether Bus,Target,Lun addresses shall be converted literally as old
     Pseudo SCSI-Adresses. New default is to use (possibly system emulated)
     real SCSI addresses via burn_drive_convert_scsi_adr() and literally
     emulated and cdrecord-incompatible ATA: addresses. */
 int old_pseudo_scsi_adr;

 /** Whether to omit bus scanning */
 int do_not_scan;

 /** Whether bus scans shall exit!=0 if no drive was found */
 int scan_demands_drive;

 /** Whether to abort when a busy drive is encountered during bus scan */
 int abort_on_busy_drive;

 /** Linux specific : Whether to try to avoid collisions when opening drives */
 int drive_exclusive;

 /** Linux specific : Whether to obtain an exclusive drive lock via fcntl() */
 int drive_fcntl_f_setlk;

 /** Linux specific : Device file address family to use :
     0=default , 1=sr , 2=scd , 4=sg */
 int drive_scsi_dev_family;
 

 /** Whether to try to wait for unwilling drives to become willing to open */
 int drive_blocking;

 /** Explicit write mode option is determined before skin processes
     any track arguments */
 char write_mode_name[80];

#ifndef Cdrskin_extra_leaN

 /** List of startupfiles */
 char rc_filenames[Cdrpreskin_rc_nuM][Cdrskin_strleN];
 int rc_filename_count;

 /** Non-argument options from startupfiles */
 int pre_argc;
 char **pre_argv;
 int *pre_argidx;
 int *pre_arglno;

#endif /* ! Cdrskin_extra_leaN */

 /* The eventual name of a program to be executed if demands_cdrecord_caps
    is >0 and demands_cdrskin_caps is <=0
 */
 char fallback_program[Cdrskin_strleN];
 int demands_cdrecord_caps;
 int demands_cdrskin_caps;

 int result_fd;

};


/** Create a preliminary cdrskin program run control object. It will become
    part of the final control object.
    @param preskin Returns pointer to resulting
    @param flag Bitfield for control purposes: unused yet
    @return <=0 error, 1 success
*/
int Cdrpreskin_new(struct CdrpreskiN **preskin, int flag)
{
 struct CdrpreskiN *o;
 int i;

 (*preskin)= o= TSOB_FELD(struct CdrpreskiN,1);
 if(o==NULL)
   return(-1);

 o->verbosity= 0;
 strcpy(o->queue_severity,"NEVER");
 strcpy(o->print_severity,"SORRY");
 o->do_waiti= 0;
 o->raw_device_adr[0]= 0;
 o->device_adr[0]= 0;
 o->adr_trn= NULL;
 o->abort_handler= 3;
 o->allow_setuid= 0;
 o->allow_fd_source= 0;
 o->allow_untested_media= 0;
 o->allow_emulated_drives= 0;
 o->no_whitelist= 0;
 o->no_convert_fs_adr= 0;
 o->old_pseudo_scsi_adr= 0;
 o->do_not_scan= 0;
 o->scan_demands_drive= 0;
 o->abort_on_busy_drive= 0;
 o->drive_exclusive= 1;
 o->drive_fcntl_f_setlk= 1;
 o->drive_scsi_dev_family= 0;
 o->drive_blocking= 0;
 strcpy(o->write_mode_name,"DEFAULT");

#ifndef Cdrskin_extra_leaN
 o->rc_filename_count= Cdrpreskin_rc_nuM;
 for(i=0;i<o->rc_filename_count-1;i++)
   strcpy(o->rc_filenames[i],Cdrpreskin_sys_rc_nameS[i]);
 o->rc_filenames[o->rc_filename_count-1][0]= 0;
 o->pre_argc= 0;
 o->pre_argv= NULL;
 o->pre_argidx= NULL;
 o->pre_arglno= NULL;
#endif /* ! Cdrskin_extra_leaN */

 o->fallback_program[0]= 0;
 o->demands_cdrecord_caps= 0;
 o->demands_cdrskin_caps= 0;
 o->result_fd = -1;
 return(1);
}


int Cdrpreskin_destroy(struct CdrpreskiN **preskin, int flag)
{
 struct CdrpreskiN *o;

 o= *preskin;
 if(o==NULL)
   return(0);

#ifndef Cdrskin_extra_leaN
 if((o->pre_arglno)!=NULL)
   free((char *) o->pre_arglno);
 if((o->pre_argidx)!=NULL)
   free((char *) o->pre_argidx);
 if(o->pre_argc>0 && o->pre_argv!=NULL)
   Sfile_destroy_argv(&(o->pre_argc),&(o->pre_argv),0);
 Cdradrtrn_destroy(&(o->adr_trn),0);
#endif /* ! Cdrskin_extra_leaN */

 free((char *) o);
 *preskin= NULL;
 return(1);
}


int Cdrpreskin_set_severities(struct CdrpreskiN *preskin, char *queue_severity,
                              char *print_severity, int flag)
{
 if(queue_severity!=NULL)
   strcpy(preskin->queue_severity,queue_severity);
 if(print_severity!=NULL)
   strcpy(preskin->print_severity,print_severity);
 burn_msgs_set_severities(preskin->queue_severity, preskin->print_severity,
                          "cdrskin: ");
 return(1);
}
 

int Cdrpreskin_initialize_lib(struct CdrpreskiN *preskin, int flag)
{
 int ret, major, minor, micro;

 /* Needed are at least 44 bits in signed type off_t .
    This is a popular mistake in configuration or compilation.
 */
 if(sizeof(off_t) < 6) {
   fprintf(stderr,
"\ncdrskin: FATAL : Compile time misconfiguration. sizeof(off_t) too small.\n"
          );
   return(0);
 }

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

/* The indendation is an advise of man gcc to help old compilers ignoring */
 #if Cdrskin_libburn_majoR > burn_header_version_major
 #define Cdrskin_libburn_dot_h_too_olD 1
 #endif
 #if Cdrskin_libburn_majoR == burn_header_version_major && Cdrskin_libburn_minoR > burn_header_version_minor
 #define Cdrskin_libburn_dot_h_too_olD 1
 #endif
 #if Cdrskin_libburn_minoR == burn_header_version_minor && Cdrskin_libburn_micrO > burn_header_version_micro
 #define Cdrskin_libburn_dot_h_too_olD 1
 #endif

#ifdef Cdrskin_libburn_dot_h_too_olD
LIBBURN_MISCONFIGURATION = 0;
INTENTIONAL_ABORT_OF_COMPILATION__HEADERFILE_libburn_dot_h_TOO_OLD__SEE_cdrskin_dot_c = 0;
LIBBURN_MISCONFIGURATION_ = 0;
#endif

 ret= burn_initialize();
 if(ret==0) {
   fprintf(stderr,"cdrskin: FATAL : Initialization of libburn failed\n");
   return(0);
 }

 /* This is the runtime check towards eventual dynamically linked libburn.
    cdrskin deliberately does not to allow the library to be older than
    the header file which was seen at compile time. More liberal would be
    to use here Cdrskin_libburn_* instead of burn_header_version_* .
 */
 burn_version(&major, &minor, &micro);
 if(major<burn_header_version_major || 
    (major==burn_header_version_major && (minor<burn_header_version_minor ||
     (minor==burn_header_version_minor && micro<burn_header_version_micro)))) {
   fprintf(stderr,"cdrskin: FATAL : libburn version too old: %d.%d.%d . Need at least: %d.%d.%d .\n",
          major, minor, micro,
          Cdrskin_libburn_majoR, Cdrskin_libburn_minoR, Cdrskin_libburn_micrO);
   return(-1);
 }
 Cdrpreskin_set_severities(preskin,NULL,NULL,0);
 burn_allow_drive_role_4(1);
 return(1);
}


/** Enable queuing of libburn messages or disable and print queue content.
    @param flag Bitfield for control purposes:
                bit0= enable queueing, else disable and print
*/
int Cdrpreskin_queue_msgs(struct CdrpreskiN *o, int flag)
{
/* In cdrskin there is not much sense in queueing library messages.
#ifndef Cdrskin_extra_leaN
#define Cdrskin_debug_libdax_msgS 1
#endif
       It would be done here only for debugging
*/
#ifdef Cdrskin_debug_libdax_msgS

 int ret;
 static char queue_severity[81]= {"NEVER"}, print_severity[81]= {"SORRY"};
 static int queueing= 0;
 char msg[BURN_MSGS_MESSAGE_LEN],msg_severity[81],filler[81];
 int error_code,os_errno,first,i;

 if(flag&1) {
   if(!queueing) {
     strcpy(queue_severity,o->queue_severity);
     strcpy(print_severity,o->print_severity);
   }
   if(o->verbosity>=Cdrskin_verbose_debuG)
     Cdrpreskin_set_severities(o,"DEBUG","NEVER",0);
   else
     Cdrpreskin_set_severities(o,"SORRY","NEVER",0);
   queueing= 1;
   return(1);
 }

 if(queueing)
   Cdrpreskin_set_severities(o,queue_severity,print_severity,0);
 queueing= 0;

 for(first= 1; ; first= 0) {
   ret= burn_msgs_obtain("ALL",&error_code,msg,&os_errno,msg_severity);
   if(ret==0)
 break;
   if(ret<0) {
     fprintf(stderr,
           "cdrskin: NOTE : Please inform libburn-hackers@pykix.org about:\n");
     fprintf(stderr,
           "cdrskin:        burn_msgs_obtain() returns %d\n",ret);
 break;
   }
   if(first)
     fprintf(stderr,
"cdrskin: -------------------- Messages from Libburn ---------------------\n");
   for(i=0;msg_severity[i]!=0;i++)
     filler[i]= ' ';
   filler[i]= 0;
   fprintf(stderr,"cdrskin: %s : %s\n",msg_severity,msg);
   if(strcmp(msg_severity,"DEBUG")!=0 && os_errno!=0)
     fprintf(stderr,"cdrskin: %s   ( errno=%d  '%s')\n",
                    filler,os_errno,strerror(os_errno));
 }
 if(first==0)
   fprintf(stderr,
"cdrskin: ----------------------------------------------------------------\n");

#endif /* Cdrskin_debug_libdax_msgS */

 return(1);
}


/** Evaluate whether the user would be allowed in any case to use device_adr
    as pseudo-drive */
int Cdrpreskin__allows_emulated_drives(char *device_adr, char reason[4096],
                                      int flag)
{
 struct stat stbuf;

 reason[0]= 0;
 if(device_adr[0]) {
   if(strcmp(device_adr,"/dev/null")==0)
     return(1);
   strcat(reason,"File object is not /dev/null. ");
 }

 if(getuid()!=geteuid()) {
   strcat(reason,"UID and EUID differ");
   return(0);
 }
 if(getuid()!=0)
   return(1);

 strcat(reason,"UID is 0. ");
 /* Directory must be owned by root and write protected against any others*/
 if(lstat("/root/cdrskin_permissions",&stbuf)==-1 || !S_ISDIR(stbuf.st_mode)) {
   strcat(reason, "No directory /root/cdrskin_permissions exists");
   return(0);
 }
 if(stbuf.st_uid!=0) {
   strcat(reason, "Directory /root/cdrskin_permissions not owned by UID 0");
   return(0);
 }
 if(stbuf.st_mode & (S_IWGRP | S_IWOTH)) {
   strcat(reason,
  "Directory /root/cdrskin_permissions has w-permission for group or others");
   return(0);
 }
 if(stat("/root/cdrskin_permissions/allow_emulated_drives",&stbuf)==-1) {
   strcat(reason,
          "No file /root/cdrskin_permissions/allow_emulated_drives exists");
   return(0);
 }
 reason[0]= 0;
 return(1);
}


int Cdrpreskin_consider_normal_user(int flag)
{
 fprintf(stderr,
    "cdrskin: HINT : Consider to allow rw-access to the writer devices and\n");
 fprintf(stderr,
    "cdrskin: HINT : to run cdrskin under your normal user identity.\n");
 return(1);
}


/* Start the fallback program as replacement of the cdrskin run.
   @param flag bit0=do not report start command
*/
int Cdrpreskin_fallback(struct CdrpreskiN *preskin, int argc, char **argv,
                        int flag)
{
 char **hargv= NULL;
 int i, wp= 1;
 char *ept, *upt;

 if(preskin->fallback_program[0] == 0)
   return(1);
 if(getuid()!=geteuid() && !preskin->allow_setuid) {
   fprintf(stderr,
     "cdrskin: SORRY : uid and euid differ. Will not start external fallback program.\n");
   Cdrpreskin_consider_normal_user(0);
   fprintf(stderr,
    "cdrskin: HINT : Option  --allow_setuid  disables this safety check.\n");
   goto failure;
 }
 if(!(flag&1)) {
   fprintf(stderr,"cdrskin: --------------------------------------------------------------------\n");
   fprintf(stderr,"cdrskin: Starting fallback program:\n");
 }
 hargv= TSOB_FELD(char *,argc+1);
 if(hargv==NULL)
   goto failure;
 hargv[0]= strdup(preskin->fallback_program);
 if(argv[0]==NULL)
   goto failure; 
 if(!(flag&1))
   fprintf(stderr,"         %s", hargv[0]);
 for(i= 1; i<argc; i++) {
   /* filter away all cdrskin specific options :  --?*  and  *_*=*   */
   if(argv[i][0]=='-' && argv[i][1]=='-' && argv[i][2])
 continue;
   ept= strchr(argv[i],'=');
   if(ept!=NULL) {
     upt= strchr(argv[i],'_');
     if(upt!=NULL && upt<ept)
 continue;
   }
   hargv[wp]= strdup(argv[i]);
   if(hargv[wp]==NULL)
     goto failure;
   if(!(flag&1))
     fprintf(stderr,"  %s", hargv[wp]);
   wp++;
 }
 hargv[wp]= NULL;
 if(!(flag&1)) {
   fprintf(stderr,"\n");
   fprintf(stderr,"cdrskin: --------------------------------------------------------------------\n");
 }
 execvp(hargv[0], hargv);
failure:;
 fprintf(stderr,"cdrskin: FATAL : Cannot start fallback program '%s'\n",
         preskin->fallback_program);
 fprintf(stderr,"cdrskin: errno=%d  \"%s\"\n",
         errno, (errno > 0 ? strerror(errno) : "unidentified error"));
 exit(15);
}


/** Convert a cdrecord-style device address into a libburn device address or
    into a libburn drive number. It depends on the "scsibus" number of the
    cdrecord-style address which kind of libburn address emerges:
      bus=0 : drive number , bus=1 : /dev/sgN , bus=2 : /dev/hdX
    (This call intentionally has no CdrpreskiN argument)
    @param flag Bitfield for control purposes: 
                bit0= old_pseudo_scsi_adr
    @return 1 success, 0=no recognizable format, -1=severe error,
            -2 could not find scsi device, -3 address format error
*/
int Cdrpreskin__cdrecord_to_dev(char *adr, char device_adr[Cdrskin_adrleN],
                                int *driveno, int flag)
{
 int comma_seen= 0,digit_seen= 0,busno= 0,k,lun_no= -1;

 *driveno= -1;
 device_adr[0]= 0;
 if(strlen(adr)==0)
   return(0);
 if(strncmp(adr,"stdio:",6)==0)
   return(0);

 /* read the trailing numeric string as device address code */
 /* accepts "1" , "0,1,0" , "ATA:0,1,0" , ... */
 for(k= strlen(adr)-1;k>=0;k--) {
   if(adr[k]==',' && !comma_seen) {
     sscanf(adr+k+1,"%d",&lun_no);
     comma_seen= 1;
     digit_seen= 0;
 continue;
   }
   if(adr[k]<'0' || adr[k]>'9')
 break;
   digit_seen= 1;
 }
 if(!digit_seen) {
   k= strlen(adr)-1;
   if(adr[k]==':' || (adr[k]>='A' && adr[k]<='Z')) {/* empty prefix ? */
     *driveno= 0;
     return(1);
   }
   return(0);
 }
 sscanf(adr+k+1,"%d",driveno);

 digit_seen= 0;
 if(k>0) if(adr[k]==',') {
   for(k--;k>=0;k--) {
     if(adr[k]<'0' || adr[k]>'9')
   break;
     digit_seen= 1;
   }
   if(digit_seen) {
     sscanf(adr+k+1,"%d",&busno);
     if(flag&1) {
       /* look for symbolic bus :  1=/dev/sgN  2=/dev/hdX */
       if(busno==1) {
         sprintf(device_adr,"/dev/sg%d",*driveno);
       } else if(busno==2) {
         sprintf(device_adr,"/dev/hd%c",'a'+(*driveno));
       } else if(busno!=0) {
         fprintf(stderr,
  "cdrskin: FATAL : dev=[Prefix:]Bus,Target,Lun expects Bus out of {0,1,2}\n");
         return(-3);
       }
     } else {
       if(busno<0) {
         fprintf(stderr,
     "cdrskin: FATAL : dev=[Prefix:]Bus,Target,Lun expects Bus number >= 0\n");
         return(-3);
       }
       if(busno>=1000) {
         busno-= 1000;
         goto ata_bus;
       } else if((strncmp(adr,"ATA",3)==0 && (adr[3]==0 || adr[3]==':')) ||
                 (strncmp(adr,"ATAPI",5)==0 && (adr[5]==0 || adr[5]==':'))) {
ata_bus:;
         if(busno>12 || (*driveno)<0 || (*driveno)>1) {
           fprintf(stderr,
"cdrskin: FATAL : dev=ATA:Bus,Target,Lun expects Bus {0..12}, Target {0,1}\n");
           return(-3);
         }
         sprintf(device_adr,"/dev/hd%c",'a'+(2*busno)+(*driveno));

       } else {
         int ret;

         ret= burn_drive_convert_scsi_adr(busno,-1,-1,*driveno,lun_no,
                                          device_adr);
         if(ret==0) {
           fprintf(stderr,
   "cdrskin: FATAL : Cannot find /dev/* with Bus,Target,Lun = %d,%d,%d\n",
                   busno,*driveno,lun_no);
           fprintf(stderr,
   "cdrskin: HINT : This drive may be in use by another program currently\n");
           return(-2);
         } else if(ret<0)
           return(-1);
         return(1);
       }
     }
   } 
 }
 return(1);
}


/** Set the eventual output fd for the result of Cdrskin_msinfo()
*/
int Cdrpreskin_set_result_fd(struct CdrpreskiN *o, int result_fd, int flag)
{
 o->result_fd= result_fd;
 return(1);
}


#ifndef Cdrskin_extra_leaN

/** Load content startup files into preskin cache */
int Cdrpreskin_read_rc(struct CdrpreskiN *o, char *progname, int flag)
{
 int ret,i;
 char **filenames_v;

 filenames_v= TSOB_FELD(char *, o->rc_filename_count+1);
 if(filenames_v==NULL)
   return(-1);
 for(i=0;i<o->rc_filename_count;i++)
   filenames_v[i]= o->rc_filenames[i];
 Sfile_home_adr_s(".cdrskinrc",o->rc_filenames[o->rc_filename_count-1],
                  Cdrskin_strleN,0);
 ret= Sfile_multi_read_argv(progname,filenames_v,o->rc_filename_count,
                            &(o->pre_argc),&(o->pre_argv),
                            &(o->pre_argidx),&(o->pre_arglno),4);
 free((char *) filenames_v);
 return(ret);
}

#endif /* ! Cdrskin_extra_leaN */


/** Interpret those arguments which do not need libburn or which influence the
    startup of libburn and/or the creation of the CdrskiN object. This is run
    before libburn gets initialized and before Cdrskin_new() is called.
    Options which need libburn or a CdrskiN object are processed in a different
    function named Cdrskin_setup().
    @param flag Bitfield for control purposes: 
                bit0= do not finalize setup
                bit1= do not read and interpret rc files
    @return <=0 error, 1 success , 2 end program run with exit value 0
*/
int Cdrpreskin_setup(struct CdrpreskiN *o, int argc, char **argv, int flag)
/*
return:
 <=0 error
   1 ok
   2 end program run (--help)
*/
{
 int i,ret;
 char *value_pt, reason[4096], *argpt;

#ifndef Cdrskin_extra_leaN
 if(argc>1) {
   if(strcmp(argv[1],"--no_rc")==0 || strcmp(argv[1],"-version")==0 ||
      strcmp(argv[1],"--help")==0 || strcmp(argv[1],"-help")==0 ||
      strncmp(argv[1], "textfile_to_v07t=", 17) == 0 ||
      strncmp(argv[1], "-textfile_to_v07t=", 18) == 0)
     flag|= 2;
 }
 if(!(flag&2)) {
   ret= Cdrpreskin_read_rc(o,argv[0],0);
   if(ret<0)
     return(-1);
   if(o->pre_argc>1) {
     ret= Cdrpreskin_setup(o,o->pre_argc,o->pre_argv,flag|1|2);
     if(ret<=0)
       return(ret);
     /* ??? abort on ret==2 ? */
   }
 }
#endif

 if(argc==1) {
   fprintf(stderr,"cdrskin: SORRY : No options given. Try option  --help\n");
   return(0);
 }

 /* The two predefined fallback personalities are triggered by the progname */
 value_pt= strrchr(argv[0],'/');
 if(value_pt==NULL)
   value_pt= argv[0];
 else
   value_pt++;
 if(strcmp(value_pt,"unicord")==0)
   strcpy(o->fallback_program,"cdrecord");
 else if(strcmp(value_pt,"codim")==0)
   strcpy(o->fallback_program,"wodim");

 for (i= 1;i<argc;i++) {

   argpt= argv[i];
   if (strncmp(argpt, "--", 2) == 0 && strlen(argpt) > 3)
     argpt++;

   if(strcmp(argv[i],"--abort_handler")==0) {
     o->abort_handler= 3;

   } else if(strcmp(argv[i],"--allow_emulated_drives")==0) {
     if(Cdrpreskin__allows_emulated_drives("",reason,0)<=0) {
       fprintf(stderr,"cdrskin: WARNING : %s.\n",reason);
       fprintf(stderr,
     "cdrskin: WARNING : Only /dev/null will be available with \"stdio:\".\n");
       Cdrpreskin_consider_normal_user(0);
       o->allow_emulated_drives= 2;
     } else
       o->allow_emulated_drives= 1;

   } else if(strcmp(argv[i],"--allow_setuid")==0) {
     o->allow_setuid= 1;

   } else if(strcmp(argv[i],"--allow_untested_media")==0) {
     o->allow_untested_media= 1;

   } else if(strcmp(argpt, "blank=help") == 0 ||
             strcmp(argpt, "-blank=help") == 0) {

#ifndef Cdrskin_extra_leaN

     fprintf(stderr,"Blanking options:\n");
     fprintf(stderr,"\tall\t\tblank the entire disk\n");
     fprintf(stderr,"\tdisc\t\tblank the entire disk\n");
     fprintf(stderr,"\tdisk\t\tblank the entire disk\n");
     fprintf(stderr,"\tfast\t\tminimally blank the entire disk\n");
     fprintf(stderr,"\tminimal\t\tminimally blank the entire disk\n");
     fprintf(stderr,
         "\tas_needed\tblank or format media to make it ready for (re-)use\n");
     fprintf(stderr,
          "\tdeformat_sequential\t\tfully blank, even formatted DVD-RW\n");
     fprintf(stderr,
          "\tdeformat_sequential_quickest\tminimally blank, even DVD-RW\n");
     fprintf(stderr,
          "\tformat_if_needed\t\tmake overwriteable if needed and possible\n");
     fprintf(stderr,
        "\tformat_overwrite\t\tformat a DVD-RW to \"Restricted Overwrite\"\n");
     fprintf(stderr,
    "\tformat_overwrite_quickest\tto \"Restricted Overwrite intermediate\"\n");
     fprintf(stderr,
          "\tformat_overwrite_full\t\tfull-size format a DVD-RW or DVD+RW\n");
     fprintf(stderr,
          "\tformat_defectmgt[_max|_min|_none]\tformat DVD-RAM or BD-R[E]\n");
     fprintf(stderr,
         "\tformat_defectmgt[_cert_on|_cert_off]\tcertification slow|quick\n");
     fprintf(stderr,
          "\tformat_defectmgt_payload_<size>\tformat DVD-RAM or BD-R[E]\n");
     fprintf(stderr,
        "\tformat_by_index_<number>\t\tformat by index from --list_formats\n");

#else /* ! Cdrskin_extra_leaN */

     goto see_cdrskin_eng_html;

#endif /* ! Cdrskin_extra_leaN */

     if(argc==2)
       {ret= 2; goto final_checks;}

   } else if(strcmp(argv[i],"--bragg_with_audio")==0) {
     /* OBSOLETE 0.2.3 */;

   } else if(strcmp(argv[i],"--demand_a_drive")==0) {
     o->scan_demands_drive= 1;
     o->demands_cdrskin_caps= 1;

   } else if(strcmp(argv[i],"--devices") == 0 ||
             strcmp(argv[i],"--device_links") == 0) {
     o->no_whitelist= 1;
     o->demands_cdrskin_caps= 1;

   } else if(strncmp(argv[i],"dev_translation=",16)==0) {
     o->demands_cdrskin_caps= 1;

#ifndef Cdrskin_extra_leaN

     if(o->adr_trn==NULL) {
       ret= Cdradrtrn_new(&(o->adr_trn),0);
       if(ret<=0)
         goto no_adr_trn_mem;
     }
     if(argv[i][16]==0) {
       fprintf(stderr,
         "cdrskin: FATAL : dev_translation= : missing separator character\n");
       return(0);
     }
     ret= Cdradrtrn_add(o->adr_trn,argv[i]+17,argv[i]+16,1);
     if(ret==-2) {
no_adr_trn_mem:;
       fprintf(stderr,
           "cdrskin: FATAL : address_translation= : cannot allocate memory\n");
     } else if(ret==-1)
       fprintf(stderr,
             "cdrskin: FATAL : address_translation= : table full (%d items)\n",
               Cdradrtrn_leN);
     else if(ret==0)
       fprintf(stderr,
  "cdrskin: FATAL : address_translation= : no address separator '%c' found\n",
               argv[i][17]);
     if(ret<=0)
       return(0);

#else /* ! Cdrskin_extra_leaN */

     fprintf(stderr,
       "cdrskin: FATAL : dev_translation= is not available in lean version\n");
     return(0);

#endif /* Cdrskin_extra_leaN */


   } else if(strncmp(argpt, "-dev=", 5) == 0) {
     value_pt= argpt + 5;
     goto set_dev;
   } else if(strncmp(argpt, "dev=", 4) == 0) {
     value_pt= argpt + 4;
set_dev:;
     if(strcmp(value_pt,"help")==0) {

#ifndef Cdrskin_extra_leaN

       printf("\nSupported SCSI transports for this platform:\n");
       fflush(stdout);
       if(o->old_pseudo_scsi_adr) {
         fprintf(stderr,"\nTransport name:\t\tlibburn OLD_PSEUDO\n");
         fprintf(stderr,
         "Transport descr.:\tBus0=DriveNum , Bus1=/dev/sgN , Bus2=/dev/hdX\n");
       } else {
         fprintf(stderr,"\nTransport name:\t\tlibburn SCSI\n");
         fprintf(stderr,
            "Transport descr.:\tSCSI Bus,Id,Lun as of operating system\n");
       }
       fprintf(stderr,"Transp. layer ind.:\t\n");
       fprintf(stderr,"Target specifier:\tbus,target,lun\n");
       fprintf(stderr,"Target example:\t\t1,2,0\n");
       fprintf(stderr,"SCSI Bus scanning:\tsupported\n");
       fprintf(stderr,"Open via UNIX device:\tsupported\n");
       if(!o->old_pseudo_scsi_adr) {
         fprintf(stderr,"\nTransport name:\t\tlibburn HD\n");
         fprintf(stderr,
           "Transport descr.:\tLinux specific alias for /dev/hdX\n");
         fprintf(stderr,"Transp. layer ind.:\tATA:\n");
         fprintf(stderr,"Target specifier:\tbus,target,lun\n");
         fprintf(stderr,"Target example:\t\tATA:1,0,0\n");
         fprintf(stderr,"SCSI Bus scanning:\tsupported\n");
         fprintf(stderr,"Open via UNIX device:\tsupported\n");
       }
       if(o->allow_emulated_drives) {
         fprintf(stderr,"\nTransport name:\t\tlibburn on standard i/o\n");
         if(o->allow_emulated_drives==2)
           fprintf(stderr, "Transport descr.:\troot or setuid may only write into /dev/null\n");
         else
           fprintf(stderr, "Transport descr.:\twrite into file objects\n");
         fprintf(stderr,"Transp. layer ind.:\tstdio:\n");
         fprintf(stderr,"Target specifier:\tpath\n");
         fprintf(stderr,"Target example:\t\tstdio:/tmp/pseudo_drive\n");
         fprintf(stderr,"SCSI Bus scanning:\tnot supported\n");
         fprintf(stderr,"Open via UNIX device:\tnot supported\n");
       } else {
         if(Cdrpreskin__allows_emulated_drives("",reason,0)>0)
           printf("\ncdrskin: NOTE : Option --allow_emulated_drives would allow dev=stdio:<path>\n");
       }

#else /* ! Cdrskin_extra_leaN */

       goto see_cdrskin_eng_html;

#endif /* Cdrskin_extra_leaN */

       {ret= 2; goto final_checks;}
     }
     if(strlen(value_pt)>=sizeof(o->raw_device_adr))
       goto dev_too_long;
     strcpy(o->raw_device_adr,value_pt);

   } else if(strcmp(argv[i],"--drive_abort_on_busy")==0) {
     o->abort_on_busy_drive= 1;

   } else if(strcmp(argv[i],"--drive_blocking")==0) {
     o->drive_blocking= 1;

   } else if(strcmp(argv[i],"--drive_f_setlk")==0) {
     o->drive_fcntl_f_setlk= 1;

   } else if(strcmp(argv[i],"--drive_not_exclusive")==0) {
     o->drive_exclusive= 0;
     o->drive_fcntl_f_setlk= 0;

   } else if(strcmp(argv[i],"--drive_not_f_setlk")==0) {
     o->drive_fcntl_f_setlk= 0;

   } else if(strcmp(argv[i],"--drive_not_o_excl")==0) {
     o->drive_exclusive= 0;

   } else if(strncmp(argv[i],"drive_scsi_dev_family=",22)==0) {
     value_pt= argv[i]+22;
     if(strcmp(value_pt,"sr")==0)
       o->drive_scsi_dev_family= 1;
     else if(strcmp(value_pt,"scd")==0)
       o->drive_scsi_dev_family= 2;
     else if(strcmp(value_pt,"sg")==0)
       o->drive_scsi_dev_family= 4;
     else
       o->drive_scsi_dev_family= 0;
   } else if(strcmp(argv[i],"--drive_scsi_exclusive")==0) {
     o->drive_exclusive= 2;
     o->demands_cdrskin_caps= 1;

   } else if(strcmp(argpt,"driveropts=help")==0 ||
             strcmp(argpt,"-driveropts=help")==0) {

#ifndef Cdrskin_extra_leaN

     fprintf(stderr,"Driver options:\n");
     fprintf(stderr,"burnfree\tPrepare writer to use BURN-Free technology\n");
     fprintf(stderr,"noburnfree\tDisable using BURN-Free technology\n");

#else /* ! Cdrskin_extra_leaN */

     goto see_cdrskin_eng_html;

#endif /* Cdrskin_extra_leaN */

     if(argc==2 || (i==2 && argc==3 && strncmp(argv[1],"dev=",4)==0))
       {ret= 2; goto final_checks;}

   } else if(strcmp(argv[i],"--help")==0) {

#ifndef Cdrskin_extra_leaN

     printf("\n");
     printf("Usage: %s [options|source_addresses]\n", argv[0]);
     printf("Burns preformatted data to CD or DVD via libburn.\n");
     printf("For the cdrecord compatible options which control the work of\n");
     printf(
      "blanking and burning see output of option -help rather than --help.\n");
     printf("Non-cdrecord options:\n");
     printf(" --abort_handler    do not leave the drive in busy state\n");
     printf(
     " --adjust_speed_to_drive  set only speeds offered by drive and media\n");
     printf(" --allow_emulated_drives  dev=stdio:<path> on file objects\n");
     printf(
        " --allow_setuid     disable setuid warning (setuid is insecure !)\n");
     printf(
        " --allow_untested_media   enable implemented untested media types\n");
     printf(
         " --any_track        allow source_addresses to match '^-.' or '='\n");
     printf(
         " assert_write_lba=<lba>  abort if not next write address == lba\n");
     printf(
      " cd_start_tno=<number>  set number of first track in CD SAO session\n");
     printf(
        " --cdtext_dummy     show CD-TEXT pack array instead of writing CD\n");
     printf(
   " cdtext_to_textfile=<path>  extract CD-TEXT from CD to disk file\n");
     printf(
   " cdtext_to_v07t=<path|\"-\">  report CD-TEXT from CD to file or stdout\n");
     printf(" --cdtext_verbose   show CD-TEXT pack array before writing CD\n");
     printf(
    " direct_write_amount=<size>  write random access to media like DVD+RW\n");
     printf(" --demand_a_drive   exit !=0 on bus scans with empty result\n");
     printf(" --devices          list accessible devices (tells /dev/...)\n");
     printf(" --device_links     list accessible devices by (udev) links\n");
     printf(
          " dev_translation=<sep><from><sep><to>   set input address alias\n");
     printf("                    e.g.: dev_translation=+ATA:1,0,0+/dev/sg1\n");
     printf(" --drive_abort_on_busy  abort process if busy drive is found\n");
     printf("                    (might be triggered by a busy hard disk)\n");
     printf(" --drive_blocking   try to wait for busy drive to become free\n");
     printf("                    (might be stalled by a busy hard disk)\n");
     printf(" --drive_f_setlk    obtain exclusive lock via fcntl.\n");
     printf(" --drive_not_exclusive  combined not_o_excl and not_f_setlk.\n");
     printf(" --drive_not_f_setlk  do not obtain exclusive lock via fcntl.\n");
     printf(" --drive_not_o_excl   do not ask kernel to prevent opening\n");
     printf("                    busy drives. Effect is kernel dependent.\n");
     printf(
         " drive_scsi_dev_family=<sr|scd|sg|default> select Linux device\n");
     printf("                    file family to be used for (pseudo-)SCSI.\n");
     printf(
         " --drive_scsi_exclusive  try to exclusively reserve device files\n");
     printf("                    /dev/srN, /dev/scdM, /dev/stK of drive.\n");
     printf(" dvd_obs=\"default\"|number\n");
     printf(
     "                    set number of bytes per DVD/BD write: 32k or 64k\n");
     printf(" extract_audio_to=<dir>\n");
     printf(
          "                    Copy all tracks of an audio CD as separate\n");
     printf(
          "                    .WAV files <dir>/track<NN>.wav to hard disk\n");
     printf(" extract_basename=<name>\n");
     printf(
          "                    Compose track files as <dir>/<name><NN>.wav\n");
     printf(
   " --extract_dap      Enable flaw obscuring mechanisms for audio reading\n");
     printf(" extract_tracks=<number[,number[,...]]>\n");
     printf(
        "                    Restrict extract_audio_to= to list of tracks.\n");
     printf(
        " fallback_program=<cmd>  use external program for exotic CD jobs.\n");
     printf(" --fifo_disable     disable fifo despite any fs=...\n");
     printf(" --fifo_per_track   use a separate fifo for each track\n");
     printf(
      " fifo_start_at=<number> do not wait for full fifo but start burning\n");
     printf(
         "                    as soon as the given number of bytes is read\n");
     printf(" --fill_up_media    cause the last track to have maximum size\n");
     printf(" --four_channel     indicate that audio tracks have 4 channels\n");
     printf(
          " grab_drive_and_wait=<num>  grab drive, wait given number of\n");
     printf(
          "                    seconds, release drive, and do normal work\n");
     printf(
    " --grow_overwriteable_iso  emulate multi-session on media like DVD+RW\n");
     printf(
       " --ignore_signals   try to ignore any signals rather than to abort\n");
     printf(" input_sheet_v07t=<path>  read a Sony CD-TEXT definition file\n");
     printf(" --list_formats     list format descriptors for loaded media.\n");
     printf(" --list_ignored_options list all ignored cdrecord options.\n");
     printf(" --list_speeds      list speed descriptors for loaded media.\n");
     printf(" --list_features    list SCSI features reported by the drive\n");
     printf(" --long_toc         print overview of media content\n");
     printf(" modesty_on_drive=<options> no writing into full drive buffer\n");
     printf(" --no_abort_handler  exit even if the drive is in busy state\n");
     printf(" --no_blank_appendable  refuse to blank appendable CD-RW\n");
     printf(" --no_convert_fs_adr  only literal translations of dev=\n");
     printf(" --no_load          do not try to load the drive tray\n");
     printf(
         " --no_rc            as first argument: do not read startup files\n");
     printf(" --obs_pad          pad DVD DAO to full 16 or 32 blocks\n");
     printf(" --old_pseudo_scsi_adr  use and report literal Bus,Target,Lun\n");
     printf("                    rather than real SCSI and pseudo ATA.\n");
     printf(
    " --pacifier_with_newline  do not overwrite pacifier line by next one.\n");
     printf(" --prodvd_cli_compatible  react on some DVD types more like\n");
     printf("                    cdrecord-ProDVD with blank= and -multi\n");
     printf(" sao_postgap=\"off\"|number\n");
     printf("                    defines whether the next track will get a\n");
     printf(
          "                    post-gap and how many sectors it shall have\n");
     printf(" sao_pregap=\"off\"|number\n");
     printf("                    defines whether the next track will get a\n");
     printf(
           "                    pre-gap and how many sectors it shall have\n");
     printf(
          " --single_track     accept only last argument as source_address\n");
     printf(" stream_recording=\"on\"|\"off\"|number\n");
     printf(
         "                    \"on\" requests to prefer speed over write\n");
     printf(
         "                    error management. A number prevents this with\n");
     printf(
         "                    byte addresses below that number.\n");
     printf(" stdio_sync=\"default\"|\"off\"|number\n");
     printf(
     "                    set number of bytes after which to force output\n");
     printf(
     "                    to drives with prefix \"stdio:\".\n");
     printf(
          " tao_to_sao_tsize=<num>  use num as fixed track size if in a\n");
     printf(
          "                    non-TAO mode track data are read from \"-\"\n");
     printf(
          "                    and no tsize= was specified.\n");
     printf(
 " --tell_media_space  print maximum number of writeable data blocks\n");
     printf(" textfile_to_v07t=file_path\n");
     printf(
          "                    print CD-TEXT file in Sony format and exit.\n");
     printf(" --two_channel      indicate that audio tracks have 2 channels\n");
     printf(
         " use_immed_bit=on|off|default  real control over SCSI Immed bit\n");
     printf(
         " write_start_address=<num>  write to given byte address (DVD+RW)\n");
     printf(
         " --xa1-ignore       with -xa1 do not strip 8 byte headers\n");
     printf(
 " -xamix             DO NOT USE (Dummy announcement to make K3B use -xa)\n");
     printf(
        "Preconfigured arguments are read from the following startup files\n");
     printf(
          "if they exist and are readable. The sequence is as listed here:\n");
     printf("  /etc/default/cdrskin         /etc/opt/cdrskin/rc\n");
     printf("  /etc/cdrskin/cdrskin.conf    $HOME/.cdrskinrc\n");
     printf("Each file line is a single argument. No whitespace.\n");
     printf(
         "By default any argument that does not match grep '^-.' or '=' is\n");
     printf(
         "used as track source. If it is \"-\" then stdin is used.\n");
     printf("cdrskin  : http://scdbackup.sourceforge.net/cdrskin_eng.html\n");
     printf("           mailto:scdbackup@gmx.net  (Thomas Schmitt)\n");
     printf("libburn  : http://libburnia-project.org\n");
     printf("cdrecord : ftp://ftp.berlios.de/pub/cdrecord/\n");
     printf("My respect to the authors of cdrecord and libburn.\n");
     printf("scdbackup: http://scdbackup.sourceforge.net/main_eng.html\n");
     printf("\n");

#else /* ! Cdrskin_extra_leaN */

see_cdrskin_eng_html:;
     printf("This is a capability reduced lean version without help texts.\n");
     printf("See  http://scdbackup.sourceforge.net/cdrskin_eng.html\n");

#endif /* Cdrskin_extra_leaN */


     {ret= 2; goto final_checks;}
   } else if(strcmp(argv[i],"-help")==0) {

#ifndef Cdrskin_extra_leaN

     fprintf(stderr,"Usage: %s [options|source_addresses]\n",argv[0]);
     fprintf(stderr,"Note: This is not cdrecord. See cdrskin start message on stdout. See --help.\n");
     fprintf(stderr,"Options:\n");
     fprintf(stderr,"\t-version\tprint version information and exit\n");
     fprintf(stderr,
             "\tdev=target\tpseudo-SCSI target to use as CD-Recorder\n");
     fprintf(stderr,
         "\tgracetime=#\tset the grace time before starting to write to #.\n");
     fprintf(stderr,"\t-v\t\tincrement general verbose level by one\n");
     fprintf(stderr,
            "\t-V\t\tincrement SCSI command transport verbose level by one\n");
     fprintf(stderr,
             "\tdriveropts=opt\topt= one of {burnfree,noburnfree,help}\n");
     fprintf(stderr,
             "\t-checkdrive\tcheck if a driver for the drive is present\n");
     fprintf(stderr,"\t-inq\t\tdo an inquiry for the drive and exit\n");
     fprintf(stderr,"\t-scanbus\tscan the SCSI bus and exit\n");
     fprintf(stderr,"\tspeed=#\t\tset speed of drive\n");
     fprintf(stderr,"\tblank=type\tblank a CD-RW disc (see blank=help)\n");
     fprintf(stderr,"\t-format\t\tformat a CD-RW/DVD-RW/DVD+RW disc\n");
     fprintf(stderr,
             "\tfs=#\t\tSet fifo size to # (0 to disable, default is 4 MB)\n");
     fprintf(stderr,
          "\t-load\t\tload the disk and exit (works only with tray loader)\n");
     fprintf(stderr,
 "\t-lock\t\tload and lock the disk and exit (works only with tray loader)\n");
     fprintf(stderr,
       "\t-eject\t\teject the disk after doing the work\n");
     fprintf(stderr,"\t-dummy\t\tdo everything with laser turned off\n");
     fprintf(stderr,"\t-minfo\t\tretrieve and print media information/status\n");
     fprintf(stderr,"\t-media-info\tretrieve and print media information/status\n");
     fprintf(stderr,
             "\t-msinfo\t\tretrieve multi-session info for mkisofs >= 1.10\n");
     fprintf(stderr,"\tmsifile=path\trun -msinfo and copy output to file\n");
     fprintf(stderr,"\t-toc\t\tretrieve and print TOC/PMA data\n");
     fprintf(stderr,
             "\t-atip\t\tretrieve media state, print \"Is *erasable\"\n");
     fprintf(stderr,
             "\tminbuf=percent\tset lower limit for drive buffer modesty\n");
     fprintf(stderr,
             "\t-multi\t\tgenerate a TOC that allows multi session\n");
     fprintf(stderr,
           "\t-waiti\t\twait until input is available before opening SCSI\n");
     fprintf(stderr,
           "\t-immed\t\tTry to use the SCSI IMMED flag with certain long lasting commands\n");
     fprintf(stderr,
           "\t-force\t\tforce to continue on some errors to allow blanking\n");
     fprintf(stderr,"\t-tao\t\tWrite disk in TAO mode.\n");
     fprintf(stderr,"\t-dao\t\tWrite disk in SAO mode.\n");
     fprintf(stderr,"\t-sao\t\tWrite disk in SAO mode.\n");

#ifndef Cdrskin_disable_raw96R
     fprintf(stderr,"\t-raw96r\t\tWrite disk in RAW/RAW96R mode\n");
#endif

     fprintf(stderr,"\ttsize=#\t\tannounces exact size of source data\n");
     fprintf(stderr,"\tpadsize=#\tAmount of padding\n");
     fprintf(stderr,
         "\tmcn=text\tSet the media catalog number for this CD to 'text'\n");
     fprintf(stderr,
          "\tisrc=text\tSet the ISRC number for the next track to 'text'\n");
     fprintf(stderr,
          "\tindex=list\tSet the index list for the next track to 'list'\n");
     fprintf(stderr,
             "\t-text\t\tWrite CD-Text from information from *.cue files\n");
     fprintf(stderr,
             "\ttextfile=name\tSet the file with CD-Text data to 'name'\n");
     fprintf(stderr,
             "\tcuefile=name\tSet the file with CDRWIN CUE data to 'name'\n");
     fprintf(stderr,"\t-audio\t\tSubsequent tracks are CD-DA audio tracks\n");
     fprintf(stderr,
            "\t-data\t\tSubsequent tracks are CD-ROM data mode 1 (default)\n");
     fprintf(stderr,
     "\t-xa1\t\tSubsequent tracks are CD-ROM XA mode 2 form 1 - 2056 bytes\n");
     fprintf(stderr,
           "\t-isosize\tUse iso9660 file system size for next data track\n");
     fprintf(stderr,"\t-preemp\t\tAudio tracks are mastered with 50/15 microseconds preemphasis\n");
     fprintf(stderr,"\t-nopreemp\tAudio tracks are mastered with no preemphasis (default)\n");
     fprintf(stderr,"\t-copy\t\tAudio tracks have unlimited copy permission\n");
     fprintf(stderr,"\t-nocopy\t\tAudio tracks may only be copied once for personal use (default)\n");
     fprintf(stderr,"\t-scms\t\tAudio tracks will not have any copy permission at all\n");
     fprintf(stderr,"\t-pad\t\tpadsize=30k\n");
     fprintf(stderr,
        "\t-nopad\t\tDo not pad (default, but applies only to data tracks)\n");
     fprintf(stderr,
       "\t-swab\t\tAudio data source is byte-swapped (little-endian/Intel)\n");
     fprintf(stderr,"\t-help\t\tprint this text to stderr and exit\n");
     fprintf(stderr,
             "Without option -data, .wav and .au files are extracted and burned as -audio.\n");
     fprintf(stderr,
    "By default any argument that does not match grep '^-.' or '=' is used\n");
     fprintf(stderr,
         "as track source address. Address \"-\" means stdin.\n");
     fprintf(stderr,
        "cdrskin will ensure that an announced tsize= is written even if\n");
     fprintf(stderr,
        "the source delivers fewer bytes. But 0 bytes from stdin with fifo\n");
     fprintf(stderr,
        "enabled will lead to abort and no burn attempt at all.\n");

#else /* ! Cdrskin_extra_leaN */

     fprintf(stderr,"Note: This is not cdrecord. See cdrskin start message on stdout.\n");
     fprintf(stderr,
          "(writer profile: -atip retrieve, blank=type, -eject after work)\n");
     goto see_cdrskin_eng_html;

#endif /* Cdrskin_extra_leaN */

     {ret= 2; goto final_checks;}

   } else if(strcmp(argv[i],"--ignore_signals")==0) {
     o->abort_handler= 2;

   } else if(strncmp(argv[i],"fallback_program=",17)==0) {
     if(strlen(argv[i] + 17) >= sizeof(o->fallback_program)) {
       fprintf(stderr,
       "cdrskin: FATAL : fallback_program=... too long (max. %d characters)\n",
               (int) sizeof(o->fallback_program) - 1);
       {ret= 0; goto ex;}
     }
     strcpy(o->fallback_program,argv[i]+17);

   } else if(strcmp(argv[i],"--no_abort_handler")==0) {
     o->abort_handler= 0;

   } else if(strcmp(argv[i],"--no_convert_fs_adr")==0) {
     o->no_convert_fs_adr= 1;

   } else if(strcmp(argv[i],"--old_pseudo_scsi_adr")==0) {
     o->old_pseudo_scsi_adr= 1;
     o->demands_cdrskin_caps= 1;

   } else if(strcmp(argv[i],"--no_rc")==0) {
     if(i!=1)
       fprintf(stderr,
        "cdrskin: NOTE : option --no_rc would only work as first argument.\n");

#ifndef Cdrskin_disable_raw96R
   } else if(strcmp(argpt,"-raw96r")==0) {
     strcpy(o->write_mode_name,"RAW/RAW96R");
#endif

   } else if(strcmp(argpt,"-sao")==0 || strcmp(argpt,"-dao")==0) {
     strcpy(o->write_mode_name,"SAO");

   } else if(strcmp(argpt,"-scanbus")==0) {
     o->no_whitelist= 1;

   } else if(strcmp(argpt,"-tao")==0) {
     strcpy(o->write_mode_name,"TAO");

   } else if(strncmp(argpt, "-textfile_to_v07t=", 18) == 0) {
     o->do_not_scan= 1;
   } else if(strncmp(argpt ,"textfile_to_v07t=", 17) == 0) {
     o->do_not_scan= 1;

   } else if(strcmp(argv[i],"-V")==0 || strcmp(argpt, "-Verbose") == 0) {
     burn_set_scsi_logging(2 | 4); /* log SCSI to stderr */

   } else if(strcmp(argv[i],"-v")==0 || strcmp(argpt, "-verbose") == 0) {
     (o->verbosity)++;
     ClN(printf("cdrskin: verbosity level : %d\n",o->verbosity));
set_severities:;
     if(o->verbosity>=Cdrskin_verbose_debuG)
       Cdrpreskin_set_severities(o,"NEVER","DEBUG",0);
     else if(o->verbosity >= Cdrskin_verbose_progresS)
       Cdrpreskin_set_severities(o, "NEVER", "UPDATE", 0);

   } else if(strcmp(argv[i],"-vv")==0 || strcmp(argv[i],"-vvv")==0 ||
             strcmp(argv[i],"-vvvv")==0) {
     (o->verbosity)+= strlen(argv[i])-1;
     goto set_severities;

   } else if(strcmp(argpt,"-version")==0) {
     int major, minor, micro;

     printf(
"Cdrecord 2.01a27 Emulation. Copyright (C) 2006-2021, see libburnia-project.org\n");
     if(o->fallback_program[0]) {
       char *hargv[2];

       printf("Fallback program  :  %s\n",o->fallback_program);
       printf("Fallback version  :\n");
       hargv[0]= argv[0];
       hargv[1]= "-version";
       Cdrpreskin_fallback(o,2,hargv,1); /* dirty never come back */
     }
     printf("System adapter    :  %s\n", burn_scsi_transport_id(0));
     printf("libburn interface :  %d.%d.%d\n",
            burn_header_version_major, burn_header_version_minor,
            burn_header_version_micro);
     burn_version(&major, &minor, &micro);
     printf("libburn in use    :  %d.%d.%d\n", major, minor, micro);

#ifndef Cdrskin_extra_leaN
     printf("cdrskin version   :  %s\n",Cdrskin_prog_versioN);
#else
     printf("cdrskin version   :  %s.lean (capability reduced lean version)\n",
            Cdrskin_prog_versioN);
#endif

     printf("Version timestamp :  %s\n",Cdrskin_timestamP);
     printf("Build timestamp   :  %s\n",Cdrskin_build_timestamP);
     {ret= 2; goto ex;}

   } else if(strcmp(argpt,"-waiti")==0) {
     o->do_waiti= 1;

   } else if(strcmp(argpt,"-xamix")==0) {
     fprintf(stderr,
       "cdrskin: FATAL : Option -xamix not implemented and data not yet convertible to other modes.\n");
     return(0);
   }

 }
 ret= 1;
final_checks:;
 if(flag&1)
   goto ex;
 if(o->verbosity >= Cdrskin_verbose_debuG)
   ClN(fprintf(stderr,
       "cdrskin: DEBUG : Using %s\n", burn_scsi_transport_id(0)));
 if(o->do_waiti) {
   fprintf(stderr,
       "cdrskin: Option -waiti pauses program until input appears at stdin\n");
   printf("Waiting for data on stdin...\n");
   for(ret= 0; ret==0; )
     ret= Wait_for_input(0,1000000,0);
   if(ret<0 || feof(stdin))
     fprintf(stderr,
             "cdrskin: NOTE : stdin produces exception rather than data\n");
   fprintf(stderr,"cdrskin: Option -waiti pausing is done.\n");
 }
 burn_preset_device_open(o->drive_exclusive
                         | (o->drive_scsi_dev_family<<2)
                         | ((!!o->drive_fcntl_f_setlk)<<5),
                         o->drive_blocking,
                         o->abort_on_busy_drive);

 if(strlen(o->raw_device_adr)>0 && !o->no_whitelist) {
   int driveno,hret;
   char *adr,buf[Cdrskin_adrleN];

   if(strcmp(o->raw_device_adr,"stdio:-")==0) {
     fprintf(stderr,
             "cdrskin: SORRY : Cannot accept drive address \"stdio:-\".\n");
     fprintf(stderr,
             "cdrskin: HINT  : Use \"stdio:/dev/fd/1\" if you really want to write to stdout.\n");
     {ret= 0; goto ex;}
   }
   adr= o->raw_device_adr;

#ifndef Cdrskin_extra_leaN
   if(o->adr_trn!=NULL) {
     hret= Cdradrtrn_translate(o->adr_trn,adr,-1,buf,0);
     if(hret<=0) {
       fprintf(stderr,
        "cdrskin: FATAL : address translation failed (address too long ?) \n");
       {ret= 0; goto ex;}
     }
     adr= buf;
   }
#endif /* ! Cdrskin_extra_leaN */

   if(adr[0]=='/') {
     if(strlen(adr)>=sizeof(o->device_adr)) {
dev_too_long:;
       fprintf(stderr,
               "cdrskin: FATAL : dev=... too long (max. %d characters)\n",
               (int) sizeof(o->device_adr)-1);
       {ret= 0; goto ex;}
     }
     strcpy(o->device_adr,adr);
   } else {
     ret= Cdrpreskin__cdrecord_to_dev(adr,o->device_adr,&driveno,
                                      !!o->old_pseudo_scsi_adr);
     if(ret==-2 || ret==-3)
       {ret= 0; goto ex;}
     if(ret<0)
       goto ex;
     if(ret==0) {
       strcpy(o->device_adr,adr);
       ret= 1;
     }
   }

   if(strlen(o->device_adr)>0 && !o->no_convert_fs_adr) {
     int lret;
     char link_adr[Cdrskin_strleN+1];

     strcpy(link_adr,o->device_adr);
     lret = burn_drive_convert_fs_adr(link_adr,o->device_adr);
     if(lret<0) {
       fprintf(stderr,
           "cdrskin: NOTE : Please inform libburn-hackers@pykix.org about:\n");
       fprintf(stderr,
	   "cdrskin:        burn_drive_convert_fs_adr() returned %d\n",lret);
     }
   }

 }

 burn_allow_untested_profiles(!!o->allow_untested_media);

 /* A60927 : note to myself : no "ret= 1;" here. It breaks --help , -version */

ex:;
 /* Eventually replace current stdout by dup(1) from start of program */
 if(strcmp(o->device_adr,"stdio:/dev/fd/1")==0 && o->result_fd >= 0)
   sprintf(o->device_adr,"stdio:/dev/fd/%d",o->result_fd);

#ifndef Cdrskin_extra_leaN
 if(ret<=0 || !(flag&1))
   Cdradrtrn_destroy(&(o->adr_trn),0);
#endif

 return(ret);
}


/* --------------------------------------------------------------------- */



/** The maximum number of tracks */
#define Cdrskin_track_maX 99


/** Work around the fact that libburn leaves the track input fds open
    after the track is done. This can hide a few overflow bytes buffered 
    by the fifo-to-libburn pipe which would cause a broken-pipe error
    if libburn would close that outlet.
    This macro enables a coarse workaround which tries to read bytes from
    the track inlets after burning has ended. Probably not a good idea if
    libburn would close the inlet fds.
*/
#define Cdrskin_libburn_leaves_inlet_opeN 1


/** List of furter wishes towards libburn:
    - a possibility to implement cdrskin -reset
*/


#define Cdrskin_tracksize_maX 1024.0*1024.0*1024.0*1024.0


/* Some constants obtained by hearsay and experiments */

/** The CD payload speed factor for reporting progress: 1x = 150 kB/s */
static double Cdrskin_cd_speed_factoR= 150.0*1024.0;
/** The DVD payload speed factor for reporting progress: 1x */
static double Cdrskin_dvd_speed_factoR= 1385000;
/** The BD payload speed factor for reporting progress: 1x */
static double Cdrskin_bd_speed_factoR= 4495625;

/** The effective payload speed factor for reporting progress */
static double Cdrskin_speed_factoR= 150.0*1024.0;

/** The speed conversion factors consumer x-speed to libburn speed as used with
    burn_drive_set_speed() burn_drive_get_write_speed()
*/
static double Cdrskin_libburn_cd_speed_factoR= 176.4;
static double Cdrskin_libburn_dvd_speed_factoR= 1385.0;
static double Cdrskin_libburn_bd_speed_factoR= 4495.625;

/* The effective speed conversion factor for burn_drive_set_speed() */
static double Cdrskin_libburn_speed_factoR= 176.4;

/** Add-on for burn_drive_set_speed() to accommodate to the slightly oversized
    speed ideas of my LG DVDRAM GSA-4082B. LITE-ON LTR-48125S tolerates it.
*/
static double Cdrskin_libburn_cd_speed_addoN= 40.0;
static double Cdrskin_libburn_dvd_speed_addoN= 1.0; /*poor accuracy with 2.4x*/
static double Cdrskin_libburn_bd_speed_addoN= 1.0;
static double Cdrskin_libburn_speed_addoN = 40.0;


/** The program run control object. Defaults: see Cdrskin_new(). */
struct CdrskiN {

 /** Settings already interpreted by Cdrpreskin_setup */
 struct CdrpreskiN *preskin;

 /** Job: what to do, plus some parameters. */
 int verbosity;
 int pacifier_with_newline;
 double x_speed;
 int adjust_speed_to_drive;
 int gracetime;
 int dummy_mode;
 int force_is_set;
 int stream_recording_is_set; /* see burn_write_opts_set_stream_recording() */
 int dvd_obs;                 /* DVD write chunk size: 0, 32k or 64k */
 int obs_pad;                 /* Whether to force obs end padding */
 int stdio_sync;              /* stdio fsync interval: -1, 0, >=32 */
 int single_track;
 int prodvd_cli_compatible;
 int use_immed;               /* 1= yes, 0= libburn default, -1= no */

 int do_devices;              /* 1= --devices , 2= --device_links */

 int do_scanbus;

 int do_load;                 /* 1= -load , 2= -lock , -1= --no_load */

 int do_checkdrive;

 int do_msinfo;
 char msifile[Cdrskin_strleN];

 int do_atip;
 int do_list_speeds;
 int do_list_formats;
 int do_list_features;
 int do_cdtext_to_textfile;
 char cdtext_to_textfile_path[Cdrskin_strleN];
 int do_cdtext_to_vt07;
 char cdtext_to_vt07_path[Cdrskin_strleN];
 int do_extract_audio;
 char extract_audio_dir[Cdrskin_strleN];
 char extract_basename[249];
 char extract_audio_tracks[100];     /* if [i] > 0 : extract track i */
 int extract_flags;                  /* bit3 = for flag bit3 of
                                               burn_drive_extract_audio()
                                     */

#ifdef Libburn_develop_quality_scaN
 int do_qcheck;              /* 0= no , 1=nec_optiarc_rep_err_rate */
#endif /* Libburn_develop_quality_scaN */

 int do_blank;
 int blank_fast;
 int no_blank_appendable;
 int blank_format_type; /* bit0-7: job type
                           0=blank
                           1=format_overwrite for DVD+RW, DVD-RW
                             bit8-15: bit0-7 of burn_disc_format(flag)
                               bit8 = write zeros after formatting
                               bit9 = insist in size 0
                               bit10= format to maximum available size
                               bit11= - reserved -
                               bit12= - reserved -
                               bit13= try to disable eventual defect management
                               bit14= - reserved -
                               bit15= format by index
                           2=deformat_sequential (blank_fast might matter)
                           3=format (= format_overwrite restricted to DVD+RW)
                           4=format_defectmgt for DVD-RAM, BD-R[E]
                             bit8-15: bit0-7 of burn_disc_format(flag)
                               bit8 = write zeros after formatting
                               bit9+10: size mode
                               0 = use parameter size as far as it makes sense
                               1 = (identical to size mode 0)
                               2 = without bit7: format to maximum size
                                   with bit7   : take size from indexed format
                                                 descriptor
                               3 = without bit7: format to default size
                                   with bit7   : take size from indexed format
                                                 descriptor
                               bit11= - reserved -
                               bit12= - reserved -
                               bit13= try to disable defect management
                               bit14= - reserved -
                               bit15= format by index
                           5=format_by_index
                             gets mapped to 4 with DVD-RAM and BD-RE else to 1,
                             bit15 should be set and bit16-23 should contain
                             a usable index number
                           6=format_if_needed
                             gets mapped to default variants of specialized
                             formats if the media state requires formatting
                             before writing
                           7=if_needed
                             gets mapped to 6 for DVD-RAM and BD-RE,
                             to 0 with all other non-blanks

                           bit8-15: bit0-7 of burn_disc_format(flag)
                                    depending on job type
                        */
 int blank_format_index;   /* bit8-15 of burn_disc_format(flag) */
 double blank_format_size; /* to be used with burn_disc_format() */
 int blank_format_no_certify;

 int do_direct_write;
 int do_burn;
 int tell_media_space; /* actually do not burn but tell the available space */
 int burnfree;
 /** The write mode (like SAO or TAO). See libburn. 
     Controlled by preskin->write_mode_name */
 enum burn_write_types write_type;
 int block_type;
 int multi;
 int cdxa_conversion; /* bit0-30: for burn_track_set_cdxa_conv()
                         bit31  : ignore bits 0 to 30
                       */
 int modesty_on_drive;
 int min_buffer_usec;         /* The other parameters for this function */
 int max_buffer_usec;
 int buffer_timeout_sec;
 int min_buffer_percent;
 int max_buffer_percent;

 double write_start_address;
 double direct_write_amount;
 int assert_write_lba;

 int do_eject;
 char eject_device[Cdrskin_strleN];


 /** The current data source and its eventual parameters. 
     source_path may be either "-" for stdin, "#N" for open filedescriptor N
     or the address of a readable file.
 */
 char source_path[Cdrskin_strleN];
 double fixed_size;
 double smallest_tsize;
 int has_open_ended_track;
 double padding;
 int set_by_padsize;
 int fill_up_media;

 /** track_type may be set to BURN_MODE1, BURN_AUDIO, etc. */
 int track_type;
 int track_type_by_default; /* 0= explicit, 1=not set, 2=by file extension */
 int swap_audio_bytes;
 int track_modemods;

 /** CDRWIN cue sheet file */
 char cuefile[Cdrskin_adrleN]; 
 int use_cdtext;

 /** CD-TEXT */
 unsigned char *text_packs;
 int num_text_packs;

 int sheet_v07t_blocks;
 char sheet_v07t_paths[8][Cdrskin_adrleN];

 int cdtext_test;

 /* Media Catalog Number and ISRC */
 char mcn[14];
 char next_isrc[13];

 char *index_string;

 int sao_pregap;
 int sao_postgap;

 /** The list of tracks with their data sources and parameters */
 struct CdrtracK *tracklist[Cdrskin_track_maX];
 int track_counter;
 /** a guess about what track might be processing right now */
 int supposed_track_idx;

 /** The number of the first track in a CD SAO session */
 int cd_start_tno;

 int fifo_enabled;
 /** Optional fifo between input fd and libburn. It uses a pipe(2) to transfer
     data to libburn. This fifo may be actually the start of a chain of fifos
     which are to be processed simultaneously.
     The fifo object knows the real input fd and the fd[1] of the pipe.
     This is just a reference pointer. The fifos are managed by the tracks
     which either line up their fifos or share the fifo of the first track.
 */
 struct CdrfifO *fifo;
 /** fd[0] of the fifo pipe. This is from where libburn reads its data. */
 int fifo_outlet_fd;
 int fifo_size;
 int fifo_start_at;
 int fifo_per_track;
 struct burn_source *cuefile_fifo;

 /** User defined address translation */
 struct CdradrtrN *adr_trn;


 /** The drives known to libburn after scan */
 struct burn_drive_info *drives;
 unsigned int n_drives;
 /** The drive selected for operation by CdrskiN */
 int driveno;
 /** The persistent drive address of that drive */
 char device_adr[Cdrskin_adrleN];


 /** Progress state info: whether libburn is actually processing payload data*/
 int is_writing;
 /** Previously detected drive state */
 enum burn_drive_status previous_drive_status;

 /** abort parameters */
 int abort_max_wait;

 /** Engagement info for eventual abort */
 int lib_is_initialized;
 pid_t control_pid; /* pid of the thread that calls libburn */
 int drive_is_grabbed;
 struct burn_drive *grabbed_drive;
 /* Whether drive was told to do something cancel-worthy 
    0= no: directly call burn_abort
    1= yes: A worker thread is busy (e.g. writing, formatting)
    2= yes: A synchronous operation is busy (e.g. grabbing).
 */
 int drive_is_busy;

#ifndef Cdrskin_extra_leaN
 /** Abort test facility */
 double abort_after_bytecount; 
#endif /* ! Cdrskin_extra_leaN */


 /** Some intermediate option info which is stored until setup finalization */
 double tao_to_sao_tsize;
 int stdin_source_used;

 /* Info about media capabilities */
 int media_does_multi;
 int media_is_overwriteable;

 /* For option -isosize and --grow_overwriteable_iso */
 int use_data_image_size;

 /* For growisofs stunt :
    0=disabled,
    1=do stunt, fabricate toc, allow multi,
    2=overwriteable_iso_head is valid
    3=initial session (mostly to appease -multi on overwriteables)
 */
 int grow_overwriteable_iso;
 /* New image head buffer for --grow_overwriteable_iso */
 char overwriteable_iso_head[32*2048]; /* block  0 to 31 of target */

};

int Cdrskin_destroy(struct CdrskiN **o, int flag);
int Cdrskin_grab_drive(struct CdrskiN *skin, int flag);
int Cdrskin_release_drive(struct CdrskiN *skin, int flag);
int Cdrskin_report_disc_status(struct CdrskiN *skin, enum burn_disc_status s,
                               int flag);

/** Create a cdrskin program run control object.
    @param skin Returns pointer to resulting
    @param flag Bitfield for control purposes: 
                bit0= library is already initialized
    @return <=0 error, 1 success
*/
int Cdrskin_new(struct CdrskiN **skin, struct CdrpreskiN *preskin, int flag)
{
 struct CdrskiN *o;
 int ret,i;

 (*skin)= o= TSOB_FELD(struct CdrskiN,1);
 if(o==NULL)
   return(-1);
 o->preskin= preskin;
 o->verbosity= preskin->verbosity;
 o->pacifier_with_newline= 0;
 o->x_speed= -1.0;
 o->adjust_speed_to_drive= 0;
 o->gracetime= 0;
 o->dummy_mode= 0;
 o->force_is_set= 0;
 o->stream_recording_is_set= 0;
 o->dvd_obs= 0;
 o->obs_pad= 0;
 o->stdio_sync= 0;
 o->single_track= 0;
 o->prodvd_cli_compatible= 0;
 o->use_immed= 0;
 o->do_devices= 0;
 o->do_scanbus= 0;
 o->do_load= 0;
 o->do_checkdrive= 0;
 o->do_msinfo= 0;
 o->msifile[0]= 0;
 o->do_atip= 0;
 o->do_list_speeds= 0;
 o->do_list_formats= 0;
 o->do_list_features= 0;
 o->do_cdtext_to_textfile= 0;
 o->cdtext_to_textfile_path[0]= 0;
 o->do_cdtext_to_vt07= 0;
 o->cdtext_to_vt07_path[0]= 0;
 o->do_extract_audio= 0;
 o->extract_audio_dir[0]= 0;
 o->extract_basename[0]= 0;
 for(i= 0; i < 100; i++)
   o->extract_audio_tracks[i]= 0;
 o->extract_flags= 0;

#ifdef Libburn_develop_quality_scaN
 o->do_qcheck= 0;
#endif /* Libburn_develop_quality_scaN */

 o->do_blank= 0;
 o->blank_fast= 0;
 o->no_blank_appendable= 0;
 o->blank_format_type= 0;
 o->blank_format_index= -1;
 o->blank_format_size= 0.0;
 o->blank_format_no_certify= 0;
 o->do_direct_write= 0;
 o->do_burn= 0;
 o->tell_media_space= 0;
 o->write_type= BURN_WRITE_SAO;
 o->block_type= BURN_BLOCK_SAO;
 o->multi= 0;
 o->cdxa_conversion= 0;
 o->modesty_on_drive= 0;
 o->buffer_timeout_sec= 120;
 o->min_buffer_usec= 10000;
 o->max_buffer_usec= 100000;
 o->min_buffer_percent= 65;
 o->max_buffer_percent= 95;
 o->write_start_address= -1.0;
 o->direct_write_amount= -1.0;
 o->assert_write_lba= -1;
 o->burnfree= 1;
 o->do_eject= 0;
 o->eject_device[0]= 0;
 o->source_path[0]= 0;
 o->fixed_size= 0.0;
 o->smallest_tsize= -1.0;
 o->has_open_ended_track= 0;
 o->padding= 0.0;
 o->set_by_padsize= 0;
 o->fill_up_media= 0;
 o->track_type= BURN_MODE1;
 o->swap_audio_bytes= 1;   /* cdrecord default is big-endian (msb_first) */
 o->cuefile[0] = 0; 
 o->use_cdtext = 0;
 o->text_packs= NULL;
 o->num_text_packs= 0;
 o->sheet_v07t_blocks= 0;
 for(i= 0; i < 8; i++)
   memset(o->sheet_v07t_paths, 0, Cdrskin_adrleN);
 o->cdtext_test= 0;
 o->mcn[0]= 0;
 o->next_isrc[0]= 0;
 o->index_string= NULL;
 o->sao_pregap= -1;
 o->sao_postgap= -1;
 o->track_modemods= 0;
 o->track_type_by_default= 1;
 for(i=0;i<Cdrskin_track_maX;i++)
   o->tracklist[i]= NULL;
 o->track_counter= 0;
 o->supposed_track_idx= -1;
 o->cd_start_tno= 0;
 o->fifo_enabled= 1;
 o->fifo= NULL;
 o->fifo_outlet_fd= -1;
 o->fifo_size= 4*1024*1024;
 o->fifo_start_at= -1;
 o->fifo_per_track= 0;
 o->cuefile_fifo= NULL;
 o->adr_trn= NULL;
 o->drives= NULL;
 o->n_drives= 0;
 o->driveno= 0;
 o->device_adr[0]= 0;
 o->is_writing= 0;
 o->previous_drive_status = BURN_DRIVE_IDLE;
 o->abort_max_wait= 74*60;
 o->lib_is_initialized= (flag&1);
 o->control_pid= getpid();
 o->drive_is_grabbed= 0;
 o->drive_is_busy= 0;
 o->grabbed_drive= NULL;
#ifndef Cdrskin_extra_leaN
 o->abort_after_bytecount= -1.0;
#endif /* ! Cdrskin_extra_leaN */
 o->tao_to_sao_tsize= 0.0;
 o->stdin_source_used= 0;
 o->use_data_image_size= 0;
 o->media_does_multi= 0;
 o->media_is_overwriteable= 0;
 o->grow_overwriteable_iso= 0;
 memset(o->overwriteable_iso_head,0,sizeof(o->overwriteable_iso_head));

#ifndef Cdrskin_extra_leaN
 ret= Cdradrtrn_new(&(o->adr_trn),0);
 if(ret<=0)
   goto failed;
#endif /* ! Cdrskin_extra_leaN */

 return(1);
failed:;
 Cdrskin_destroy(skin,0);
 return(-1);
}


/** Release from memory a cdrskin object */
int Cdrskin_destroy(struct CdrskiN **o, int flag)
{
 struct CdrskiN *skin;
 int i;

 skin= *o;
 if(skin==NULL)
   return(0);
 if(skin->drive_is_grabbed)
   Cdrskin_release_drive(skin,0);
 for(i=0;i<skin->track_counter;i++)
   Cdrtrack_destroy(&(skin->tracklist[i]),0);
 if(skin->index_string != NULL)
   free(skin->index_string);

#ifndef Cdrskin_extra_leaN
 Cdradrtrn_destroy(&(skin->adr_trn),0);
#endif /* ! Cdrskin_extra_leaN */

 if(skin->cuefile_fifo != NULL)
   burn_source_free(skin->cuefile_fifo);
 if(skin->text_packs != NULL)
   free(skin->text_packs);
 Cdrpreskin_destroy(&(skin->preskin),0);
 if(skin->drives!=NULL)
   burn_drive_info_free(skin->drives);
 free((char *) skin);
 *o= NULL;
 return(1);
}


int Cdrskin_assert_driveno(struct CdrskiN *skin, int flag)
{
 if(skin->driveno < 0 || (unsigned int) skin->driveno >= skin->n_drives) {
   fprintf(stderr,
       "cdrskin: FATAL : No drive found. Cannot perform desired operation.\n");
   return(0);
 }
 return(1);
}


/** Return the addresses of the drive. device_adr is the libburn persistent
    address of the drive, raw_adr is the address as given by the user.
*/
int Cdrskin_get_device_adr(struct CdrskiN *skin,
           char **device_adr, char **raw_adr, int *no_convert_fs_adr, int flag)
{
 int ret;

 if(skin->driveno < 0 || (unsigned int) skin->driveno >= skin->n_drives)
   return(0);
 ret= burn_drive_get_adr(&skin->drives[skin->driveno],skin->device_adr);
 if(ret <= 0)
   return(0);
 *device_adr= skin->device_adr;
 *raw_adr= skin->preskin->raw_device_adr;
 *no_convert_fs_adr= skin->preskin->no_convert_fs_adr;
 return(1); 
}


int Cdrskin_get_drive(struct CdrskiN *skin, struct burn_drive **drive,int flag)
{
 if(skin->driveno<0 || (unsigned int) skin->driveno >= skin->n_drives)
   return(0);
 *drive= skin->drives[skin->driveno].drive;
 return ((*drive) != NULL); 
}


/** Return information about current track source */
int Cdrskin_get_source(struct CdrskiN *skin, char *source_path,
                       double *fixed_size, double *tao_to_sao_tsize,
                       int *use_data_image_size,
                       double *padding, int *set_by_padsize,
                       int *track_type, int *track_type_by_default,
                       int *mode_mods,
                       int *swap_audio_bytes, int *cdxa_conversion, int flag)
{
 strcpy(source_path,skin->source_path);
 *fixed_size= skin->fixed_size;
 *tao_to_sao_tsize = skin->tao_to_sao_tsize;
 *use_data_image_size= skin->use_data_image_size;
 *padding= skin->padding;
 *set_by_padsize= skin->set_by_padsize;
 *track_type= skin->track_type;
 *track_type_by_default= skin->track_type_by_default;
 *mode_mods= skin->track_modemods;
 *swap_audio_bytes= skin->swap_audio_bytes;
 *cdxa_conversion= skin->cdxa_conversion;
 return(1);
}


#ifndef Cdrskin_extra_leaN

/** Return information about current fifo setting */
int Cdrskin_get_fifo_par(struct CdrskiN *skin, int *fifo_enabled,
                         int *fifo_size, int *fifo_start_at, int flag)
{
 *fifo_enabled= skin->fifo_enabled;
 *fifo_size= skin->fifo_size;
 *fifo_start_at= skin->fifo_start_at;
 return(1);
}


#ifndef Cdrskin_no_cdrfifO

/** Create and install fifo objects between track data sources and libburn.
    The sources and parameters are known to skin.
    @return <=0 error, 1 success
*/
int Cdrskin_attach_fifo(struct CdrskiN *skin, int flag)
{
 struct CdrfifO *ff= NULL;
 int ret,i,hflag;
 int profile_number;
 char profile_name[80];

 ret= Cdrskin_assert_driveno(skin, 0);
 if(ret <= 0)
   return(ret);

 ret= burn_disc_get_profile(skin->drives[skin->driveno].drive,
                            &profile_number, profile_name);

#ifdef Cdrskin_use_libburn_fifO

 /* Refuse here and thus use libburn fifo only with single track, non-CD */
 if(profile_number != 0x09 && profile_number != 0x0a &&
    skin->track_counter == 1)
   return(1);

#endif /* Cdrskin_use_libburn_fifO */

 skin->fifo= NULL;
 for(i=0;i<skin->track_counter;i++) {
   hflag= (skin->verbosity>=Cdrskin_verbose_debuG) |
          ((profile_number == 0x09 || profile_number == 0x0a) << 3);
   if(i==skin->track_counter-1)
     hflag|= 4;
   if(skin->verbosity>=Cdrskin_verbose_cmD) {
     if(skin->fifo_per_track)
       printf("cdrskin: track %d establishing fifo of %d bytes\n",
              i+1,skin->fifo_size);
     else if(i==0)
       printf("cdrskin: establishing fifo of %d bytes\n",skin->fifo_size);
     else {
       if(skin->verbosity>=Cdrskin_verbose_debuG)
        ClN(fprintf(stderr,"cdrskin_debug: attaching track %d to fifo\n",i+1));
       hflag|= 2;
     }
   }
   ret= Cdrtrack_attach_fifo(skin->tracklist[i],&(skin->fifo_outlet_fd),ff,
                             hflag);
   if(ret<=0) {
     fprintf(stderr,"cdrskin: FATAL : failed to attach fifo.\n");
     return(0);
   }
   if(i==0 || skin->fifo_per_track)
     Cdrtrack_get_fifo(skin->tracklist[i],&ff,0);
   if(i==0)
     skin->fifo= ff;
 }
 return(1);
}

#endif /* ! Cdrskin_no_cdrfifO */


/** Read data into the track fifos until either #1 is full or its data source
    is exhausted.
    @return <=0 error, 1 success
*/
int Cdrskin_fill_fifo(struct CdrskiN *skin, int flag)
{
 int ret;

 ret= Cdrtrack_fill_fifo(skin->tracklist[0],skin->fifo_start_at,0);
 if(ret<=0)
   return(ret);
 printf("input buffer ready.\n");
 fflush(stdout);
 return(1);
}

#endif /* ! Cdrskin_extra_leaN */


/** Inform libburn about the consumer x-speed factor of skin */
int Cdrskin_adjust_speed(struct CdrskiN *skin, int flag)
{
 int k_speed, modesty= 0;

 if(skin->x_speed<0)
   k_speed= 0; /* libburn.h promises 0 to be max speed. */
 else if(skin->x_speed==0) { /* cdrecord specifies 0 as minimum speed. */
   k_speed= -1;
 } else
   k_speed= skin->x_speed*Cdrskin_libburn_speed_factoR +
            Cdrskin_libburn_speed_addoN;
 if(skin->verbosity>=Cdrskin_verbose_debuG)
   ClN(fprintf(stderr,"cdrskin_debug: k_speed= %d\n",k_speed));

 if(skin->adjust_speed_to_drive && !skin->force_is_set) {
   struct burn_speed_descriptor *best_descr;
   burn_drive_get_best_speed(skin->drives[skin->driveno].drive,k_speed,
                             &best_descr,0);
   if(best_descr!=NULL) {
     k_speed= best_descr->write_speed;
     skin->x_speed = ((double) k_speed) / Cdrskin_libburn_speed_factoR;
   }  
 }
 burn_drive_set_speed(skin->drives[skin->driveno].drive,k_speed,k_speed);
 modesty= skin->modesty_on_drive;
 burn_drive_set_buffer_waiting(skin->drives[skin->driveno].drive,
                               modesty, skin->min_buffer_usec,
                               skin->max_buffer_usec, skin->buffer_timeout_sec,
                               skin->min_buffer_percent,
                               skin->max_buffer_percent);
 return(1);
}


int Cdrskin_determine_media_caps(struct CdrskiN *skin, int flag)
{
 int ret;
 struct burn_multi_caps *caps = NULL;
 
 skin->media_is_overwriteable= skin->media_does_multi= 0;
 ret= burn_disc_get_multi_caps(skin->grabbed_drive,BURN_WRITE_NONE,&caps,0);
 if(ret<=0)
   goto ex;
 skin->media_is_overwriteable= !!caps->start_adr;
 skin->media_does_multi= !!caps->multi_session;
 ret= 1;
ex:;
 if(caps != NULL)
   burn_disc_free_multi_caps(&caps);
 return(ret);
}


int Cdrskin__is_aborting(int flag)
{
 if(Cdrskin_abort_leveL)
   return(-1);
 return(burn_is_aborting(0));
}


int Cdrskin_abort(struct CdrskiN *skin, int flag)
{
 int ret;

 Cdrskin_abort_leveL= 1;
 ret= burn_abort(skin->abort_max_wait, burn_abort_pacifier, "cdrskin: ");
 if(ret<=0) {
   fprintf(stderr,
       "\ncdrskin: ABORT : Cannot cancel burn session and release drive.\n");
 } else {
   fprintf(stderr,
  "cdrskin: ABORT : Drive is released and library is shut down now.\n");
 }
 fprintf(stderr,
  "cdrskin: ABORT : Program done. Even if you do not see a shell prompt.\n");
 fprintf(stderr,"\n");
 exit(1);
}


/** Obtain access to a libburn drive for writing or information retrieval.
    If libburn is not restricted to a single persistent address then the
    unused drives are dropped. This might be done by shutting down and
    restartiing libburn with the wanted drive only. Thus, after this call,
    libburn is supposed to have open only the reserved drive.
    All other drives should be free for other use.
    Warning: Do not store struct burn_drive pointer over this call.
             Any such pointer might be invalid afterwards.
    @param flag Bitfield for control purposes:
                bit0= bus is unscanned, device is known, 
                      use burn_drive_scan_and_grab()
                bit1= do not load drive tray
                bit2= do not issue error message on failure
                bit3= demand and evtl. report media, return 0 if none to see
                bit4= grab drive with unsuitable media even if fallback program
                bit5= do not re-assess the drive if it is already grabbed
                      but rather perform a release-and-grab cycle
    @return <=0 error, 1 success
*/
int Cdrskin_grab_drive(struct CdrskiN *skin, int flag)
{
 int ret,i,profile_number, mem;
 struct burn_drive *drive;
 char profile_name[80];
 enum burn_disc_status s;

 if(skin->drive_is_grabbed && (flag & 32))
   Cdrskin_release_drive(skin,0);

 if(!skin->drive_is_grabbed) {
   if(flag&1) {
     skin->driveno= 0;
     drive= NULL;
     skin->grabbed_drive= drive;
   } else {
     ret= Cdrskin_assert_driveno(skin, 0);
     if(ret <= 0)
       return(ret);
     drive= skin->drives[skin->driveno].drive;
     skin->grabbed_drive= drive;
   }
 }
 if(skin->drive_is_grabbed) {
   drive= skin->grabbed_drive;
   mem= skin->drive_is_busy;
   skin->drive_is_busy= 2;
   ret= burn_drive_re_assess(skin->grabbed_drive, 0);
   skin->drive_is_busy= mem;
   if(Cdrskin__is_aborting(0)) {
     fprintf(stderr,"cdrskin: ABORT : Drive re-assessment aborted\n");
     Cdrskin_abort(skin, 0); /* Never comes back */
   }
   if(ret <= 0) {
     if(!(flag&4))
       fprintf(stderr,"cdrskin: FATAL : unable to re-assess drive '%s'\n",
               skin->preskin->device_adr);
     goto ex;
   }
 } else if(flag&1) {
   mem= skin->drive_is_busy;
   skin->drive_is_busy= 2;
   ret= burn_drive_scan_and_grab(&(skin->drives),skin->preskin->device_adr,
                                 (skin->do_load != -1) && !(flag&2));
   skin->drive_is_busy= mem;
   if(Cdrskin__is_aborting(0)) {
aborted:;
     fprintf(stderr,"cdrskin: ABORT : Drive aquiration aborted\n");
     Cdrskin_abort(skin, 0); /* Never comes back */
   }
   if(ret<=0) {
     if(!(flag&4))
       fprintf(stderr,"cdrskin: FATAL : unable to open drive '%s'\n",
               skin->preskin->device_adr);
     goto ex;
   }
   skin->driveno= 0;
   drive= skin->drives[skin->driveno].drive;
   skin->grabbed_drive= drive;
 } else {
   if(strlen(skin->preskin->device_adr)<=0) {
     if(skin->verbosity>=Cdrskin_verbose_debuG)   
       ClN(fprintf(stderr,
         "cdrskin_debug: Cdrskin_grab_drive() dropping unwanted drives (%d)\n",
          skin->n_drives-1));
     for(i= 0; i < (int) skin->n_drives; i++) {
       if(i==skin->driveno)
     continue;
       if(skin->verbosity>=Cdrskin_verbose_debuG)   
         ClN(fprintf(stderr,
           "cdrskin_debug: Cdrskin_grab_drive() dropped drive number %d\n",i));
       ret= burn_drive_info_forget(&(skin->drives[i]), 0);
       if(ret==1 || ret==2)
     continue;
       fprintf(stderr,
           "cdrskin: NOTE : Please inform libburn-hackers@pykix.org about:\n");
       fprintf(stderr,
           "cdrskin:        burn_drive_info_forget() returns %d\n",ret);
     }
   }

   mem= skin->drive_is_busy;
   skin->drive_is_busy= 2;
   ret= burn_drive_grab(drive,(skin->do_load != -1) && !(flag&2));
   skin->drive_is_busy= mem;
   if(Cdrskin__is_aborting(0))
     goto aborted;
   if(ret==0) {
     if(!(flag&4))
       fprintf(stderr,"cdrskin: FATAL : unable to open drive %d\n",
               skin->driveno);
     goto ex;
   }
 }
 if(skin->use_immed != 0)
   burn_drive_set_immed(drive, skin->use_immed > 0);
 skin->drive_is_grabbed= 1;

 s= burn_disc_get_status(drive);
 if((flag&8)) {
   if(skin->verbosity>=Cdrskin_verbose_progresS)
     Cdrskin_report_disc_status(skin,s,1);
   if(s==BURN_DISC_EMPTY) {
     Cdrskin_release_drive(skin,0);
     fprintf(stderr,"cdrskin: SORRY : No media found in drive\n");
     ret= 0; goto ex;
   }
 }

 Cdrskin_speed_factoR= Cdrskin_cd_speed_factoR;
 Cdrskin_libburn_speed_factoR= Cdrskin_libburn_cd_speed_factoR;
 Cdrskin_libburn_speed_addoN= Cdrskin_libburn_cd_speed_addoN;
 ret= burn_disc_get_profile(drive,&profile_number,profile_name);
 if(ret>0) {
   if(strstr(profile_name,"DVD")==profile_name ||
      strstr(profile_name,"stdio")==profile_name ) {
     Cdrskin_speed_factoR= Cdrskin_dvd_speed_factoR;
     Cdrskin_libburn_speed_factoR= Cdrskin_libburn_dvd_speed_factoR;
     Cdrskin_libburn_speed_addoN= Cdrskin_libburn_dvd_speed_addoN;
   } else if(strstr(profile_name,"BD")==profile_name) {
     Cdrskin_speed_factoR= Cdrskin_bd_speed_factoR;
     Cdrskin_libburn_speed_factoR= Cdrskin_libburn_bd_speed_factoR;
     Cdrskin_libburn_speed_addoN= Cdrskin_libburn_bd_speed_addoN;
   }
 }
 if(skin->preskin->fallback_program[0] && s==BURN_DISC_UNSUITABLE &&
    skin->preskin->demands_cdrskin_caps<=0 && !(flag&16)) {
   skin->preskin->demands_cdrecord_caps= 1;
   fprintf(stderr,
           "cdrskin: NOTE : Will delegate job to fallback program '%s'.\n",
           skin->preskin->fallback_program);
   Cdrskin_release_drive(skin,0);
   ret= 0; goto ex;
 }

 Cdrskin_determine_media_caps(skin,0);

 ret= 1;
ex:;
 if(ret<=0) {
   skin->drive_is_grabbed= 0;
   skin->grabbed_drive= NULL;
 }
 return(ret);
}


/** Release grabbed libburn drive 
    @param flag Bitfield for control purposes:
                bit0= eject
                bit1= leave tray locked (eventually overrides bit0)
*/
int Cdrskin_release_drive(struct CdrskiN *skin, int flag)
{
 if((!skin->drive_is_grabbed) || skin->grabbed_drive==NULL) {
   fprintf(stderr,"cdrskin: CAUGHT : release of non-grabbed drive.\n");
   return(0);
 }
 if(flag&2)
   burn_drive_leave_locked(skin->grabbed_drive,0);
 else
   burn_drive_release(skin->grabbed_drive,(flag&1));
 skin->drive_is_grabbed= 0;
 skin->grabbed_drive= NULL;
 return(1);
}


/** Clean up resources in abort situations. To be called by Cleanup subsystem
    but hardly ever by the application. The program must exit afterwards.
*/
int Cdrskin_abort_handler(struct CdrskiN *skin, int signum, int flag)
{
 struct burn_progress p;
 enum burn_drive_status drive_status= BURN_DRIVE_SPAWNING;

/*
 fprintf(stderr,
"cdrskin_DEBUG: Cdrskin_abort_handler: signum=%d, flag=%d, drive_is_busy=%d\n",
         signum, flag, skin->drive_is_busy);
*/

 if(skin->drive_is_busy == 0)
   Cdrskin_abort(skin, 0); /* Never comes back */

 if(getpid()!=skin->control_pid) {
   if(skin->verbosity>=Cdrskin_verbose_debuG)   
     ClN(fprintf(stderr,
        "\ncdrskin_debug: ABORT : [%lu] Thread rejected: pid=%lu, signum=%lu\n",
        (unsigned long int) skin->control_pid, (unsigned long int) getpid(),
        (unsigned long int) signum));

#ifdef Not_yeT
   /* >>> find more abstract and system independent way to determine 
          signals which make no sense with a return */
   if(signum==11) {
     Cleanup_set_handlers(NULL,NULL,1); /* allow abort */
     return(0); /* let exit */
   }
#endif
   usleep(1000000);

   return(-2); /* do only process the control thread */
 }
 if(skin->preskin->abort_handler==3)
   Cleanup_set_handlers(NULL,NULL,2); /* ignore all signals */
 else if(skin->preskin->abort_handler==4)
   Cleanup_set_handlers(NULL,NULL,1); /* allow abort */
 fprintf(stderr,
     "\ncdrskin: ABORT : Handling started. Please do not press CTRL+C now.\n");
 if(skin->preskin->abort_handler==3)
   fprintf(stderr,"cdrskin: ABORT : Trying to ignore any further signals\n");

#ifndef Cdrskin_no_cdrfifO
 if(skin->fifo!=NULL)
   Cdrfifo_close_all(skin->fifo,0);
#endif

 /* Only for user info */
 if(skin->grabbed_drive!=NULL)
   drive_status= burn_drive_get_status(skin->grabbed_drive,&p);
 if(drive_status!=BURN_DRIVE_IDLE) {
   fprintf(stderr,"cdrskin: ABORT : Abort processing depends on speed and buffer size\n");
   fprintf(stderr,"cdrskin: ABORT : Usually it is done with 4x speed after about a MINUTE\n");
   fprintf(stderr,"cdrskin: URGE  : But wait at least the normal burning time before any kill -9\n");
 }

 Cdrskin_abort_leveL= -1;
 if (!(flag & 1)) {
   if(skin->verbosity>=Cdrskin_verbose_debuG)
     ClN(fprintf(stderr,"cdrskin: ABORT : Calling burn_abort(-1)\n"));
   burn_abort(-1, burn_abort_pacifier, "cdrskin: ");
 }
 fprintf(stderr,
        "cdrskin: ABORT : Urged drive worker threads to do emergency halt.\n");
 return(-2);
}


/** Convert a libburn device address into a libburn drive number
    @return <=0 error, 1 success
*/
int Cdrskin_driveno_of_location(struct CdrskiN *skin, char *devicename,
                                int *driveno, int flag)
{
 int i,ret;
 char adr[Cdrskin_adrleN];

 for(i= 0; i < (int) skin->n_drives; i++) {
   ret= burn_drive_get_adr(&(skin->drives[i]), adr);
   if(ret<=0)
 continue;
   if(strcmp(adr,devicename)==0) {
     *driveno= i;
     return(1);
   }
 }
 return(0);
}


/** Convert a cdrskin address into a libburn drive number
    @return <=0 error, 1 success
*/
int Cdrskin_dev_to_driveno(struct CdrskiN *skin, char *in_adr, int *driveno,
                           int flag)
{
 int ret;
 char *adr,translated_adr[Cdrskin_adrleN],synthetic_adr[Cdrskin_adrleN];

 adr= in_adr;

#ifndef Cdrskin_extra_leaN
 /* user defined address translation */
 ret= Cdradrtrn_translate(skin->adr_trn,adr,-1,translated_adr,0);
 if(ret<=0) {
   fprintf(stderr,
        "cdrskin: FATAL : address translation failed (address too long ?) \n");
   return(0);
 }
 if(skin->verbosity>=Cdrskin_verbose_cmD && strcmp(adr,translated_adr)!=0)
   printf("cdrskin: dev_translation=... :  dev='%s' to dev='%s'\n",
          adr,translated_adr);
 adr= translated_adr;
#endif /* ! Cdrskin_extra_leaN */

 if(strncmp(adr, "stdio:", 6)==0) {
   if(skin->n_drives<=0)
     goto wrong_devno;
   *driveno= 0;
   return(1);
 } else if(adr[0]=='/') {
   ret= Cdrskin_driveno_of_location(skin,adr,driveno,0);
   if(ret<=0) {
location_not_found:;
     fprintf(stderr,
         "cdrskin: FATAL : cannot find '%s' among accessible drive devices.\n",
            adr);
     fprintf(stderr,
      "cdrskin: HINT : use option  --devices  for a list of drive devices.\n");
     return(0);
   }
   return(1);
 }
 ret= Cdrpreskin__cdrecord_to_dev(adr,synthetic_adr,driveno,
                                  !!skin->preskin->old_pseudo_scsi_adr);
 if(ret<=0) {
wrong_devno:;
   if(skin->n_drives<=0) {
     fprintf(stderr,"cdrskin: FATAL : No accessible drives.\n");
   } else {
     fprintf(stderr,
         "cdrskin: FATAL : Address does not lead to an accessible drive: %s\n",
             in_adr);
     fprintf(stderr,
    "cdrskin: HINT : dev= expects /dev/xyz, Bus,Target,0 or a number [0,%d]\n",
           skin->n_drives-1);
   }
   return(0);
 }
 if(strlen(synthetic_adr)>0) {
   if(skin->verbosity>=Cdrskin_verbose_cmD)
    ClN(printf("cdrskin: converted address '%s' to '%s'\n",adr,synthetic_adr));
   ret= Cdrskin_driveno_of_location(skin,synthetic_adr,driveno,0);
   if(ret<=0) {
     fprintf(stderr,
             "cdrskin: failure while using address converted from '%s'\n",adr);
     adr= synthetic_adr;
     goto location_not_found;
   }
 }
 if((unsigned int) (*driveno) >= skin->n_drives || (*driveno) < 0) {
   ClN(fprintf(stderr,"cdrskin: obtained drive number  %d  from '%s'\n",
               *driveno,adr));
   goto wrong_devno;
 }
 return(1);
}


/** Convert a libburn drive number into a cdrecord-style address which 
    represents a device address if possible and the drive number else.
    @param flag Bitfield for control purposes: 
                bit0= do not apply user defined address translation
    @return <0 error,
                pseudo transport groups:
                 0 volatile drive number, 
                 1 /dev/sgN, 2 /dev/hdX, 3 stdio,
                 1000000+busno = non-pseudo SCSI bus
                 2000000+busno = pseudo-ATA|ATAPI SCSI bus (currently busno==2)
*/
int Cdrskin_driveno_to_btldev(struct CdrskiN *skin, int driveno,
                              char btldev[Cdrskin_adrleN], int flag)
{
 int k,ret,still_untranslated= 1,hret,k_start;
 char *loc= NULL,buf[Cdrskin_adrleN],adr[Cdrskin_adrleN];

 if(driveno < 0 || driveno > (int) skin->n_drives)
   goto fallback;

 ret= burn_drive_get_adr(&(skin->drives[driveno]), adr);
 if(ret<=0)
   goto fallback;
 loc= adr;
 ret= burn_drive_get_drive_role(skin->drives[driveno].drive);
 if(ret!=1) {
   sprintf(btldev,"stdio:%s",adr);
   {ret= 2; goto adr_translation;}
 }

 if(!skin->preskin->old_pseudo_scsi_adr) {
   int host_no= -1,channel_no= -1,target_no= -1,lun_no= -1, bus_no= -1;

   ret= burn_drive_obtain_scsi_adr(loc,&bus_no,&host_no,&channel_no,
                                   &target_no,&lun_no); 
   if(ret<=0) {
     if(strncmp(loc,"/dev/hd",7)==0)
       if(loc[7]>='a' && loc[7]<='z')
         if(loc[8]==0) {
           bus_no= (loc[7]-'a')/2;
           sprintf(btldev,"%d,%d,0",bus_no,(loc[7]-'a')%2);
           {ret= 2000000 + bus_no; goto adr_translation;}
         }
     goto fallback;
   } else {
     sprintf(btldev,"%d,%d,%d",bus_no,target_no,lun_no);
     ret= 1000000+bus_no;
     goto adr_translation;
   }
 }

 k_start= 0;
 if(strncmp(loc,"/dev/sg",7)==0 || strncmp(loc,"/dev/sr",7)==0)
   k_start= 7;
 if(strncmp(loc,"/dev/scd",8)==0)
   k_start= 8;
 if(k_start>0) {
   for(k= k_start;loc[k]!=0;k++)
     if(loc[k]<'0' || loc[k]>'9')
   break;
   if(loc[k]==0 && k>k_start) {
     sprintf(btldev,"1,%s,0",loc+k_start);
     {ret= 1; goto adr_translation;}
   }
 }
 if(strncmp(loc,"/dev/hd",7)==0)
   if(loc[7]>='a' && loc[7]<='z')
     if(loc[8]==0) {
       sprintf(btldev,"2,%d,0",loc[7]-'a');
       {ret= 2; goto adr_translation;}
     }
fallback:;
 if(skin->preskin->old_pseudo_scsi_adr) {
   sprintf(btldev,"0,%d,0",driveno);
 } else {
   if(loc!=NULL)
     strcpy(btldev,loc);
   else
     sprintf(btldev,"%d",driveno);
 }
 ret= 0;

adr_translation:;
#ifndef Cdrskin_extra_leaN
 /* user defined address translation */
 if(!(flag&1)) {
   if(ret>0) {
     /* try whether a translation points to loc */
     hret= Cdradrtrn_translate(skin->adr_trn,loc,driveno,buf,1);
     if(hret==2) {
       still_untranslated= 0;
       strcpy(btldev,buf);
     }
   }
   if(still_untranslated) {
     Cdradrtrn_translate(skin->adr_trn,btldev,driveno,buf,1);
     strcpy(btldev,buf);
   }
 }
#endif /* ! Cdrskin_extra_leaN */

 return(ret);
}


/** Read and buffer the start of an existing ISO-9660 image from
    overwriteable target media.
*/
int Cdrskin_overwriteable_iso_size(struct CdrskiN *skin, int *size, int flag)
{
 int ret;
 off_t data_count= 0;
 double size_in_bytes;
 char *buf;

 buf= skin->overwriteable_iso_head;
 if(!skin->media_is_overwriteable)
   {ret= 0; goto ex;}
 /* Read first 64 kB */
 ret= burn_read_data(skin->grabbed_drive,(off_t) 0,buf,32*2048,&data_count,0);
 if(ret<=0)
   {ret= 0; goto ex;}
 ret= Scan_for_iso_size((unsigned char *) (buf+16*2048), &size_in_bytes,0);
 if(ret<=0) {
   if(skin->verbosity>=Cdrskin_verbose_debuG)
  ClN(fprintf(stderr,"cdrskin_debug: No detectable ISO-9660 size on media\n"));
   {ret= 0; goto ex;}
 }
 if(skin->verbosity>=Cdrskin_verbose_debuG)
   ClN(fprintf(stderr,"cdrskin_debug: detected ISO-9660 size : %.f  (%fs)\n",
               size_in_bytes, size_in_bytes/2048.0));
 if(size_in_bytes/2048.0>2147483647-1-16) {
   fprintf(stderr,
           "cdrskin: FATAL : ISO-9660 filesystem in terabyte size detected\n");
   {ret= 0; goto ex;}
 }
 *size= size_in_bytes/2048.0;
 if(size_in_bytes-((double) *size)*2048.0>0.0)
   (*size)++;
 if((*size)%16)
   *size+= 16-((*size)%16);
 if(skin->grow_overwriteable_iso==1)
   skin->grow_overwriteable_iso= 2;
 ret= 1;
ex:;
 return(ret);
}     


int Cdrskin_invalidate_iso_head(struct CdrskiN *skin, int flag)
{
 int ret;
 int size;

 fprintf(stderr,
   "cdrskin: blank=... : invalidating ISO-9660 head on overwriteable media\n");
 ret= Cdrskin_overwriteable_iso_size(skin,&size,0);
 if(ret<=0) {
   fprintf(stderr,
           "cdrskin: NOTE : Not an ISO-9660 file system. Left unaltered.\n");
   return(2);
 }
 skin->overwriteable_iso_head[16*2048]=
 skin->overwriteable_iso_head[16*2048+3]= 
 skin->overwriteable_iso_head[16*2048+4]= 'x';
 ret= burn_random_access_write(skin->grabbed_drive,(off_t) 16*2048,
                               skin->overwriteable_iso_head+16*2048,
                               (off_t) 16*2048,1);
 return(ret);
}


/** Report media status s to the user
    @param flag Bitfield for control purposes: 
                bit0= permission to check for overwriteable ISO image
                bit1= do not report media profile
                bit2= do not report but only check for pseudo appendable
    @return 1=ok, 2=ok, is pseudo appendable, <=0 error
*/
int Cdrskin_report_disc_status(struct CdrskiN *skin, enum burn_disc_status s,
                               int flag)
{
 int ret, iso_size, pseudo_appendable= 0;

 if(flag&1) {
   if(skin->media_is_overwriteable && skin->grow_overwriteable_iso>0) {
     if(skin->grow_overwriteable_iso==2)
       ret= 1;
     else
       ret= Cdrskin_overwriteable_iso_size(skin,&iso_size,0);
     if(ret>0) {
       s= BURN_DISC_APPENDABLE;
       pseudo_appendable= 1;
     }
   }
 } 
 if(flag&4)
   return(1+pseudo_appendable);

 printf("cdrskin: status %d ",s);
 if(s==BURN_DISC_FULL) {
   printf("burn_disc_full \"There is a disc with data on it in the drive\"\n"); 
 } else if(s==BURN_DISC_BLANK) {
   printf("burn_disc_blank \"The drive holds a blank disc\"\n"); 
 } else if(s==BURN_DISC_APPENDABLE) {
   printf(
        "BURN_DISC_APPENDABLE \"There is an incomplete disc in the drive\"\n");
 } else if(s==BURN_DISC_EMPTY) {
   printf("BURN_DISC_EMPTY \"There is no disc at all in the drive\"\n");
 } else if(s==BURN_DISC_UNREADY) {
   printf("BURN_DISC_UNREADY \"The current status is not yet known\"\n");
 } else if(s==BURN_DISC_UNGRABBED) {
   printf("BURN_DISC_UNGRABBED \"API usage error: drive not grabbed\"\n");
 } else if(s==BURN_DISC_UNSUITABLE) {
   printf("BURN_DISC_UNSUITABLE \"Media is not suitable\"\n");
 } else 
   printf("-unknown status code-\n");

 if(flag&2)
   return(1+pseudo_appendable);

 if((s==BURN_DISC_FULL || s==BURN_DISC_APPENDABLE || s==BURN_DISC_BLANK ||
     s==BURN_DISC_UNSUITABLE) && skin->driveno>=0) {
   char profile_name[80];
   int profile_number;

   printf("Current: ");
   ret= burn_disc_get_profile(skin->drives[skin->driveno].drive,
                              &profile_number,profile_name);
   if(ret>0 && profile_name[0]!=0)
     printf("%s\n", profile_name);
   else if(ret>0)
     printf("UNSUITABLE MEDIA (Profile %4.4Xh)\n",profile_number);
   else
     printf("-unidentified-\n");
 } else if(s==BURN_DISC_EMPTY) {
   printf("Current: none\n");
 }

 return(1+pseudo_appendable);
}


/** Perform operations -scanbus or --devices 
    @param flag Bitfield for control purposes: 
                bit0= perform --devices rather than -scanbus
                bit1= with bit0: perform --device_links
    @return <=0 error, 1 success
*/
int Cdrskin_scanbus(struct CdrskiN *skin, int flag)
{
 int ret,i,busno,first_on_bus,pseudo_transport_group= 0,skipped_devices= 0;
 int busmax= 16, busidx, max_dev_len, pad, j;
 char shellsafe[5*Cdrskin_strleN+2],perms[40],btldev[Cdrskin_adrleN];
 char adr[Cdrskin_adrleN],*raw_dev,*drives_shown= NULL;
 char link_adr[BURN_DRIVE_ADR_LEN];
 int *drives_busses= NULL;
 struct stat stbuf;

 drives_shown= calloc(1, skin->n_drives+1);
 drives_busses= calloc((skin->n_drives+1), sizeof(int));
 if(drives_shown == NULL || drives_busses == NULL)
   {ret= -1; goto ex;}

 if(flag&1) {
   max_dev_len= 0;
   for(i= 0; i < (int) skin->n_drives; i++) {
     drives_shown[i]= 0;
     ret= burn_drive_get_adr(&(skin->drives[i]), adr);
     if(ret<=0)
   continue;
     if(flag & 2) {
       ret= burn_lookup_device_link(adr, link_adr, "/dev", NULL, 0, 0);
       if(ret == 1)
         strcpy(adr, link_adr);
     }
     if(strlen(adr)>=Cdrskin_strleN)
       Text_shellsafe("failure:oversized string", shellsafe, 0);
     else
       Text_shellsafe(adr, shellsafe,0);
     if((int) strlen(shellsafe) > max_dev_len)
       max_dev_len= strlen(shellsafe);
   }
   printf("cdrskin: Overview of accessible drives (%d found) :\n",
          skin->n_drives);
   printf("-----------------------------------------------------------------------------\n");
   for(i= 0; i < (int) skin->n_drives; i++) {
     ret= burn_drive_get_adr(&(skin->drives[i]), adr);
     if(ret<=0) {
       /* >>> one should massively complain */;
   continue;
     }
     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';
     }
     if(flag & 2) {
       ret= burn_lookup_device_link(adr, link_adr, "/dev", NULL, 0, 0);
       if(ret == 1)
         strcpy(adr, link_adr);
     }
     if(strlen(adr)>=Cdrskin_strleN)
       Text_shellsafe("failure:oversized string",shellsafe,0);
     else
       Text_shellsafe(adr,shellsafe,0);
     printf("%d  dev=%s", i, shellsafe);
     pad= max_dev_len - strlen(shellsafe);
     if(pad > 0)
       for(j= 0; j < pad; j++)
         printf(" ");
     printf("  %s :  '%-8.8s'  '%s'\n",
            perms, skin->drives[i].vendor, skin->drives[i].product);
   }
   printf("-----------------------------------------------------------------------------\n");
 } else {
   if(!skin->preskin->old_pseudo_scsi_adr) {
     pseudo_transport_group= 1000000;
     raw_dev= skin->preskin->raw_device_adr;
     if(strncmp(raw_dev,"ATA",3)==0 && (raw_dev[3]==0 || raw_dev[3]==':'))
       pseudo_transport_group= 2000000;
     if(strncmp(raw_dev,"ATAPI",5)==0 && (raw_dev[5]==0 || raw_dev[5]==':'))
       pseudo_transport_group= 2000000;
     if(pseudo_transport_group==2000000) {
       fprintf(stderr,"scsidev: 'ATA'\ndevname: 'ATA'\n");
       fprintf(stderr,"scsibus: -2 target: -2 lun: -2\n");
     }
   }
   /* >>> fprintf(stderr,"Linux sg driver version: 3.1.25\n"); */
   printf("Using libburn version '%s'.\n", Cdrskin_libburn_versioN);
   if(pseudo_transport_group!=1000000)
   if(skin->preskin->old_pseudo_scsi_adr)
     printf("cdrskin: NOTE : The printed addresses are not cdrecord compatible !\n");

   for(i= 0; i < (int) skin->n_drives; i++) {
     drives_busses[i]= -1;
     ret= Cdrskin_driveno_to_btldev(skin,i,btldev,1);
     if(ret >= pseudo_transport_group &&
        ret < pseudo_transport_group + 1000000) {
       drives_busses[i]= ret - pseudo_transport_group;
       if(ret > pseudo_transport_group + busmax)
         busmax= 1 + ret - pseudo_transport_group;
     }
   }
   for(busidx= 0; busidx < (int) skin->n_drives + 1; busidx++) {
     if(busidx < (int) skin->n_drives)
       busno= drives_busses[busidx];
     else
       busno= busmax;
     if(busno < 0)
   continue;
     for(i= 0; i < busidx; i++)
       if(drives_busses[i] == busno)
     break;
     if(i < busidx)
   continue;
     first_on_bus= 1;
     for(i= 0; i < (int) skin->n_drives; i++) {
       ret= Cdrskin_driveno_to_btldev(skin,i,btldev,1);
       if(busno==busmax && drives_shown[i]==0) {
         if(ret/1000000 != pseudo_transport_group) {
           skipped_devices++;
           if(skin->verbosity>=Cdrskin_verbose_debuG)
             ClN(fprintf(stderr,"cdrskin_debug: skipping drive '%s%s'\n",
                         ((ret/1000000)==2?"ATA:":""), btldev));
     continue;
         }
       } else if(ret != pseudo_transport_group + busno)
     continue;
       if(first_on_bus)
         printf("scsibus%d:\n",busno);
       first_on_bus= 0;
       printf("\t%s\t  %d) '%-8s' '%-16s' '%-4s' Removable CD-ROM\n",
              btldev,i,skin->drives[i].vendor,skin->drives[i].product,
              skin->drives[i].revision);
       drives_shown[i]= 1;
     }
   }
 }
 if(skipped_devices>0) {
   if(skipped_devices>1)
     printf("cdrskin: NOTE : There were %d drives not shown.\n",
            skipped_devices);
   else
     printf("cdrskin: NOTE : There was 1 drive not shown.\n");
   printf("cdrskin: HINT : To surely see all drives try option: --devices\n");
   if(pseudo_transport_group!=2000000)
     printf("cdrskin: HINT : or try options:               dev=ATA -scanbus\n");
 }
 ret= 1;
ex:;
 if(drives_shown!=NULL)
   free((char *) drives_shown);
 if(drives_busses!=NULL)
   free((char *) drives_busses);
 return(ret);
}


/** Perform -checkdrive .
    @param flag Bitfield for control purposes:
                bit0= do not print message about pseudo-checkdrive
    @return <=0 error, 1 success
*/
int Cdrskin_checkdrive(struct CdrskiN *skin, char *profile_name, int flag)
{
 struct burn_drive_info *drive_info;
 int ret;
 char btldev[Cdrskin_adrleN];
 char *sno= NULL;
 int sno_len = 0;

 if(!(flag&1)) {
   if(flag&2)
     ClN(printf("cdrskin: pseudo-inquiry on drive %d\n",skin->driveno));
   else
     ClN(printf("cdrskin: pseudo-checkdrive on drive %d\n",skin->driveno));
 }
 if(skin->driveno >= (int) skin->n_drives || skin->driveno < 0) {
   fprintf(stderr,"cdrskin: FATAL : there is no drive #%d\n",skin->driveno);
   {ret= 0; goto ex;}  
 } 
 drive_info= &(skin->drives[skin->driveno]);
 ret= Cdrskin_driveno_to_btldev(skin,skin->driveno,btldev,0);
 if(ret>=0)
   fprintf(stderr,"scsidev: '%s'\n",btldev);
 printf("Device type    : ");
 ret= burn_drive_get_drive_role(drive_info->drive);
 if(ret==0)
   printf("%s\n","Emulated (null-drive)");
 else if(ret==2)
   printf("%s\n","Emulated (stdio-drive, 2k random read-write)");
 else if(ret==3)
   printf("%s\n","Emulated (stdio-drive, sequential write-only)");
 else if(ret==4)
   printf("%s\n","Emulated (stdio-drive, 2k random read-only)");
 else if(ret == 5)
   printf("%s\n","Emulated (stdio-drive, 2k random write-only)");
 else if(ret!=1)
   printf("%s\n","Emulated (stdio-drive)");
 else
   printf("%s\n","Removable CD-ROM");
 printf("Vendor_info    : '%s'\n",drive_info->vendor);
 printf("Identifikation : '%s'\n",drive_info->product);
 printf("Revision       : '%s'\n",drive_info->revision);

 if(flag&2)
   {ret= 1; goto ex;}

 burn_drive_get_serial_no(drive_info->drive, &sno, &sno_len);
 if(sno_len > 0)
   printf("Drive id       : '%s'\n", sno);
 if(sno != NULL)
   free(sno);
 sno= NULL;
 printf("Driver flags   : %s\n","BURNFREE");
 printf("Supported modes:");
 if((drive_info->tao_block_types & (BURN_BLOCK_MODE1))
    == (BURN_BLOCK_MODE1))
   printf(" TAO");
 if(drive_info->sao_block_types & BURN_BLOCK_SAO)
   printf(" SAO");

#ifndef Cdrskin_disable_raw96R
 if((drive_info->raw_block_types & BURN_BLOCK_RAW96R) &&
    strstr(profile_name,"DVD")!=profile_name &&
    strstr(profile_name,"BD")!=profile_name)
   printf(" RAW/RAW96R");
#endif /* ! Cdrskin_disable_raw96R */

 printf("\n");
 ret= 1;
ex:;
 return(ret);
}


/** Predict address block number where the next write will go to
    @param flag Bitfield for control purposes:
                bit0= do not return nwa from eventual write_start_address
    @return <=0 error, 1 nwa from drive , 2 nwa from write_start_address
*/
int Cdrskin_obtain_nwa(struct CdrskiN *skin, int *nwa, int flag)
{
 int ret,lba;
 struct burn_drive *drive;
 struct burn_write_opts *o= NULL;

 if(skin->write_start_address>=0 && !(flag&1)) {
   /* (There is no sense in combining random addressing with audio) */
   *nwa= skin->write_start_address/2048;
   return(2);
 }

 /* Set write opts in order to provoke MODE SELECT. LG GSA-4082B needs it. */
 drive= skin->drives[skin->driveno].drive;
 o= burn_write_opts_new(drive);
 if(o!=NULL) {
   burn_write_opts_set_perform_opc(o, 0);
   burn_write_opts_set_write_type(o,skin->write_type,skin->block_type);
   burn_write_opts_set_underrun_proof(o,skin->burnfree);
 }
 ret= burn_disc_track_lba_nwa(drive,o,0,&lba,nwa);
 if(o!=NULL)
   burn_write_opts_free(o);
 return(ret);
}


/* @param flag bit0-3= source of text packs:
                        0= CD Lead-in
                        1= session and tracks
                        2= textfile=
               bit4-7= output format
                        0= -vv -toc
                        1= Sony CD-TEXT Input Sheet Version 0.7T
*/
int Cdrskin_print_text_packs(struct CdrskiN *skin, unsigned char *text_packs,
                             int num_packs, int flag)
{
 int i, j, from, fmt, ret, char_code= 0;
 char *from_text= "", *result= NULL;
 unsigned char *pack;

 from= flag & 15;
 fmt= (flag >> 4) & 15;
 if(from == 0)
   from_text= " from CD Lead-in";
 else if(from == 1)
   from_text= " from session and tracks";
 else if(from == 2)
   from_text= " from textfile= or cuefile= CDTEXTFILE";
 if (fmt == 1) {
   ret = burn_make_input_sheet_v07t(text_packs, num_packs, 0, 0, &result,
                                    &char_code, 0);
   if(ret <= 0)
     goto ex;
   if(char_code == 0x80)
     fprintf(stderr,
             "cdrskin : WARNING : Double byte characters encountered.\n");
   for(i = 0; i < ret; i++)
     printf("%c", result[i]);
 } else {
   printf("CD-TEXT data%s:\n", from_text);
   for(i= 0; i < num_packs; i++) {
     pack= text_packs + 18 * i;
     printf("%4d :", i);
     for(j= 0; j < 18; j++) {
       if(j >= 4 && j <= 15 && pack[j] >= 32 && pack[j] <= 126 &&
          pack[0] != 0x88 && pack[0] != 0x89 && pack[0] != 0x8f)
         printf("  %c",  pack[j]);
       else
         printf(" %2.2x", pack[j]);
     }
     printf("\n");
   }
 }
 ret= 1;
ex:;
 if(result != NULL)
   free(result);
 return(ret);
}


/* @param flag bit4= no not overwrite existing target file
*/
int Cdrskin_store_text_packs(struct CdrskiN *skin, unsigned char *text_packs,
                             int num_packs, char *path, int flag)
{
 int data_length, ret;
 struct stat stbuf;
 FILE *fp;
 unsigned char fake_head[4];

 if(stat(path, &stbuf) != -1 && (flag & 16)) {
   fprintf(stderr, "cdrskin: SORRY : Will not overwrite file '%s'\n", path);
   return(0);
 }
 fp= fopen(path, "w");
 if(fp == NULL) {
   fprintf(stderr, "cdrskin: SORRY : Cannot open file '%s' for storing extracted CD-TEXT\n", path);
   fprintf(stderr, "cdrskin: %s (errno=%d)\n", strerror(errno), errno);
   return(0);
 }
 data_length= num_packs * 18 + 2;
 fake_head[0]= (data_length >> 8) & 0xff;
 fake_head[1]= data_length & 0xff;
 fake_head[2]= fake_head[3]= 0;
 ret= fwrite(fake_head, 4, 1, fp);
 if(ret != 1) {
write_failure:;
   fprintf(stderr,
           "cdrskin: SORRY : Cannot write all data to file '%s'\n", path);
   fprintf(stderr, "cdrskin: %s (errno=%d)\n", strerror(errno), errno);
   fclose(fp);
   return(0);
 }
 ret= fwrite(text_packs, data_length - 2, 1, fp);
 if(ret != 1)
   goto write_failure;
 fprintf(stderr,
    "cdrskin: NOTE : Wrote header and %d CD-TEXT bytes to file '%s'\n",
         data_length - 2, path);
 fclose(fp);
 return(1);
}


/* @param flag bit0-3= output format
                       1= Sony CD-TEXT Input Sheet Version 0.7T
                      15= Cdrskin_store_text_packs
               bit4= do not overwrite existing the target file
*/
int Cdrskin_cdtext_to_file(struct CdrskiN *skin, char *path, int flag)
{
 int ret, fmt, char_code= 0, to_write;
 struct burn_drive *drive;
 unsigned char *text_packs= NULL;
 int num_packs= 0;
 char *result= 0;
 FILE *fp= NULL;
 struct stat stbuf;

 ret= Cdrskin_grab_drive(skin, 0);
 if(ret<=0)
   goto ex;

 fmt= flag & 15;
 drive= skin->drives[skin->driveno].drive;
 ret= burn_disc_get_leadin_text(drive, &text_packs, &num_packs, 0);
 if(ret <= 0 || num_packs <= 0) {
   fprintf(stderr, "cdrskin: No CD-Text or CD-Text unaware drive.\n");
   ret= 2; goto ex;
 }  
 if(fmt == 1) {
   ret = burn_make_input_sheet_v07t(text_packs, num_packs, 0, 0, &result,
                                    &char_code, 0);
   if(ret <= 0)
     goto ex;
   to_write= ret;
   if(stat(path, &stbuf) != -1 && (flag & 16)) {
     fprintf(stderr, "cdrskin: SORRY : Will not overwrite file '%s'\n", path);
     return(0);
   }
   if(strcmp(path, "-") == 0)
     fp= stdout;
   else
     fp = fopen(path, "w");
   if(fp == NULL) {
     if(errno > 0)
       fprintf(stderr, "cdrskin: %s (errno=%d)\n", strerror(errno), errno);
     fprintf(stderr,
             "cdrskin: SORRY : Cannot write CD-TEXT list to file '%s'\n",
             path);
     {ret= 0; goto ex;}
   }
   ret = fwrite(result, to_write, 1, fp);
   if(ret != 1) {
     if(errno > 0)
       fprintf(stderr, "cdrskin: %s (errno=%d)\n", strerror(errno), errno);
     fprintf(stderr,
            "cdrskin: SORRY : Cannot write all CD-TEXT to file '%s'\n", path);
     ret= 0;
   }
   if(ret <= 0)
     goto ex;

 } else if(fmt == 15) {
   if(skin->verbosity >= Cdrskin_verbose_debuG)
     Cdrskin_print_text_packs(skin, text_packs, num_packs, 0);
   ret= Cdrskin_store_text_packs(skin, text_packs, num_packs, path, flag & 16);
   if(ret <= 0)
     goto ex;
   printf("CD-Text len: %d\n", num_packs * 18 + 4);

 } else {
   fprintf(stderr, "cdrskin: FATAL : Program error : Unknown format %d with Cdrskin_cdtext_to_file.\n", fmt);
   {ret= -1; goto ex;}
 }
 ret= 1;
ex:;
 if(result != NULL)
   free(result);
 if(text_packs != NULL)
   free(text_packs);
 if(fp != NULL && fp != stdout)
   fclose(fp);
 return(1);
}


/** Perform -toc under control of Cdrskin_atip().
    @param flag Bitfield for control purposes:
                bit0= do not list sessions separately (do it cdrecord style)
    @return <=0 error, 1 success
*/
int Cdrskin_toc(struct CdrskiN *skin, int flag)
{
 int num_sessions= 0,num_tracks= 0,lba= 0,track_count= 0,total_tracks= 0;
 int session_no, track_no, pmin, psec, pframe, ret, final_ret= 1;
 int track_offset = 1, open_sessions= 0, have_real_open_session= 0;
 struct burn_drive *drive;
 struct burn_disc *disc= NULL;
 struct burn_session **sessions;
 struct burn_track **tracks;
 struct burn_toc_entry toc_entry;
 enum burn_disc_status s;
 char profile_name[80];
 int profile_number, is_bdr_pow= 0;

 drive= skin->drives[skin->driveno].drive;

 s= burn_disc_get_status(drive);
 if(s == BURN_DISC_EMPTY || s == BURN_DISC_BLANK)
   goto summary;
 disc= burn_drive_get_disc(drive);
 if(disc==NULL) {
   if(skin->grow_overwriteable_iso>0) {
     ret= Cdrskin_overwriteable_iso_size(skin,&lba,0);
     if(ret>0) {
       printf(
"first: 1 last: 1  (fabricated from ISO-9660 image on overwriteable media)\n");
       printf(
"track:   1 lba:         0 (        0) 00:02:00 adr: 1 control: 4 mode: 1\n");
       burn_lba_to_msf(lba, &pmin, &psec, &pframe);
       printf("track:lout lba: %9d (%9d) %2.2d:%2.2d:%2.2d",
          lba,4*lba,pmin,psec,pframe);
       printf(" adr: 1 control: 4 mode: -1\n");
       goto summary;
     }
   }
   goto cannot_read;
 }
 sessions= burn_disc_get_sessions(disc,&num_sessions);
 open_sessions= burn_disc_get_incomplete_sessions(disc);
 if(num_sessions > 0)
   track_offset = burn_session_get_start_tno(sessions[0], 0);
 if(track_offset <= 0)
   track_offset= 1;
 if(flag&1) {
   for(session_no= 0; session_no < num_sessions + open_sessions;
       session_no++) {
     tracks= burn_session_get_tracks(sessions[session_no],&num_tracks);
     total_tracks+= num_tracks;
     if(session_no == num_sessions + open_sessions - 1 && open_sessions > 0) {
       total_tracks--; /* Do not count invisible track */
       if(num_tracks > 1)
         have_real_open_session= 1;
     }
   }
   printf("first: %d last %d\n",
          track_offset, total_tracks + track_offset - 1);
 }
 for(session_no= 0; session_no < num_sessions + open_sessions; session_no++) {
   tracks= burn_session_get_tracks(sessions[session_no],&num_tracks);
   if(tracks==NULL)
 continue;
   if(session_no == num_sessions + open_sessions - 1 && open_sessions > 0)
     num_tracks--;
   if(num_tracks <= 0)
 continue;
   if(!(flag&1))
     printf("first: %d last: %d\n",
            track_count + track_offset,
            track_count + num_tracks + track_offset - 1);
   for(track_no= 0; track_no<num_tracks; track_no++) {
     track_count++;
     burn_track_get_entry(tracks[track_no], &toc_entry);
     if(toc_entry.extensions_valid&1) { /* DVD extension valid */
       lba= toc_entry.start_lba;
       burn_lba_to_msf(lba, &pmin, &psec, &pframe);
     } else {
       pmin= toc_entry.pmin;
       psec= toc_entry.psec;
       pframe= toc_entry.pframe;
       lba= burn_msf_to_lba(pmin,psec,pframe);
     }
     if(track_no==0 && burn_session_get_hidefirst(sessions[session_no]))
       printf("cdrskin: NOTE : first track is marked as \"hidden\".\n");
     printf("track:  %2d lba: %9d (%9d) %2.2d:%2.2d:%2.2d",
            track_count + track_offset - 1, lba, 4 * lba, pmin, psec, pframe);
     printf(" adr: %d control: %d",toc_entry.adr,toc_entry.control);

     /* >>> From where does cdrecord take "mode" ? */

     /* This is not the "mode" as printed by cdrecord :
       printf(" mode: %d\n",burn_track_get_mode(tracks[track_no]));
     */
     /* own guess: cdrecord says "1" on data and "0" on audio : */
     printf(" mode: %d\n",((toc_entry.control&7)<4?0:1));

   }
   if((flag&1) &&
    session_no < num_sessions + open_sessions - 1 + have_real_open_session - 1)
 continue;
   if(have_real_open_session) {
     /* Use start of invisible track */
     burn_track_get_entry(tracks[num_tracks], &toc_entry);
   } else {
     burn_session_get_leadout_entry(sessions[session_no],&toc_entry);
   }
   if(toc_entry.extensions_valid&1) { /* DVD extension valid */
     lba= toc_entry.start_lba;
     burn_lba_to_msf(lba, &pmin, &psec, &pframe);
   } else {
     pmin= toc_entry.pmin;
     psec= toc_entry.psec;
     pframe= toc_entry.pframe;
     lba= burn_msf_to_lba(pmin,psec,pframe);
   }
   printf("track:lout lba: %9d (%9d) %2.2d:%2.2d:%2.2d",
          lba,4*lba,pmin,psec,pframe);
   printf(" adr: %d control: %d",toc_entry.adr,toc_entry.control);
   printf(" mode: -1\n");
 }

 if(skin->verbosity >= Cdrskin_verbose_cmD) {
   ret= Cdrskin_cdtext_to_file(skin, "cdtext.dat", 15 | 16);
   if(ret <= 0 && ret < final_ret)
     final_ret= ret;
 }

summary:
 ret= burn_disc_get_profile(drive, &profile_number, profile_name);
 if(ret <= 0)
   strcpy(profile_name, "media");

 if(open_sessions > 0 && !have_real_open_session)
   open_sessions--;
 is_bdr_pow= burn_drive_get_bd_r_pow(drive);
 printf("Media summary: %d sessions, %d tracks, %s %s\n",
        num_sessions + open_sessions, track_count, 
        is_bdr_pow ? "unusable (POW)" :
        s==BURN_DISC_BLANK ? "blank" :
        s==BURN_DISC_APPENDABLE ? "appendable" :
        s==BURN_DISC_FULL ? "closed" :
        s==BURN_DISC_EMPTY ? "no " : "unknown ",
        profile_name);
        
 if(have_real_open_session)
   printf("Warning      : Incomplete session encountered !\n");
 
 if(disc!=NULL)
   burn_disc_free(disc);
 if(s == BURN_DISC_EMPTY)
   return(0);
 return(final_ret);
cannot_read:;
 fprintf(stderr,"cdrecord_emulation: Cannot read TOC header\n");
 fprintf(stderr,"cdrecord_emulation: Cannot read TOC/PMA\n");
 return(0);
}


/** Perform -minfo under control of Cdrskin_atip().
    @param flag Bitfield for control purposes:
    @return <=0 error, 1 success
*/
int Cdrskin_minfo(struct CdrskiN *skin, int flag)
{
 int num_sessions= 0,num_tracks= 0,lba= 0,track_count= 0,total_tracks= 0;
 int session_no, track_no, pmin, psec, pframe, ret, size= 0, nwa= 0;
 int last_leadout= 0, ovwrt_full= 0, track_offset= 1, open_sessions= 0;
 struct burn_drive *drive;
 struct burn_disc *disc= NULL;
 struct burn_session **sessions= NULL;
 struct burn_track **tracks;
 struct burn_toc_entry toc_entry;
 enum burn_disc_status s, show_status;
 char profile_name[80];
 int pno, is_bdr_pow= 0;
 char media_class[80];
 int nominal_sessions= 1, ftils= 1, ltils= 1, first_track= 1, read_capacity= 0;
 int app_code, cd_info_valid, lra, alloc_blocks, free_blocks;
 int have_real_open_session= 0;
 off_t avail, buf_count;
 char disc_type[80], bar_code[9], buf[2 * 2048], *type_text;
 unsigned int disc_id;

 drive= skin->drives[skin->driveno].drive;

 s= burn_disc_get_status(drive);
 if(s == BURN_DISC_EMPTY) {
   fprintf(stderr, "cdrecord-Emulation: No disk / Wrong disk!\n");
   return(1);
 }
 ret= burn_disc_get_profile(drive, &pno, profile_name);
 if(ret <= 0) {
   fprintf(stderr, "cdrskin: SORRY : Cannot inquire current media profile\n");
   return(1);
 }
 if(pno >= 0x08 && pno <= 0x0a)
   strcpy(media_class, "CD");
 else if(pno >= 0x10 && pno <= 0x2f)
   strcpy(media_class, "DVD");
 else if(pno >= 0x40 && pno <= 0x43)
   strcpy(media_class, "BD");
 else
   sprintf(media_class, "Unknown class (profile 0x%4.4X)", pno);
 is_bdr_pow= burn_drive_get_bd_r_pow(drive);

 printf("\n");
 printf("Mounted media class:      %s\n", media_class);
 printf("Mounted media type:       %s%s\n", profile_name,
        is_bdr_pow ? ", Pseudo Overwrite formatted" : "");
 ret= burn_disc_erasable(drive);
 printf("Disk Is %serasable\n",
        (ret || skin->media_is_overwriteable) ? "" : "not ");
 show_status= s;
 ret = burn_get_read_capacity(drive, &read_capacity, 0);
 if(ret <= 0)
   read_capacity= 0;
 if(skin->media_is_overwriteable && read_capacity > 0)
   ovwrt_full= 1;
 if(ovwrt_full)
   show_status= BURN_DISC_FULL;
 printf("disk status:              %s\n",
        is_bdr_pow ?                          "unusable (POW)" :
        show_status == BURN_DISC_BLANK ?      "empty" :
        show_status == BURN_DISC_APPENDABLE ? "incomplete/appendable" :
        show_status == BURN_DISC_FULL ?       "complete" :
                                              "unusable");
 printf("session status:           %s\n",
        show_status == BURN_DISC_BLANK ?      "empty" :
        show_status == BURN_DISC_APPENDABLE ? "empty" :
        show_status == BURN_DISC_FULL ?       "complete" :
                                              "unknown");

 disc= burn_drive_get_disc(drive);
 if(disc==NULL || s == BURN_DISC_BLANK) {
   first_track= 1;
   num_sessions= 0;
   nominal_sessions= 1;
   ftils= ltils= 1;
 } else {

   sessions= burn_disc_get_sessions(disc, &num_sessions);
   open_sessions= burn_disc_get_incomplete_sessions(disc);
   if(num_sessions > 0)
     track_offset= burn_session_get_start_tno(sessions[0], 0);
   if(track_offset <= 0)
     track_offset= 1;
   first_track= track_offset;
   nominal_sessions= num_sessions + open_sessions;
   if(s == BURN_DISC_APPENDABLE && open_sessions == 0)
     nominal_sessions++;
   for(session_no= 0; session_no < num_sessions + open_sessions;
       session_no++) {
     ftils= total_tracks + 1;
     tracks= burn_session_get_tracks(sessions[session_no],&num_tracks);
     if(tracks==NULL)
   continue;
     total_tracks+= num_tracks;
     ltils= total_tracks;
     if(session_no==0 && burn_session_get_hidefirst(sessions[session_no])
        && total_tracks >= 2)
       first_track= 2;
   }
   if(s == BURN_DISC_APPENDABLE && open_sessions == 0)
     ftils= ltils= total_tracks + 1;
 }
 printf("first track:              %d\n", first_track);
 printf("number of sessions:       %d\n", nominal_sessions);
 printf("first track in last sess: %d\n", ftils + track_offset - 1);
 printf("last track in last sess:  %d\n", ltils + track_offset - 1);

 burn_disc_get_cd_info(drive, disc_type, &disc_id, bar_code, &app_code,
                       &cd_info_valid);
 printf("Disk Is %sunrestricted\n", (cd_info_valid & 16) ? "" : "not ");
 if((cd_info_valid & 8) && !(cd_info_valid & 16))
   printf("Disc application code: %d\n", app_code);
 if(strcmp(media_class, "DVD") == 0 || strcmp(media_class, "BD") == 0)
   printf("Disk type: DVD, HD-DVD or BD\n");
 else if(strcmp(media_class, "CD") == 0 && (cd_info_valid & 1))
   printf("Disk type: %s\n", disc_type);
 else
   printf("Disk type: unrecognizable\n");
 if(cd_info_valid & 2)
   printf("Disk id: 0x%-X\n", disc_id);
 ret= burn_disc_get_bd_spare_info(drive, &alloc_blocks, &free_blocks, 0);
 if(ret == 1) {
   printf("BD Spare Area consumed:   %d\n", alloc_blocks - free_blocks);
   printf("BD Spare Area available:  %d\n", free_blocks);
 }

 printf("\n");
 printf("Track  Sess Type   Start Addr End Addr   Size\n");
 printf("==============================================\n");
 for(session_no= 0; session_no < num_sessions + open_sessions; session_no++) {
   tracks= burn_session_get_tracks(sessions[session_no],&num_tracks);
   if(tracks==NULL)
 continue;
   for(track_no= 0; track_no<num_tracks; track_no++) {
     track_count++;
     burn_track_get_entry(tracks[track_no], &toc_entry);
     if(toc_entry.extensions_valid&1) { /* DVD extension valid */
       lba= toc_entry.start_lba;
       size= toc_entry.track_blocks;
     } else {
       pmin= toc_entry.min;
       psec= toc_entry.sec;
       pframe= toc_entry.frame;
       size= burn_msf_to_lba(pmin,psec,pframe);
       pmin= toc_entry.pmin;
       psec= toc_entry.psec;
       pframe= toc_entry.pframe;
       lba= burn_msf_to_lba(pmin,psec,pframe);
     }

     lra= lba + size - 1;
     if(pno >= 0x08 && pno <= 0x0a && ((toc_entry.control & 7) >= 4) &&
        lra >= 2 && size >= 2) {
       /* If last two blocks not readable then assume TAO and subtract 2
          from lra and size.
       */;
       ret= burn_read_data(drive, (off_t) (lra - 1) * (off_t) 2048, buf,
                           2 * 2048, &buf_count, 2 | 4);
       if(ret <= 0) {
         lra-= 2;
         size-= 2;
       }
     }

#ifdef Cdrskin_with_last_recorded_addresS

     /* Interesting, but obviously not what cdrecord prints as "End Addr" */
     if(toc_entry.extensions_valid & 2) { /* LRA extension valid */
       if(pno == 0x11 || pno == 0x13 || pno == 0x14 || pno == 0x15 ||
	  pno == 0x41 || pno == 0x42 || pno == 0x51)
         lra= toc_entry.last_recorded_address;
     }

#endif /* Cdrskin_with_last_recorded_addresS */

     if(session_no < num_sessions) {
       type_text= ((toc_entry.control&7)<4) ? "Audio" : "Data";
     } else {
       if(track_no < num_tracks - 1) {
         type_text= "Rsrvd";
         have_real_open_session = 1;
       } else {
         type_text= "Blank";
       }
       if(toc_entry.extensions_valid & 4) {
         if(toc_entry.track_status_bits & (1 << 14))
           type_text= "Blank";
         else if(toc_entry.track_status_bits & (1 << 16)) {
           type_text= "Apdbl";
           have_real_open_session = 1;
         } else if(toc_entry.track_status_bits & (1 << 15)) {
           type_text= "Rsrvd";
           have_real_open_session = 1;
         } else
           type_text= "Invsb";
       }
     }
     printf("%5d %5d %-6s %-10d %-10d %-10d\n",
            track_count + track_offset - 1, session_no + 1,
            type_text, lba, lra, size);
     if(session_no < num_sessions)
       last_leadout= lba + size;
   }
 }
 if(last_leadout > 0)
   if(read_capacity > last_leadout)
     read_capacity= last_leadout;
 if(nominal_sessions > num_sessions) {
   ret= burn_disc_track_lba_nwa(drive, NULL, 0, &lba, &nwa);
   if(ret > 0) {
     avail= burn_disc_available_space(drive, NULL);
     size= avail / 2048;
     if(read_capacity == 0 && skin->media_is_overwriteable)
       size= 0; /* unformatted overwriteable media */
     if(nominal_sessions > num_sessions + open_sessions) {
       printf("%5d %5d %-6s %-10d %-10d %-10d\n",
              track_count + track_offset, nominal_sessions,
              ovwrt_full ? "Data" : "Blank",
              nwa, nwa + size - 1, size);
     }
   }
 }
 printf("\n");

 if(num_sessions > 0) {
   ret= burn_disc_get_msc1(drive, &lba);
   if(ret > 0)
     printf("Last session start address:         %-10d\n", lba);
   if(last_leadout > 0)
     printf("Last session leadout start address: %-10d\n", last_leadout);
   if(s == BURN_DISC_FULL) {
     if(read_capacity > 0 && (last_leadout != read_capacity ||
                    pno == 0x08 || pno == 0x10 || pno == 0x40 || pno == 0x50))
       printf("Read capacity:                      %-10d\n", read_capacity);
   }
 } else if(ovwrt_full) {
   printf("Last session start address:         %-10d\n", nwa);
   printf("Last session leadout start address: %-10d\n", size);
 }
 if(nominal_sessions > num_sessions && !skin->media_is_overwriteable) {
   printf("Next writable address:              %-10d\n", nwa);
   printf("Remaining writable size:            %-10d\n", size);
 }

 if(ovwrt_full) {
   printf("\n");
   printf("cdrskin: Media is overwriteable. No blanking needed. No reliable track size.\n");
   printf("cdrskin: Above contrary statements follow cdrecord traditions.\n");
 }

 if(have_real_open_session)
   printf("\nWarning: Incomplete session encountered !\n");

 if(disc!=NULL)
   burn_disc_free(disc);
 if(s == BURN_DISC_EMPTY)
   return(0);
 return(1);
}


int Cdrskin_print_all_profiles(struct CdrskiN *skin, struct burn_drive *drive,
                               int flag)
{
 int num_profiles, profiles[64], i, ret;
 char is_current[64], profile_name[80];

 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");
   printf("Profile: 0x%4.4X (%s)%s\n", (unsigned int) profiles[i],
          profile_name, is_current[i] ? " (current)" : "");
 }
 return(1);
}


/** Perform -atip .
    @param flag Bitfield for control purposes:
                bit0= perform -toc
                bit1= perform -toc with session markers
                bit2= perform -minfo
    @return <=0 error, 1 success
*/
int Cdrskin_atip(struct CdrskiN *skin, int flag)
{
 int ret,is_not_really_erasable= 0, current_is_cd= 1;
 double x_speed_max= -1.0, x_speed_min= -1.0;
 enum burn_disc_status s;
 struct burn_drive *drive;
 int profile_number= 0;
 char profile_name[80], *manuf= NULL, *media_code1= NULL, *media_code2= NULL;
 char *book_type= NULL, *product_id= NULL;
 char *sno= NULL;
 int sno_len = 0, i, is_bdr_pow= 0;

 ClN(printf("cdrskin: pseudo-atip on drive %d\n",skin->driveno));
 ret= Cdrskin_grab_drive(skin,0);
 if(ret<=0)
   return(ret);
 drive= skin->drives[skin->driveno].drive;
 s= burn_disc_get_status(drive);
 Cdrskin_report_disc_status(skin,s,1|2);
 if(s==BURN_DISC_APPENDABLE && skin->no_blank_appendable)
   is_not_really_erasable= 1;

 profile_name[0]= 0;
 ret= burn_disc_get_profile(drive,&profile_number,profile_name);
 if(ret<=0) {
   profile_number= 0;
   strcpy(profile_name, "-unidentified-");
 }
 if(profile_number != 0x08 && profile_number != 0x09 && profile_number != 0x0a)
   current_is_cd= 0;

 ret= Cdrskin_checkdrive(skin,profile_name,1);
 if(ret<=0)
   return(ret);

 if(burn_disc_get_status(drive) != 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)/Cdrskin_libburn_speed_factoR;
   }
 }
 if(burn_disc_get_status(drive) == BURN_DISC_UNSUITABLE) {
   if(skin->verbosity>=Cdrskin_verbose_progresS) {
     if(profile_name[0])
       printf("Current: %s\n",profile_name);
     else
       printf("Current: UNSUITABLE MEDIA (Profile %4.4Xh)\n",profile_number);
   }
   {ret= 0; goto ex;}
 }
 if(burn_disc_get_status(drive) != BURN_DISC_EMPTY) {
   ret= burn_drive_get_write_speed(drive);
   x_speed_max= ((double) ret)/Cdrskin_libburn_speed_factoR;
   if(x_speed_min<0)
     x_speed_min= x_speed_max;
 }
 printf("cdrskin: burn_drive_get_write_speed = %d  (%.1fx)\n",ret,x_speed_max);
 if(skin->verbosity>=Cdrskin_verbose_progresS) {
   if(burn_disc_get_status(drive) == BURN_DISC_EMPTY)
     printf("Current: none\n");
   else if(profile_name[0])
     printf("Current: %s\n",profile_name);
   else if(burn_disc_erasable(drive))
     printf("Current: CD-RW\n");
   else
     printf("Current: CD-R\n");
   Cdrskin_print_all_profiles(skin, drive, 0);
 }

 if(burn_disc_get_status(drive) == BURN_DISC_EMPTY)
   {ret= 0; goto ex;}

 if(strstr(profile_name,"DVD")==profile_name) {
   /* These are dummy messages for project scdbackup, so its media recognition
      gets a hint that the media is suitable and not in need of blanking.
      scdbackup will learn to interpret cdrskin's DVD messages but the
      current stable version needs to believe it is talking to its own
      growisofs_wrapper. So this is an emulation of an emulator.
      The real book type is available meanwhile. But that one is not intended.
   */
   printf("book type:     %s (emulated booktype)\n", profile_name);
   if(profile_number==0x13) /* DVD-RW */
     printf("cdrskin: message for sdvdbackup: \"(growisofs mode Restricted Overwrite)\"\n");
 } else if(strstr(profile_name,"BD")==profile_name) {
   is_bdr_pow= burn_drive_get_bd_r_pow(drive);
   printf("Mounted Media: %2.2Xh, %s%s\n", profile_number, profile_name,
          is_bdr_pow ? ", Pseudo Overwrite formatted" : "");
 } else {
   printf("ATIP info from disk:\n");
   if(burn_disc_erasable(drive)) {
     if(is_not_really_erasable)
       printf("  Is erasable (but not while in this incomplete state)\n");
     else
       printf("  Is erasable\n");
   } else {
     printf("  Is not erasable\n");
   }
   { int start_lba,end_lba,min,sec,fr;
     int m_lo, s_lo, f_lo;

     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);
       printf("  ATIP start of lead in:  %d (%-2.2d:%-2.2d/%-2.2d)\n",
              start_lba,min,sec,fr);
       burn_lba_to_msf(end_lba,&m_lo,&s_lo,&f_lo);
       printf("  ATIP start of lead out: %d (%-2.2d:%-2.2d/%-2.2d)\n",
              end_lba, m_lo, s_lo, f_lo);
       if(current_is_cd)
         manuf= burn_guess_cd_manufacturer(min, sec, fr, m_lo, s_lo, f_lo, 0);
     }
   }
   printf("  1T speed low:  %.f 1T speed high: %.f\n",x_speed_min,x_speed_max);
 }

 ret= burn_disc_get_media_id(drive, &product_id, &media_code1, &media_code2,
                                &book_type, 0);
 if(ret > 0 && (!current_is_cd) &&
    manuf == NULL && media_code1 != NULL && media_code2 != NULL) {

   manuf= burn_guess_manufacturer(profile_number, media_code1, media_code2, 0);
 }
 if(product_id != NULL)
   printf("Product Id:    %s\n", product_id);
 if(manuf != NULL)
   printf("Producer:      %s\n", manuf);
 if(skin->verbosity >= Cdrskin_verbose_progresS) {
   if (current_is_cd) {
     if(manuf != NULL)
       printf("Manufacturer: %s\n", manuf);
   } 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) {
       if(profile_number == 0x11 || profile_number == 0x13 ||
          profile_number == 0x14 || profile_number == 0x15)
         printf("Manufacturer:   '%s'\n", media_code1);
       else if(profile_number >= 0x40 && profile_number <= 0x43) {
         printf("Manufacturer:       '%s'\n", media_code1);
         if(media_code2[0])
           printf("Media type:         '%s'\n", media_code2);
       } else {
         printf("Manufacturer:    '%s'\n", media_code1);
         if(media_code2[0])
           printf("Media type:      '%s'\n", media_code2);
       }
     }
   }
 }

 burn_drive_get_media_sno(drive, &sno, &sno_len);
 if(sno_len > 0) {
   printf("Media id:      ");
   for(i= 0; i < sno_len && i < 1024; i++)
     printf("%2.2X", (unsigned int) ((unsigned char *) sno)[i]);
   if(i < sno_len)
     printf("...");
   printf("\n");
 }
 if(sno != NULL)
   free(sno);
 sno= NULL;


 ret= 1;
 if(flag&1)
   ret= Cdrskin_toc(skin, !(flag & 2));
                       /*cdrecord seems to ignore -toc errors if -atip is ok */
 if(ret > 0 && (flag & 4))
   ret= Cdrskin_minfo(skin, 0);

ex:;
 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(ret);
}


/** Perform --list_formats
    @param flag Bitfield for control purposes:
    @return <=0 error, 1 success
*/
int Cdrskin_list_formats(struct CdrskiN *skin, int flag)
{
 struct burn_drive *drive;
 int ret, i, status, num_formats, profile_no, type, alloc_blocks, free_blocks;
 int is_bdr_pow= 0;
 off_t size;
 unsigned dummy;
 char status_text[80], profile_name[90];

 ret= Cdrskin_grab_drive(skin,0);
 if(ret<=0)
   return(ret);
 drive= skin->drives[skin->driveno].drive;

 ret = burn_disc_get_formats(drive, &status, &size, &dummy,
                             &num_formats);
 if(ret <= 0) {
   fprintf(stderr, "cdrskin: SORRY: Cannot obtain format list info\n");
   ret= 0; goto ex;
 }
 ret= burn_disc_get_profile(drive, &profile_no, profile_name);
 is_bdr_pow= burn_drive_get_bd_r_pow(drive);
 printf("Media current: ");
 if(profile_no > 0 && ret > 0) {
   if(profile_name[0])
     printf("%s%s\n", profile_name,
                      is_bdr_pow ? ", Pseudo Overwrite formatted" : "");
   else
     printf("%4.4Xh\n", profile_no);
 } else
   printf("is not recognizable\n");

 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");
 printf("Format status: %s\n", status_text);
 ret= burn_disc_get_bd_spare_info(drive, &alloc_blocks, &free_blocks, 0);
 if(ret == 1)
   printf("BD Spare Area: %d blocks consumed, %d blocks available\n",
          alloc_blocks - free_blocks, free_blocks);

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


/** Perform --list_speeds
    @param flag Bitfield for control purposes:
    @return <=0 error, 1 success
*/
int Cdrskin_list_speeds(struct CdrskiN *skin, int flag)
{
 struct burn_drive *drive;
 int ret, i, profile_no, high= -1, low= 0x7fffffff, is_cd= 0, is_bdr_pow= 0;
 char profile_name[90], *speed_unit= "D";
 double speed_factor= 1385000.0, cd_factor= 75.0 * 2352;
 struct burn_speed_descriptor *speed_list= NULL, *item, *other;

 ret= Cdrskin_grab_drive(skin,0);
 if(ret<=0)
   return(ret);
 drive= skin->drives[skin->driveno].drive;
 ret= burn_drive_get_speedlist(drive, &speed_list);
 if(ret <= 0) {
   fprintf(stderr, "cdrskin: SORRY: Cannot obtain speed list info\n");
   ret= 0; goto ex;
 }
 ret= burn_disc_get_profile(drive, &profile_no, profile_name);
 is_bdr_pow= burn_drive_get_bd_r_pow(drive);
 printf("Media current: ");
 if(profile_no > 0 && ret > 0) {
   if(profile_name[0])
     printf("%s%s\n", profile_name,
                    is_bdr_pow ? ", Pseudo Overwrite formatted" : "");
   else
     printf("%4.4Xh\n", profile_no);
 } else
   printf("is not recognizable\n");
 if(profile_no >= 0x08 && profile_no <= 0x0a)
   is_cd= profile_no;
 speed_factor= Cdrskin_libburn_speed_factoR * 1000.0;
 if(Cdrskin_libburn_speed_factoR == Cdrskin_libburn_cd_speed_factoR)
   speed_unit= "C";
 else if(Cdrskin_libburn_speed_factoR ==  Cdrskin_libburn_bd_speed_factoR)
   speed_unit= "B";

 for (item= speed_list; item != NULL; item= item->next) {
   if(item->source == 1) {
     /* CD mode page 2Ah : report only if not same speed by GET PERFORMANCE */
     for(other= speed_list; other != NULL; other= other->next)
       if(other->source == 2 && item->write_speed == other->write_speed)
     break;
     if(other != NULL)
 continue;
   }
   printf("Write speed  :  %5dk , %4.1fx%s\n",
          item->write_speed,
          ((double) item->write_speed) * 1000.0 / speed_factor, speed_unit);
   if(item->write_speed > high)
     high= item->write_speed;
   if(item->write_speed < low)
     low= item->write_speed;
 }
   
 /* Maybe there is ATIP info */
 if(is_cd) {
   ret= burn_disc_read_atip(drive);
   if(ret < 0)
     goto ex;
   if(ret > 0) {
     for(i= 0; i < 2; i++) {
       if(i == 0)
         ret= burn_drive_get_min_write_speed(drive);
       else
         ret= burn_drive_get_write_speed(drive);
       if(ret > 0) {
         if(ret < low || (i == 0 && ret != low)) { 
           printf("Write speed l:  %5dk , %4.1fx%s\n",
                  ret, ((double) ret) * 1000.0 / cd_factor, "C");
           low= ret;
         }
         if(ret > high || (i == 1 && ret != high)) {
           printf("Write speed h:  %5dk , %4.1fx%s\n",
                  ret, ((double) ret) * 1000.0 / cd_factor, "C");
           high= ret;
         }
       }
     }
   }
 }
 if(high > -1) {
   printf("Write speed L:  %5dk , %4.1fx%s\n",
          low, ((double) low) * 1000.0 / speed_factor, speed_unit);
   printf("Write speed H:  %5dk , %4.1fx%s\n",
           high, ((double) high) * 1000.0 / speed_factor, speed_unit);
   ret= burn_drive_get_best_speed(drive, -1, &item, 2);
   if(ret > 0 && item != NULL)
     if(item->write_speed != low)
       printf("Write speed 0:  %5dk , %4.1fx%s\n",
              item->write_speed,
             ((double) item->write_speed) * 1000.0 / speed_factor, speed_unit);
   ret= burn_drive_get_best_speed(drive, 0, &item, 2);
   if(ret > 0 && item != NULL)
     if(item->write_speed != high)
       printf("Write speed-1:  %5dk , %4.1fx%s\n",
              item->write_speed,
             ((double) item->write_speed) * 1000.0 / speed_factor, speed_unit);
 } else {
   fprintf(stderr,
      "cdrskin: SORRY : Could not get any write speed information from drive");
 }
 ret= 1;
ex:;
 if(speed_list != NULL)
   burn_drive_free_speedlist(&speed_list);
 return(ret);
}


/* @param flag: bit0= do not print prefix on first line
                bit1= plan for multiples of 4 hex words
 */
static int Feature_printf(char *prefix, char *text, int flag)
{
 char *tpt, *npt;
 int lp, lp0, line_size;

 lp0= !(flag & 1);
 tpt= text;
 lp= strlen(prefix);
 /* Plan 2 char shorter for possible comma inclusion */
 line_size= 77;
 if(flag & 2) {
   line_size= (80 - lp) / 3;
   line_size-= line_size % 4;
   line_size= line_size * 3 - 1 + lp;
 }

 while(lp * lp0 + strlen(tpt) >= 80) {
   /* Line size for 20  */
   for(npt= tpt + line_size - lp * lp0; npt > tpt; npt--)
     if(*npt == ' ')
   break;
   if(npt <= tpt) {
     printf("%s%s\n", lp0 ? prefix : "", tpt);
     return(2);
   }
   /* Keep comma in printed line */
   if(*(npt + 1) == ',' && *(npt + 1) == ' ')
     npt+= 2;
   /* Print and advance */
   *npt= 0;
   printf("%s%s\n", lp0 ? prefix : "", tpt);
   tpt= npt + 1;
   lp0= 1;
 }
 printf("%s%s\n", lp0 ? prefix : "", tpt);
 return(1);
}


/** Perform --list_features
    @param flag Bitfield for control purposes:
    @return <=0 error, 1 success
*/
int Cdrskin_list_features(struct CdrskiN *skin, int flag)
{
 struct burn_drive *d;
 int ret, count, i, j;
 unsigned int *feature_codes= NULL;
 unsigned char flags, additional_length, *feature_data = NULL;
 char *feature_text = NULL, *cpt, *dpt;

 ret= Cdrskin_grab_drive(skin,0);
 if(ret <= 0)
   return(ret);
 d= skin->drives[skin->driveno].drive;

 fprintf(stderr, "SCSI Features reported by the drive:\n");
 fprintf(stderr,
   "     + = Current, - = Not Current, P = Persistent, N = Not Persistent\n");
 fprintf(stderr, "Code C : Name         : Version,Persistent\n");
 fprintf(stderr, "         Additional data if present (hex bytes)\n");
 fprintf(stderr, "         Parsed info if available (fieldname=content)\n");
 fprintf(stderr, "\n");

 burn_drive_get_feature_codes(d, &count, &feature_codes);
 if(count <= 0) {
   fprintf(stderr, "cdrskin: NOTE: The drive feature list is empty\n");
   ret= 1; goto ex;
 }
 for(i= 0; i < count; i++) {
   ret= burn_drive_get_feature(d, feature_codes[i], &flags, &additional_length,
                               &feature_data, &feature_text);
   if(ret <= 0) {
     fprintf(stderr, "cdrskin: SORRY: Cannot obtain data of feature %4.4x\n",
                     feature_codes[i]);
 continue;
   }
   cpt= feature_text;
   for(j= 0; j < 3 && cpt != NULL; j++)
     cpt= strchr(cpt + 1, ':');
   if(cpt != NULL) {
     *cpt= 0;
     cpt++;
   }
   Feature_printf("         ", feature_text, 1);
   if(cpt != NULL) {
     dpt= cpt;
     if(*dpt == ' ')
       dpt++;
     for(j= 0; j < 1 && cpt != NULL; j++)
       cpt= strchr(cpt + 1, ':');
     if(cpt != NULL) {
       *cpt= 0;
       cpt++;
     }
     Feature_printf("         ", dpt, 2);
   }
   if(cpt != NULL) {
     if(*cpt == ' ')
       cpt++;
     Feature_printf("         ", cpt, 0);
   } else {
     printf("\n");
   }
   if(feature_data != NULL)
     free(feature_data);
   feature_data= NULL;
   if(feature_text != NULL)
     free(feature_text);
   feature_text= NULL;
 }
 ret= 1;
ex:;
 if(feature_codes != NULL)
   free(feature_codes);
 return(ret);
}


int Cdrskin_read_textfile(struct CdrskiN *skin, char *path, int flag)
{
 int ret, num_packs = 0;
 unsigned char *text_packs = NULL;

 ret= burn_cdtext_from_packfile(path, &text_packs, &num_packs, 0);
 if(ret <= 0)
   goto ex;

 if(skin->text_packs != NULL)
   free(skin->text_packs);
 skin->text_packs= text_packs;
 skin->num_text_packs= num_packs;
 ret= 1;
ex:;
 if(ret <= 0 && text_packs != NULL)
   free(text_packs);
 return(ret);
}


/*
    @param flag         Bitfield for control purposes:
                        bit3= Enable DAP : "flaw obscuring mechanisms like
                                            audio data mute and interpolate"
*/
int Cdrskin_extract_audio_to_dir(struct CdrskiN *skin,
                                 char *dir, char *basename,
                                 char print_tracks[100], int flag)
{
 int num_sessions= 0, num_tracks= 0, profile_number, session_no, track_no;
 int track_count= 0, existing= 0, ret, pass, not_audio= 0, pick_tracks= 0, i;
 int tracks_extracted= 0, min_tno= 100, max_tno= 0;
 struct burn_drive *drive;
 struct burn_disc *disc= NULL;
 struct burn_session **sessions;
 struct burn_track **tracks;
 enum burn_disc_status s;
 struct burn_toc_entry toc_entry;
 struct stat stbuf;
 char profile_name[80], path[4096 + 256];

 ret= Cdrskin_grab_drive(skin, 0);
 if(ret<=0)
   goto ex;
 drive= skin->drives[skin->driveno].drive;
 s= burn_disc_get_status(drive);
 disc= burn_drive_get_disc(drive);
 if(s == BURN_DISC_EMPTY || s == BURN_DISC_BLANK || disc == NULL) {
not_readable:;
   fprintf(stderr, "cdrskin: SORRY : No audio CD in drive.\n");
   ret= 0; goto ex;
 }
 ret= burn_disc_get_profile(drive, &profile_number, profile_name);
 if(ret <= 0 || profile_number < 0x08 || profile_number > 0x0a) {
not_audio_cd:;
   fprintf(stderr, "cdrskin: SORRY : Medium in drive is not an audio CD.\n");
   ret= 0; goto ex;
 }
 for(i= 0; i < 99; i++)
   if(print_tracks[i])
     pick_tracks= 1;
 sessions= burn_disc_get_sessions(disc, &num_sessions);
 if(num_sessions <= 0)
   goto not_readable;
 for(pass= 0; pass < 2; pass++) {
   if(pass > 0) {
     if(existing > 0)
       {ret= 0; goto ex;}
     if(not_audio == track_count)
       goto not_audio_cd;
   }
   track_count= 0;
   for(session_no= 0; session_no < num_sessions; session_no++) {
     tracks= burn_session_get_tracks(sessions[session_no],&num_tracks);
     if(tracks==NULL)
   continue;
     for(track_no= 0; track_no < num_tracks; track_no++) {
       track_count++;
       burn_track_get_entry(tracks[track_no], &toc_entry);
       if(toc_entry.point < min_tno)
         min_tno= toc_entry.point;
       if(toc_entry.point > max_tno)
         max_tno= toc_entry.point;
       if(pick_tracks) {
         if(toc_entry.point <= 0 || toc_entry.point > 99)
     continue;                                          /* should not happen */
         if(print_tracks[toc_entry.point] == 0)
     continue;
       }
       if((toc_entry.control & 7) >= 4) {
         if(pass == 0)
           fprintf(stderr,
                   "cdrskin: WARNING : Track %2.2d is not an audio track.\n",
                   toc_entry.point);
         not_audio++;
     continue;
       }
       sprintf(path, "%s/%s%2.2u.wav", dir, basename, toc_entry.point);
       if(pass == 0) {
         if(stat(path, &stbuf) != -1) {
           fprintf(stderr,
                   "cdrskin: SORRY : May not overwrite existing file: '%s'\n",
                   path);
           existing++;
         }
     continue;
       }
       if(skin->verbosity >= Cdrskin_verbose_progresS)
         fprintf(stderr, "cdrskin: Writing audio track file: %s\n", path);
       ret= burn_drive_extract_audio_track(drive, tracks[track_no], path,
                   (flag & 8) | (skin->verbosity >= Cdrskin_verbose_progresS));
       if(ret <= 0)
         goto ex;
       tracks_extracted++;
     }
   }
 }
 if(tracks_extracted == 0 && pick_tracks) {
   fprintf(stderr,
  "cdrskin: SORRY : Not a single track matched the list of extract_tracks=\n");
   if(min_tno < 100 && max_tno > 0)
     fprintf(stderr, "cdrskin: HINT : Track range is %2.2d to %2.2d\n",
             min_tno, max_tno);
   ret= 0; goto ex;
 }
 ret= 1;
ex:;
 return(ret);
}


#ifndef Cdrskin_extra_leaN

/* A70324: proposal by Eduard Bloch */
int Cdrskin_warn_of_mini_tsize(struct CdrskiN *skin, int flag)
{
 off_t media_space= 0;
 enum burn_disc_status s;
 struct burn_drive *drive;
 int ret;

 ret= burn_drive_get_drive_role(skin->drives[skin->driveno].drive);
 if(ret != 1)
   return(1);

 if(skin->multi || skin->has_open_ended_track || skin->smallest_tsize<0)
   return(1);
 drive= skin->drives[skin->driveno].drive;
 s= burn_disc_get_status(drive);
 if(s!=BURN_DISC_BLANK)
   return(1);
 media_space= burn_disc_available_space(drive, NULL);
 if(media_space<=0 || 
    skin->smallest_tsize >= media_space / Cdrskin_minimum_tsize_quotienT)
   return(1);
 fprintf(stderr,"\n");
 fprintf(stderr,"\n");
 fprintf(stderr,
         "cdrskin: WARNING: Very small track size set by option tsize=\n");
 fprintf(stderr,
         "cdrskin:          Track size %.1f MB <-> media capacity %.1f MB\n",
         skin->smallest_tsize/1024.0/1024.0,
         ((double) media_space)/1024.0/1024.0);
 fprintf(stderr,"\n");
 fprintf(stderr,
         "cdrskin: Will wait at least 15 seconds until real burning starts\n");
 fprintf(stderr,"\n");
 if(skin->gracetime<15)
   skin->gracetime= 15;
 return(1);
}


/** Emulate the gracetime= behavior of cdrecord 
    @param flag Bitfield for control purposes:
                bit0= do not print message about pseudo-checkdrive
*/
int Cdrskin_wait_before_action(struct CdrskiN *skin, int flag)
/* flag: bit0= BLANK rather than write mode
         bit1= FORMAT rather than write mode
*/
{
 int i;

 Cdrskin_warn_of_mini_tsize(skin,0);

 if(skin->verbosity>=Cdrskin_verbose_progresS) {
   char speed_text[80];
   if(skin->x_speed<0)
     strcpy(speed_text,"MAX");
   else if(skin->x_speed==0)
     strcpy(speed_text,"MIN");
   else
     sprintf(speed_text,"%.f",skin->x_speed);
   printf(
  "Starting to write CD/DVD at speed %s in %s %s mode for %s session.\n",
          speed_text,(skin->dummy_mode?"dummy":"real"),
          (flag&2?"FORMAT":(flag&1?"BLANK":skin->preskin->write_mode_name)),
          (skin->multi?"multi":"single"));
   printf("Last chance to quit, starting real write in %3d seconds.",
          skin->gracetime);
   fflush(stdout);
 }
 for(i= skin->gracetime-1;i>=0;i--) {
   usleep(1000000);
   if(Cdrskin__is_aborting(0))
     return(0);
   if(skin->verbosity>=Cdrskin_verbose_progresS) {
     printf("\b\b\b\b\b\b\b\b\b\b\b\b\b %3d seconds.",i);
     fflush(stdout);
   }
 }
 if(skin->verbosity>=Cdrskin_verbose_progresS)
   {printf(" Operation starts.\n");fflush(stdout);} 
 return(1);
}

#endif /* Cdrskin_extra_leaN */


/** Perform blank=[all|fast]
    @return <=0 error, 1 success
*/
int Cdrskin_blank(struct CdrskiN *skin, int flag)
{
 enum burn_disc_status s;
 struct burn_progress p;
 struct burn_drive *drive;
 int ret,loop_counter= 0,hint_force= 0,do_format= 0, profile_number= -1;
 int wrote_well= 1, format_flag= 0, status, num_formats, using_immed= 1;
 off_t size;
 unsigned dummy;
 double start_time;
 char *verb= "blank", *presperf="blanking", *fmt_text= "...";
 char profile_name[80], progress_text[40];
 static char fmtp[][40]= {
                  "...", "format_overwrite", "deformat_sequential",
                  "(-format)", "format_defectmgt", "format_by_index",
                  "format_if_needed", "as_needed"};
 static int fmtp_max= 7;

 start_time= Sfile_microtime(0); /* will be refreshed later */
 ret= Cdrskin_grab_drive(skin,0);
 if(ret<=0)
   return(ret);
 drive= skin->drives[skin->driveno].drive;
 s= burn_disc_get_status(drive);
 profile_name[0]= 0;
 if(skin->grabbed_drive)
   burn_disc_get_profile(skin->grabbed_drive,&profile_number,profile_name);

 ret= Cdrskin_report_disc_status(skin,s,
                           1|(4*!(skin->verbosity>=Cdrskin_verbose_progresS)));
 if(ret==2)
   s= BURN_DISC_APPENDABLE;
 do_format= skin->blank_format_type & 0xff;
 if(s==BURN_DISC_UNSUITABLE) {
   if(skin->force_is_set) {
     ClN(fprintf(stderr,"cdrskin: NOTE : -force blank=... : Treating unsuitable media as burn_disc_full\n"));
     ret= burn_disc_pretend_full(drive);
     if(ret <= 0) {
       ClN(fprintf(stderr,
                   "cdrskin: FAILURE : Medium still considered unsuitable\n"));
       {ret= 0; goto ex;}
     }
     s= burn_disc_get_status(drive);
   } else
     hint_force= 1;
 }
 if(do_format)
   if(do_format>=0 && do_format<=fmtp_max)
     fmt_text= fmtp[do_format];
 if(do_format==5) { /* format_by_index */
   if(profile_number == 0x12 || profile_number == 0x43)
     do_format= 4;
   else
     do_format= 1;
 } else if(do_format==6 || do_format==7) { /* format_if_needed , as_needed */
   /* Find out whether format is needed at all.
      Eventuelly set up a suitable formatting run
   */
   if(profile_number == 0x14 && do_format==6) { /* sequential DVD-RW */
     do_format= 1;
     skin->blank_format_type= 1|(1<<8);
     skin->blank_format_size= 128*1024*1024;
   } else if(profile_number == 0x12 ||
             profile_number == 0x43 ||
            (profile_number == 0x41 && do_format==6)) {
                                               /* DVD-RAM , BD-RE , BD-R SRM */
     ret= burn_disc_get_formats(drive, &status, &size, &dummy, &num_formats);
     if((ret>0 && status!=BURN_FORMAT_IS_FORMATTED)) {
       do_format= 4;
       skin->blank_format_type= 4|(3<<9);  /* default payload size */
       skin->blank_format_size= 0;
     }
   } else if(do_format==7) { /* try to blank what is not blank yet */
     if(s!=BURN_DISC_BLANK) {
       do_format= 0;
       skin->blank_fast= 1;
     }
   }
   if(do_format==6 || do_format==7) {
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(fprintf(stderr,
        "cdrskin: NOTE : blank=%s : no need for action detected\n", fmt_text));
     {ret= 2; goto ex;}
   }
 }

 if(do_format == 1 || do_format == 3 || do_format == 4) {
   verb= "format";
   presperf= "formatting";
 }
 if(do_format==2) {
   /* Forceful blanking to Sequential Recording for DVD-R[W] and CD-RW */

   if(!(profile_number == 0x14 || profile_number == 0x13 ||
        profile_number == 0x0a)) {
     if(skin->grow_overwriteable_iso>0 && skin->media_is_overwriteable)
       goto pseudo_blank_ov;
     else
       goto unsupported_format_type;
   }

 } else if(do_format==1 || do_format==3) {
   /* Formatting to become overwriteable for DVD-RW and DVD+RW */

   if(do_format==3 && profile_number != 0x1a) {
     fprintf(stderr, "cdrskin: SORRY : -format does DVD+RW only\n");
     if(profile_number==0x14)
       fprintf(stderr,
          "cdrskin: HINT  : blank=format_overwrite would format this media\n");
     {ret= 0; goto ex;}
   }

   if(profile_number == 0x14) { /* DVD-RW sequential */
       /* ok */;
   } else if(profile_number == 0x13) { /* DVD-RW restricted overwrite */
     if(!(skin->force_is_set || ((skin->blank_format_type>>8)&4))) {
       fprintf(stderr,
       "cdrskin: NOTE : blank=format_... : media is already formatted\n");
       fprintf(stderr,
       "cdrskin: HINT : If you really want to re-format, add option -force\n");
       {ret= 2; goto ex;}
     }
   } else if(profile_number == 0x1a) { /* DVD+RW */
     if(!((skin->blank_format_type>>8)&4)) {
       fprintf(stderr,
       "cdrskin: NOTE : blank=format_... : DVD+RW do not need this\n");
       fprintf(stderr,
      "cdrskin: HINT : For de-icing use option blank=format_overwrite_full\n");
       {ret= 2; goto ex;}
     }
   } else {
     fprintf(stderr,
            "cdrskin: SORRY : blank=%s for now does DVD-RW and DVD+RW only\n",
            fmt_text);
     {ret= 0; goto ex;}
   }
   if(s==BURN_DISC_UNSUITABLE)
     fprintf(stderr,
         "cdrskin: NOTE : blank=%s accepted not yet suitable media\n",
         fmt_text);

 } else if(do_format==4) {
   /* Formatting and influencing defect management of DVD-RAM , BD-RE */
   if(!(profile_number == 0x12 || profile_number == 0x41 ||
        profile_number == 0x43)) {
     fprintf(stderr,
            "cdrskin: SORRY : blank=%s for now does DVD-RAM and BD only\n",
            fmt_text);
     {ret= 0; goto ex;}
   }
   if(s==BURN_DISC_UNSUITABLE)
     fprintf(stderr,
         "cdrskin: NOTE : blank=%s accepted not yet suitable media\n",
         fmt_text);

 } else if(do_format==0) {
   /* Classical blanking of erasable media */

   if(skin->grow_overwriteable_iso>0 && skin->media_is_overwriteable) {
pseudo_blank_ov:;
     if(skin->dummy_mode) {
       fprintf(stderr,
     "cdrskin: would have begun to pseudo-blank disc if not in -dummy mode\n");
       goto blanking_done;
     }
     skin->grow_overwriteable_iso= 3;
     ret= Cdrskin_invalidate_iso_head(skin, 0);
     if(ret<=0)
       goto ex;
     goto blanking_done;
   } else if(s!=BURN_DISC_FULL && 
      (s!=BURN_DISC_APPENDABLE || skin->no_blank_appendable) &&
      (profile_number!=0x13 || !skin->prodvd_cli_compatible) &&
      (s!=BURN_DISC_BLANK || !skin->force_is_set)) {
     if(s==BURN_DISC_BLANK) {
       fprintf(stderr,
       "cdrskin: NOTE : blank=... : media was already blank (and still is)\n");
       {ret= 2; goto ex;}
     } else if(s==BURN_DISC_APPENDABLE) {
       fprintf(stderr,
               "cdrskin: FATAL : blank=... : media is still appendable\n");
     } else {
       fprintf(stderr,
               "cdrskin: FATAL : blank=... : no blankworthy disc found\n");
       if(hint_force)
         fprintf(stderr,
    "cdrskin: HINT : If you are certain to have a CD-RW, try option -force\n");
     }
     {ret= 0; goto ex;}
   }
   if(!burn_disc_erasable(drive)) {
     fprintf(stderr,"cdrskin: FATAL : blank=... : media is not erasable\n");
     {ret= 0; goto ex;}
   }
   if((profile_number == 0x14 || profile_number == 0x13) &&
      !skin->prodvd_cli_compatible)
     skin->blank_fast= 0; /* only with deformat_sequential_quickest */

 } else {
unsupported_format_type:;
   fprintf(stderr,
          "cdrskin: SORRY : blank=%s is unsupported with media type %s\n",
          fmt_text, profile_name);
   {ret= 0; goto ex;}
 }

 if(skin->dummy_mode) {
   fprintf(stderr,
           "cdrskin: would have begun to %s disc if not in -dummy mode\n",
           verb);
   goto blanking_done;
 }
 fprintf(stderr,"cdrskin: beginning to %s disc\n",verb);
 Cdrskin_adjust_speed(skin,0);

#ifndef Cdrskin_extra_leaN
 Cdrskin_wait_before_action(skin,
                            1+(do_format==1 || do_format==3 || do_format==4));
#endif /* ! Cdrskin_extra_leaN */

 if(Cdrskin__is_aborting(0))
   {ret= 0; goto ex;}
 using_immed= burn_drive_get_immed(drive);
 skin->drive_is_busy= 1;
 if(do_format==0 || do_format==2) {
   burn_disc_erase(drive,skin->blank_fast);

 } else if(do_format==1 || do_format==3 || do_format==4) {
   format_flag= (skin->blank_format_type>>8)&(1|2|4|32|128);
   if(skin->force_is_set)
     format_flag|= 16;
   if(format_flag&128)
     format_flag|= (skin->blank_format_index&255)<<8;
   if(skin->blank_format_no_certify)
     format_flag|= 64;
   burn_disc_format(drive,(off_t) skin->blank_format_size,format_flag);

 } else {
   fprintf(stderr,"cdrskin: SORRY : Format type %d not implemented yet.\n",
           do_format);
   ret= 0; goto ex;
 }

 loop_counter= 0;
 start_time= Sfile_microtime(0);
 if(!using_immed)
   sprintf(progress_text, "synchronous");
 while(burn_drive_get_status(drive, &p) != BURN_DRIVE_IDLE) {
   if(loop_counter>0)
     if(skin->verbosity>=Cdrskin_verbose_progresS) {
       double percent= 50.0;

       if(p.sectors>0) /* i want a display of 1 to 99 percent */
         percent= 1.0+((double) p.sector+1.0)/((double) p.sectors)*98.0;
       if(using_immed)
         sprintf(progress_text, "done %.1f%%", percent);
       fprintf(stderr,
          "%scdrskin: %s ( %s , %lu seconds elapsed )          %s",
          skin->pacifier_with_newline ? "" : "\r",
          presperf,
          progress_text,
          (unsigned long) (Sfile_microtime(0)-start_time),
          skin->pacifier_with_newline ? "\n" : "");
     }
   sleep(1);
   loop_counter++;
 }
blanking_done:;
 wrote_well = burn_drive_wrote_well(drive);
 if(wrote_well && skin->verbosity>=Cdrskin_verbose_progresS) {
   fprintf(stderr,
           "%scdrskin: %s done                                        \n",
           skin->pacifier_with_newline ? "" : "\r", presperf);
   printf("%s time:   %.3fs\n",
          (do_format==1 || do_format==3 || do_format==4 ?
           "Formatting":"Blanking"),
          Sfile_microtime(0)-start_time);
 }
 fflush(stdout);
 if(!wrote_well)
   fprintf(stderr,
           "%scdrskin: %s failed                                      \n",
           skin->pacifier_with_newline ? "" : "\r", presperf);
 ret= !!(wrote_well);
ex:;
 skin->drive_is_busy= 0;
 if(Cdrskin__is_aborting(0))
   Cdrskin_abort(skin, 0); /* Never comes back */
 return(ret);
}


int Cdrskin__libburn_fifo_status(struct burn_source *current_fifo,
                                 char fifo_text[80], int flag)
{
 int ret, size, free_space, fill, fifo_percent;
 char *status_text= "";

 ret= burn_fifo_inquire_status(current_fifo, &size, &free_space,
                               &status_text);
 if(ret <= 0 || ret >= 4) {
   strcpy(fifo_text, "(fifo   0%) ");
 } else if(ret == 1) {
   burn_fifo_next_interval(current_fifo, &fill);
   fifo_percent= 100.0 * ((double) fill) / (double) size;
   if(fifo_percent<100 && fill>0)
     fifo_percent++;
   sprintf(fifo_text, "(fifo %3d%%) ", fifo_percent);
 }
 return(1);
}


/** Report burn progress. This is done partially in cdrecord style.
    Actual reporting happens only if write progress hit the next MB or if in
    non-write-progress states a second has elapsed since the last report. 
    After an actual report a new statistics interval begins.
    @param drive_status As obtained from burn_drive_get_status()
    @param p Progress information from burn_drive_get_status()
    @param start_time Timestamp of burn start in seconds
    @param last_time Timestamp of report interval start in seconds
    @param total_count Returns the total number of bytes written so far
    @param total_count Returns the number of bytes written during interval
    @param flag Bitfield for control purposes:
                bit0= report in growisofs style rather than cdrecord style
    @return <=0 error, 1 seems to be writing payload, 2 doing something else 
*/
int Cdrskin_burn_pacifier(struct CdrskiN *skin, int start_tno,
                          enum burn_drive_status drive_status,
                          struct burn_progress *p,
                          double start_time, double *last_time,
                          double *total_count, double *last_count,
                          int *min_buffer_fill, int flag)
/*
 bit0= growisofs style
*/
{
 double bytes_to_write= 0.0;
 double written_bytes= 0.0,written_total_bytes= 0.0;
 double fixed_size= 0.0, padding,sector_size,speed_factor;
 double measured_total_speed,measured_speed;
 double elapsed_time,elapsed_total_time,current_time;
 double estim_time,estim_minutes,estim_seconds,percent;
 int ret,fifo_percent,fill,advance_interval=0,new_mb,old_mb,time_to_tell;
 int old_track_idx,buffer_fill,formatting= 0,use_data_image_size;
 char fifo_text[80],mb_text[40], pending[40];
 char *debug_mark= ""; /* use this to prepend a marker text for experiments */

#ifndef Cdrskin_no_cdrfifO
 double buffer_size;
 int fs, bs, space;
#endif

#ifdef Cdrskin_use_libburn_fifO
 struct burn_source *current_fifo= NULL;

#ifdef NIX
 int size, free_space;
 char *status_text= "";
#endif

#endif /* Cdrskin_use_libburn_fifO */

 /* for debugging */
 static double last_fifo_in= 0.0,last_fifo_out= 0.0,curr_fifo_in,curr_fifo_out;

 if(Cdrskin__is_aborting(0))
   Cdrskin_abort(skin, 0); /* Never comes back */

 current_time= Sfile_microtime(0);
 elapsed_total_time= current_time-start_time;
 elapsed_time= current_time-*last_time;
 time_to_tell= (elapsed_time>=1.0)&&(elapsed_total_time>=1.0);
 written_total_bytes= *last_count; /* to be overwritten by p.sector */

 if(drive_status==BURN_DRIVE_FORMATTING)
   formatting= 1;
 if(drive_status==BURN_DRIVE_WRITING) {
   ;
 } else if(drive_status==BURN_DRIVE_WRITING_LEADIN
           || drive_status==BURN_DRIVE_WRITING_PREGAP
           || formatting) {
   if(time_to_tell || skin->is_writing) {
     if(skin->verbosity>=Cdrskin_verbose_progresS) {
       if(skin->is_writing)
         fprintf(stderr,"\n");
       fprintf(stderr,
           "%scdrskin: %s (burning since %.f seconds)%s",
           skin->pacifier_with_newline ? "" : "\r",
           (formatting?"formatting":"working pre-track"), elapsed_total_time,
           skin->pacifier_with_newline ? "\n" : "         ");
     }
     skin->is_writing= 0;
     advance_interval= 1;
   }
   {ret= 2; goto ex;}
 } else if(drive_status==BURN_DRIVE_WRITING_LEADOUT
           || drive_status==BURN_DRIVE_CLOSING_TRACK
           || drive_status==BURN_DRIVE_CLOSING_SESSION) {

   if(drive_status==BURN_DRIVE_CLOSING_SESSION &&
      skin->previous_drive_status!=drive_status)
     {printf("\nFixating...\n"); fflush(stdout);}
   if(time_to_tell || skin->is_writing) {
     if(skin->verbosity>=Cdrskin_verbose_progresS) {
       if(skin->is_writing && !skin->pacifier_with_newline)
         fprintf(stderr,"\n");
       fprintf(stderr,
               "%scdrskin: working post-track (burning since %.f seconds)%s",
               skin->pacifier_with_newline ? "" : "\r", elapsed_total_time,
               skin->pacifier_with_newline ? "\n" : "        ");
     }
     skin->is_writing= 0;
     advance_interval= 1;
   }
   {ret= 2; goto ex;}
 } else
   goto thank_you_for_patience;

 old_track_idx= skin->supposed_track_idx;
 skin->supposed_track_idx= p->track;

 if(old_track_idx>=0 && old_track_idx<skin->supposed_track_idx &&
    skin->supposed_track_idx < skin->track_counter) {
   Cdrtrack_get_size(skin->tracklist[old_track_idx],&fixed_size,&padding,
                     &sector_size,&use_data_image_size,1);
   if(skin->verbosity>=Cdrskin_verbose_progresS)
     printf("\n");
   printf("%sTrack %-2.2d: Total bytes read/written: %.f/%.f (%.f sectors).\n",
          debug_mark, old_track_idx + start_tno, fixed_size,fixed_size+padding,
          (fixed_size+padding)/sector_size);
   *last_count= 0;
 }

 sector_size= 2048.0;
 if(skin->supposed_track_idx>=0 && 
    skin->supposed_track_idx<skin->track_counter)
   Cdrtrack_get_size(skin->tracklist[skin->supposed_track_idx],&fixed_size,
                     &padding,&sector_size,&use_data_image_size,0);

 bytes_to_write= ((double) p->sectors)*sector_size;
 written_total_bytes= ((double) p->sector)*sector_size;
 written_bytes= written_total_bytes-*last_count;
 if(written_total_bytes<1024*1024) {
thank_you_for_patience:;
   if(time_to_tell || (skin->is_writing && elapsed_total_time>=1.0)) {
     if(skin->verbosity>=Cdrskin_verbose_progresS) {
       if(skin->is_writing) {
         fprintf(stderr,"\n");
       }
       pending[0]= 0;
/*
       if(bytes_to_write > 0 && skin->verbosity >= Cdrskin_verbose_debuG)
         sprintf(pending, " pnd %.f", bytes_to_write - written_total_bytes);
*/
       fprintf(stderr,
             "%scdrskin: thank you for being patient for %.f seconds%21.21s%s",
             skin->pacifier_with_newline ? "" : "\r",
             elapsed_total_time, pending,
             skin->pacifier_with_newline ? "\n" : "");
     }
     advance_interval= 1;
   }
   skin->is_writing= 0;
   {ret= 2; goto ex;}
 }
 new_mb= written_total_bytes/(1024*1024);
 old_mb= (*last_count)/(1024*1024);
 if(new_mb==old_mb && !(written_total_bytes>=skin->fixed_size &&
                        skin->fixed_size>0 && time_to_tell))
   {ret= 1; goto ex;}


#ifndef Cdrskin_extra_leaN

 percent= 0.0;
 if(bytes_to_write>0)
   percent= written_total_bytes/bytes_to_write*100.0;
 measured_total_speed= 0.0;
 measured_speed= 0.0;
 estim_time= -1.0;
 estim_minutes= -1.0;
 estim_seconds= -1.0;
 if(elapsed_total_time>0.0) {
   measured_total_speed= written_total_bytes/elapsed_total_time;
   estim_time= (bytes_to_write-written_bytes)/measured_total_speed;
   if(estim_time>0.0 && estim_time<86400.0) {
     estim_minutes= ((int) estim_time)/60;
     estim_seconds= estim_time-estim_minutes*60.0;
     if(estim_seconds<0.0)
       estim_seconds= 0.0;
   }
 }
 if(written_bytes==written_total_bytes && elapsed_total_time>0) {
   measured_speed= measured_total_speed;
 } else if(elapsed_time>0.0) {
   measured_speed= written_bytes/elapsed_time;
 } else if(written_bytes>0.0) {
   measured_speed= 99.91*Cdrskin_speed_factoR;
 }

 if(measured_speed<=0.0 && written_total_bytes>=skin->fixed_size && 
    skin->fixed_size>0) {
   if(!skin->is_writing)
     goto thank_you_for_patience;
   skin->is_writing= 0;
   measured_speed= measured_total_speed;
 } else 
   skin->is_writing= 1;
 if(skin->supposed_track_idx<0)
   skin->supposed_track_idx= 0;
 if(*last_count <= 0.0 && !skin->pacifier_with_newline)
   printf("\r%-78.78s\r", ""); /* wipe output line */
 if(skin->verbosity>=Cdrskin_verbose_progresS) {
   if(flag&1) {
     printf("%.f/%.f (%2.1f%%) @%1.1f, remaining %.f:%2.2d\n",
            written_total_bytes,bytes_to_write,percent,
            measured_speed/Cdrskin_speed_factoR,
            estim_minutes,(int) estim_seconds);
   } else {
     fill= 0;
     fifo_percent= 50;
     fifo_text[0]= 0;
     curr_fifo_in= last_fifo_in;
     curr_fifo_out= last_fifo_out;

     if(skin->cuefile_fifo != NULL)
       Cdrskin__libburn_fifo_status(skin->cuefile_fifo, fifo_text, 0);

#ifdef Cdrskin_use_libburn_fifO

     /* Inquire fifo fill and format fifo pacifier text */
     if(skin->fifo == NULL && skin->supposed_track_idx >= 0 && 
        skin->supposed_track_idx < skin->track_counter &&
        skin->fifo_size > 0 && fifo_text[0] == 0) {
       Cdrtrack_get_libburn_fifo(skin->tracklist[skin->supposed_track_idx],
                                 &current_fifo, 0);
       if(current_fifo != NULL) {
         Cdrskin__libburn_fifo_status(current_fifo, fifo_text, 0);
       } else if(skin->fifo_size > 0) {
         strcpy(fifo_text, "(fifo 100%) ");
       }
     }

#endif /* Cdrskin_use_libburn_fifO */

#ifndef Cdrskin_no_cdrfifO

     if(skin->fifo!=NULL) {
       ret= Cdrfifo_get_buffer_state(skin->fifo,&fill,&space,0);
       buffer_size= fill+space;
       if(ret==2 || ret==0) {
         fifo_percent= 100;
       } else if(ret>0 && buffer_size>0.0) {
         /* obtain minimum fill of pacifier interval */
         Cdrfifo_next_interval(skin->fifo,&fill,0);
         fifo_percent= 100.0*((double) fill)/buffer_size;
         if(fifo_percent<100 && fill>0)
           fifo_percent++;
       }
       if(skin->verbosity>=Cdrskin_verbose_debuG) {
         Cdrfifo_get_counters(skin->fifo,&curr_fifo_in,&curr_fifo_out,0);
         Cdrfifo_get_sizes(skin->fifo,&bs,&fs,0);
       }
       if(skin->fifo_size>0) {
         sprintf(fifo_text,"(fifo %3d%%) ",fifo_percent);
         if(skin->verbosity>=Cdrskin_verbose_debug_fifO) {
           fprintf(stderr,
                   "\ncdrskin_debug: fifo >= %9d / %d :  %8.f in, %8.f out\n",
                   fill,(int) buffer_size,
                   curr_fifo_in-last_fifo_in,curr_fifo_out-last_fifo_out);
           last_fifo_in= curr_fifo_in;
           last_fifo_out= curr_fifo_out;
         }
       }
     }

#endif /* ! Cdrskin_no_cdrfifO */

     if(skin->supposed_track_idx >= 0 && 
        skin->supposed_track_idx < skin->track_counter) {
       /* fixed_size,padding are fetched above via Cdrtrack_get_size() */;
     } else if(skin->fixed_size!=0) {
       fixed_size= skin->fixed_size;
       padding= skin->padding;
     }
     if(fixed_size || (skin->fill_up_media &&
                       skin->supposed_track_idx==skin->track_counter-1)) {
       sprintf(mb_text,"%4d of %4d",(int) (written_total_bytes/1024.0/1024.0),
               (int) ((double) Cdrtrack_get_sectors(
                          skin->tracklist[skin->supposed_track_idx],0)*
                      sector_size/1024.0/1024.0));
     } else
       sprintf(mb_text,"%4d",(int) (written_total_bytes/1024.0/1024.0));
     speed_factor= Cdrskin_speed_factoR*sector_size/2048;

     buffer_fill= 50;
     if(p->buffer_capacity>0)
       buffer_fill= (double) (p->buffer_capacity - p->buffer_available)*100.0
                    / (double) p->buffer_capacity;

     if(p->buffered_bytes > p->buffer_capacity)     
       if(buffer_fill < *min_buffer_fill)
         *min_buffer_fill= buffer_fill;

     printf("%s%sTrack %-2.2d: %s MB written %s[buf %3d%%]  %4.1fx.%s",
            skin->pacifier_with_newline ? "" : "\r",
            debug_mark, skin->supposed_track_idx + start_tno, mb_text,
            fifo_text, buffer_fill, measured_speed / speed_factor,
            skin->pacifier_with_newline ? "\n" : "");
     fflush(stdout);
   }
   if(skin->is_writing==0) {
     printf("\n");
     goto thank_you_for_patience;
   }
 }

#else /* ! Cdrskin_extra_leaN */

 if(skin->supposed_track_idx<0)
   skin->supposed_track_idx= 0;
 if(written_bytes<=0.0 && written_total_bytes>=skin->fixed_size && 
    skin->fixed_size>0) {
   if(!skin->is_writing)
     goto thank_you_for_patience;
   skin->is_writing= 0;
 } else {
   if((!skin->is_writing) && !skin->pacifier_with_newline)
     printf("\n");
   skin->is_writing= 1;
 }
 printf("%sTrack %-2.2d: %3d MB written %s",
        skin->pacifier_with_newline ? "" : "\r",
        skin->supposed_track_idx + start_tno,
        (int) (written_total_bytes / 1024.0 / 1024.0),
        skin->pacifier_with_newline ? "\n" : "");
 if(skin->is_writing == 0 && !skin->pacifier_with_newline)
   printf("\n");
 fflush(stdout);

#endif /* Cdrskin_extra_leaN */


 advance_interval= 1;
 ret= 1;
ex:; 
 if(advance_interval) {
   if(written_total_bytes>0)
     *last_count= written_total_bytes;
   else
     *last_count= 0.0;
   if(*last_count>*total_count)
     *total_count= *last_count;
   *last_time= current_time;
 }
 skin->previous_drive_status= drive_status;
 return(ret);
}


/** After everything else about burn_write_opts and burn_disc is set up, this
    call determines the effective write mode and checks whether the drive
    promises to support it.
*/
int Cdrskin_activate_write_mode(struct CdrskiN *skin,
                                struct burn_write_opts *opts,
                                struct burn_disc *disc,
                                int flag)
{
 int profile_number= -1, ret, was_still_default= 0;
 char profile_name[80], reasons[BURN_REASONS_LEN];
 enum burn_disc_status s= BURN_DISC_UNGRABBED;
 enum burn_write_types wt;

 profile_name[0]= 0;
 if(skin->grabbed_drive) {
   burn_disc_get_profile(skin->grabbed_drive,&profile_number,profile_name);
   s= burn_disc_get_status(skin->grabbed_drive);
 }
 if(strcmp(skin->preskin->write_mode_name,"DEFAULT")==0) {
   was_still_default= 1;
   wt= burn_write_opts_auto_write_type(opts, disc, reasons, 0);
   if(wt==BURN_WRITE_NONE) {
     if(strncmp(reasons,"MEDIA: ",7)==0)
       ret= -1;
     else
       ret= 0;
     goto report_failure;
   }
   skin->write_type= wt;

#ifndef Cdrskin_disable_raw96R
   if(wt==BURN_WRITE_RAW)
     strcpy(skin->preskin->write_mode_name,"RAW/RAW96R");
   else
#endif /* ! Cdrskin_disable_raw96R */
   if(wt==BURN_WRITE_TAO)
     strcpy(skin->preskin->write_mode_name,"TAO");
   else if(wt==BURN_WRITE_SAO)
     strcpy(skin->preskin->write_mode_name,"SAO");
   else
     sprintf(skin->preskin->write_mode_name,"LIBBURN/%d", (int) wt);
 }

#ifndef Cdrskin_disable_raw96R
 if(strcmp(skin->preskin->write_mode_name,"RAW/RAW96R")==0) {
   skin->write_type= BURN_WRITE_RAW;
   skin->block_type= BURN_BLOCK_RAW96R;
 } else
#endif /* ! Cdrskin_disable_raw96R */

 if(strcmp(skin->preskin->write_mode_name,"TAO")==0) {
   skin->write_type= BURN_WRITE_TAO;
   skin->block_type= BURN_BLOCK_MODE1;
 } else if(strncmp(skin->preskin->write_mode_name,"LIBBURN/",8)==0) {
   skin->block_type= BURN_BLOCK_MODE1;
 } else {
   strcpy(skin->preskin->write_mode_name,"SAO");
   skin->write_type= BURN_WRITE_SAO;
   skin->block_type= BURN_BLOCK_SAO;
   /* cdrecord tolerates the combination of -sao and -multi. -multi wins. */
   burn_write_opts_set_write_type(opts,skin->write_type,skin->block_type);
   ret = burn_precheck_write(opts,disc,reasons,0);
   if(ret <= 0) {
     if(strstr(reasons, "multi session capability lacking") != NULL) {
       fprintf(stderr,"cdrskin: WARNING : Cannot do SAO/DAO. Reason: %s\n",
               reasons);
       fprintf(stderr,"cdrskin: Defaulting to TAO/Incremental.\n");
       skin->write_type= BURN_WRITE_TAO;
       skin->block_type= BURN_BLOCK_MODE1;
     }
   }
 }
 if(!was_still_default)
   burn_write_opts_set_write_type(opts,skin->write_type,skin->block_type);
 ret = burn_precheck_write(opts,disc,reasons,0);
 if(ret<=0) {
report_failure:;
   if(ret!=-1)
     fprintf(stderr,"cdrskin: Reason: %s\n",reasons);
   fprintf(stderr,"cdrskin: Media : %s%s\n",
             s==BURN_DISC_BLANK?"blank ":
             s==BURN_DISC_APPENDABLE?"appendable ":
             s==BURN_DISC_FULL?"** closed ** ":"",
             profile_name[0]?profile_name:
             s==BURN_DISC_EMPTY?"no media":"unknown media");
   return(0);
 }
 if(skin->verbosity>=Cdrskin_verbose_cmD)
   printf("cdrskin: Write type : %s\n", skin->preskin->write_mode_name);
 return(1);
}


#ifndef Cdrskin_extra_leaN

int Cdrskin_announce_tracks(struct CdrskiN *skin, int start_tno, int flag)
{
 int i,mb,use_data_image_size;
 double size,padding,sector_size= 2048.0;
 double sectors;

 if(skin->verbosity>=Cdrskin_verbose_progresS) {
   if(start_tno < 1)
     start_tno= 1;
   for(i=0;i<skin->track_counter;i++) {
     Cdrtrack_get_size(skin->tracklist[i],&size,&padding,&sector_size,
                       &use_data_image_size,0);
     if(size<=0) {
       printf("Track %-2.2d: %s unknown length",
              i + start_tno, (sector_size==2048?"data ":"audio"));
     } else {
       mb= size/1024.0/1024.0;
       printf("Track %-2.2d: %s %4d MB        ",
              i + start_tno, (sector_size==2048?"data ":"audio"), mb);
     }
     if(padding>0)
       printf(" padsize:  %.f KB\n",padding/1024.0);
     else
       printf("\n");
   }
   if(skin->fixed_size<=0) {
     printf("Total size:       0 MB (00:00.00) = 0 sectors\n");
     printf("Lout start:       0 MB (00:02/00) = 0 sectors\n");
   } else {
     /* >>> This is quite a fake. Need to learn about 12:35.25 and "Lout" 
            ??? Is there a way to obtain the toc in advance (print_cue()) ? */
     double seconds;
     int min,sec,frac;

     mb= skin->fixed_size/1024.0/1024.0;
     seconds= skin->fixed_size/150.0/1024.0+2.0;
     min= seconds/60.0;
     sec= seconds-min*60;
     frac= (seconds-min*60-sec)*100;
     if(frac>99)
       frac= 99;
     sectors= (int) (skin->fixed_size/sector_size);
     if(sectors*sector_size != skin->fixed_size)
       sectors++;
     printf("Total size:    %5d MB (%-2.2d:%-2.2d.%-2.2d) = %d sectors\n",
             mb,min,sec,frac,(int) sectors);
     seconds+= 2;
     min= seconds/60.0;
     sec= seconds-min*60;
     frac= (seconds-min*60-sec)*100;
     if(frac>99)
       frac= 99;
     sectors+= 150;
     printf("Lout start:    %5d MB (%-2.2d:%-2.2d/%-2.2d) = %d sectors\n",
            mb,min,sec,frac,(int) sectors);
   }
 }
 return(1);
}

#endif /* ! Cdrskin_extra_leaN */


int Cdrskin_direct_write(struct CdrskiN *skin, int flag)
{
 off_t byte_address, data_count, chunksize, i, alignment, fill;
 int ret, max_chunksize= 64*1024, source_fd= -1, is_from_stdin, eof_sensed= 0;
 int self_opened= 0;
 char *buf= NULL, *source_path, amount_text[81];
 char *dummy_text= "";
 struct burn_multi_caps *caps= NULL;

 ret= Cdrskin_grab_drive(skin,0);
 if(ret<=0)
   goto ex;

 ret= burn_disc_get_multi_caps(skin->grabbed_drive,BURN_WRITE_NONE,&caps,0);
 if(ret<=0)
   goto ex;
 if(caps->start_adr==0) {
   fprintf(stderr,
      "cdrskin: SORRY : Direct writing is not supported by drive and media\n");
   {ret= 0; goto ex;}
 }
 alignment= caps->start_alignment;
 if(alignment>0 && (((off_t) skin->direct_write_amount) % alignment)!=0) {
   fprintf(stderr,
     "cdrskin: SORRY : direct_write_amount=%.f not aligned to blocks of %dk\n",
      skin->direct_write_amount,(int) alignment/1024);
   {ret= 0; goto ex;}
 }

 if(skin->track_counter<=0) {
   fprintf(stderr,
           "cdrskin: SORRY : No track source given for direct writing\n");
   {ret= 0; goto ex;}
 }
 Cdrtrack_get_source_path(skin->tracklist[0],
                          &source_path,&source_fd,&is_from_stdin,0);
 if(source_fd==-1) {
   ret= Cdrtrack_open_source_path(skin->tracklist[0],&source_fd,
                               2 | (skin->verbosity >= Cdrskin_verbose_debuG) |
                               (4 * (skin->fifo_size >= 256 * 1024)));
   if(ret<=0)
     goto ex;
   if(ret == 2)
     self_opened= 1;
 }
 buf= calloc(1, max_chunksize);
 if(buf==NULL) {
   fprintf(stderr,
           "cdrskin: FATAL : Cannot allocate %d bytes of read buffer.\n",
           max_chunksize);
   {ret= -1; goto ex;}
 }
 byte_address= skin->write_start_address;
 if(byte_address<0)
   byte_address= 0;
 data_count= skin->direct_write_amount;
 if(data_count>0)
   sprintf(amount_text,"%.fk",(double) (data_count/1024));
 else
   strcpy(amount_text,"0=open_ended");
 burn_drive_reset_simulate(skin->grabbed_drive, !!skin->dummy_mode);
 if(skin->dummy_mode)
   dummy_text= " simulation of";
 fprintf(stderr,"Beginning%s direct write (start=%.fk,amount=%s) ...\n",
         dummy_text, (double) (byte_address / 1024), amount_text);
 for(i= 0; i<data_count || data_count==0; i+= chunksize) {
   if(data_count==0)
     chunksize= (alignment > 0 ? alignment : 2048);
   else
     chunksize= data_count-i;
   if(chunksize>max_chunksize)
     chunksize= max_chunksize;

   /* read buffer from first track */
   for(fill= 0; fill<chunksize;fill+= ret) {
     ret= read(source_fd,buf+fill,chunksize-fill); 
     if(ret==-1) {
       fprintf(stderr,"cdrskin: FATAL : Error while reading from '%s'\n",
               source_path);
       if(errno>0)
         fprintf(stderr,"cdrskin: %s (errno=%d)\n", strerror(errno), errno);
       ret= 0; goto ex;
     } else if(ret==0) {
       eof_sensed= 1;
       if(data_count==0) {
         memset(buf+fill,0,(size_t) (chunksize-fill));
   break;
       } else {
         fprintf(stderr,
                 "cdrskin: FATAL : Premature EOF while reading from '%s'\n",
                 source_path);
         ret= 0; goto ex;
       }
     }
   }
   ret= burn_random_access_write(skin->grabbed_drive,byte_address,
                                 buf,chunksize,0);
   if(ret<=0)
     goto ex;
   if(eof_sensed)
 break;
   byte_address+= chunksize;
   fprintf(stderr, "%s%9.fk written     %s",
           skin->pacifier_with_newline ? "" : "\r",
           ((double) (i+chunksize)) / 1024.0,
           skin->pacifier_with_newline ? "\n" : ""); 
 }
 fprintf(stderr, "%s%9.fk written     \n",
         skin->pacifier_with_newline ? "" : "\r", ((double) i) / 1024.0); 
 /* flush drive buffer */
 fprintf(stderr,"syncing cache ...\n"); 
 ret = burn_random_access_write(skin->grabbed_drive,byte_address,buf,0,1);
 if(ret<=0)
   goto ex;
 ret= 1;
ex:;
 if(caps!=NULL)
   burn_disc_free_multi_caps(&caps);
 if(buf!=NULL)
   free(buf);
 if(self_opened && source_fd >= 0)
   close(source_fd);
 if(ret>0)
   fprintf(stderr,"writing done\n"); 
 else
   fprintf(stderr,"writing failed\n"); 
 return(ret);
}


int Cdrskin_grow_overwriteable_iso(struct CdrskiN *skin, int flag)
{
 int ret, i, went_well= 1;
 char *track_descr, *md;
 unsigned char *td; /* Because the representation of 255 in char is ambigous */
 double track_size, media_size;

 ret= Cdrtrack_get_iso_fs_descr(skin->tracklist[0],&track_descr,&track_size,0);
 if(ret<=0) {
   fprintf(stderr,"cdrskin: SORRY : Saw no ISO-9660 filesystem in track 0\n");
   return(ret);
 }
 if(skin->grow_overwriteable_iso==3) /* initial session */
   return(1);
 if(skin->grow_overwriteable_iso!=2) {
   fprintf(stderr,
          "cdrskin: SORRY : Could not read ISO-9660 descriptors from media\n");
   return(0);
 }
 ret= Scan_for_iso_size((unsigned char *) skin->overwriteable_iso_head+16*2048,
                        &media_size, 0);
 if(ret<=0) {
   fprintf(stderr,"cdrskin: SORRY : No recognizable ISO-9660 on media\n");
   return(0);
 }
 if(skin->write_start_address>=0.0)
   media_size= skin->write_start_address;

 /* Write new sum into media descr 0 */
 md= skin->overwriteable_iso_head+16*2048;
 memcpy(md,track_descr,2048);
 Set_descr_iso_size((unsigned char *) md,track_size+media_size,0);
 if(skin->verbosity>=Cdrskin_verbose_debuG)
   ClN(fprintf(stderr,"cdrskin_debug: new ISO-9660 size : %.f  (%fs)\n",
               track_size+media_size, (track_size+media_size)/2048));

 /* Copy type 255 CD001 descriptors from track to media descriptor buffer
    and adjust their size entries */
 for(i=1; i<16; i++) {
   td= (unsigned char *) (track_descr + i * 2048);
   md= skin->overwriteable_iso_head+(16+i)*2048;
   if(td[0] != 255)
 break;
   /* demand media descrN[0] == track descrN[0] */
   if(((char *) td)[0] != md[0]) {
     fprintf(stderr,
   "cdrskin: SORRY : Type mismatch of ISO volume descriptor #%d (%u <-> %u)\n",
             i, ((unsigned int) td[0]) & 0xff, ((unsigned int) md[0])&0xff);
     went_well= 0;
   }
   memcpy(md,td,2048);
   Set_descr_iso_size((unsigned char *) md,track_size+media_size,0);
 }
 if(skin->verbosity>=Cdrskin_verbose_debuG)
   ClN(fprintf(stderr,"cdrskin_debug: copied %d secondary ISO descriptors\n",
               i-1));

 /* write block 16 to 31 to media */
 if(skin->verbosity>=Cdrskin_verbose_debuG)
   ClN(fprintf(stderr,"cdrskin_debug: writing to media: blocks 16 to 31\n"));
 ret= burn_random_access_write(skin->grabbed_drive, (off_t) (16*2048),
                               skin->overwriteable_iso_head+16*2048,
                               (off_t) (16*2048), 1);
 if(ret<=0)
   return(ret);

 return(went_well);
}


int Cdrskin_cdtext_test(struct CdrskiN *skin, struct burn_write_opts *o,
                        struct burn_session *session, int flag)
{
 int ret, num_packs;
 unsigned char *text_packs = NULL;

 if(skin->num_text_packs > 0) {
   Cdrskin_print_text_packs(skin, skin->text_packs, skin->num_text_packs, 2);
   ret= 1; goto ex;
 }
 ret= burn_cdtext_from_session(session, &text_packs, &num_packs, 0);
 if(ret < 0)
   goto ex;
 if(ret > 0) {
   Cdrskin_print_text_packs(skin, text_packs, num_packs, 1);
   ret= 1; goto ex;
 }
 ret= 1;
ex:
 if (text_packs != NULL)
   free(text_packs);
 return(ret);
}


int Cdrskin_read_input_sheet_v07t(struct CdrskiN *skin, char *path, int block,
                        	struct burn_session *session, int flag)
{
 int ret= 0;

 ret= burn_session_input_sheet_v07t(session, path, block, 1);
 return(ret);
}


int Cdrskin_cdrtracks_from_session(struct CdrskiN *skin,
                                   struct burn_session *session, int flag)
{
 int ret, num_tracks, i;
 struct burn_track **tracks;
 struct CdrtracK *t;
 double sectors;

 for(i= 0; i < skin->track_counter; i++)
   Cdrtrack_destroy(&(skin->tracklist[i]), 0);
 skin->track_counter= 0;
 
 tracks= burn_session_get_tracks(session, &num_tracks);
 if(num_tracks <= 0) {
   fprintf(stderr, "cdrskin: SORRY : No tracks defined for burn run.\n");
   ret= 0; goto ex;
 }
 for(i= 0; i < num_tracks; i++) {
   /* Create Cdrtrack without reading parameters from skin */
   ret= Cdrtrack_new(&(skin->tracklist[skin->track_counter]), skin,
                     skin->track_counter, 1);
   if(ret <= 0) {
     fprintf(stderr,
             "cdrskin: FATAL : Creation of track control object failed.\n");
     goto ex;
   }

   /* Set essential properties */
   t= skin->tracklist[skin->track_counter];
   t->track_type= burn_track_get_mode(tracks[i]);
   if(t->track_type & BURN_AUDIO) {
     t->sector_size= 2352;
     t->track_type= BURN_AUDIO;
   } else {
     t->sector_size= 2048;
     t->track_type= BURN_MODE1;
   }
   sectors= burn_track_get_sectors(tracks[i]);
   t->fixed_size= sectors * t->sector_size;
   t->libburn_track= tracks[i];
   t->libburn_track_is_own= 0;

   skin->track_counter++;
 }
 ret= 1;
ex:
 if(ret <= 0) {
   for(i= 0; i < skin->track_counter; i++)
     Cdrtrack_destroy(&(skin->tracklist[i]), 0);
   skin->track_counter= 0;
 }
 return(ret);
}


int Cdrskin_write_result_string(struct CdrskiN *skin, char *msg, int flag)
{
 int ret;

 if(skin->preskin->result_fd < 0) {
   printf("%s",msg);
   return(1);
 }
 ret= write(skin->preskin->result_fd, msg, strlen(msg));
 if(ret != (int) strlen(msg))
   return(0);
 return(1);
}


/** Burn data via libburn according to the parameters set in skin.
    @return <=0 error, 1 success
*/
int Cdrskin_burn(struct CdrskiN *skin, int flag)
{
 struct burn_disc *disc = NULL;
 struct burn_session *session = NULL;
 struct burn_write_opts *o = NULL;
 struct burn_source *cuefile_fifo= NULL;
 enum burn_disc_status s;
 enum burn_drive_status drive_status;
 struct burn_progress p;
 struct burn_drive *drive;
 int ret,loop_counter= 0,max_track= -1,i,hflag,nwa,num, wrote_well= 2;
 int fifo_disabled= 0, min_buffer_fill= 101, length, start_tno= 1;
 int use_data_image_size, needs_early_fifo_fill= 0,iso_size= -1, non_audio= 0;
 double start_time,last_time;
 double total_count= 0.0,last_count= 0.0,size,padding,sector_size= 2048.0;
 char *doing;
 char *source_path;
 unsigned char *payload;
 int source_fd, is_from_stdin;
 int text_flag= 4; /* Check CRCs and silently repair CRCs if all are 0 */
 unsigned char *text_packs= NULL;
 int num_packs= 0, start_block, block_no, profile_number;

#ifndef Cdrskin_no_cdrfifO
 double put_counter, get_counter, empty_counter, full_counter;
 int total_min_fill, fifo_percent;
#endif
 off_t free_space;
 char msg[80], profile_name[80];

 if(skin->tell_media_space)
   doing= "estimating";
 else
   doing= "burning";
 printf("cdrskin: beginning to %s disc\n",
        skin->tell_media_space?"estimate":"burn");
 if(skin->fill_up_media && skin->multi) {
   ClN(fprintf(stderr,
           "cdrskin: NOTE : Option --fill_up_media disabled option -multi\n"));
   skin->multi= 0;
 }
 ret= Cdrskin_grab_drive(skin,0);
 if(ret<=0)
   goto burn_failed;
 drive= skin->drives[skin->driveno].drive;
 s= burn_disc_get_status(drive);
 if(skin->verbosity>=Cdrskin_verbose_progresS)
   Cdrskin_report_disc_status(skin,s,1);
 burn_disc_get_profile(drive, &profile_number, profile_name);

 disc= burn_disc_create();
 session= burn_session_create();
 ret= burn_disc_add_session(disc,session,BURN_POS_END);
 if(ret==0) {
   fprintf(stderr,"cdrskin: FATAL : Cannot add session to disc object.\n");
burn_failed:;
   ret= 0; goto ex;
 }
 skin->fixed_size= 0.0;
 skin->has_open_ended_track= 0;
 if(skin->cuefile[0]) {
   if(skin->track_counter > 0) {
     fprintf(stderr,
   "cdrskin: SORRY : Option cuefile= cannot be combined with track sources\n");
     goto burn_failed;
   }
   if(strcmp(skin->preskin->write_mode_name, "SAO") != 0 &&
      strcmp(skin->preskin->write_mode_name, "DEFAULT") != 0) {
     fprintf(stderr,
            "cdrskin: SORRY : Option cuefile= works only with write mode SAO");
     goto burn_failed;
   }
   strcpy(skin->preskin->write_mode_name, "SAO");

   ret= burn_session_by_cue_file(session, skin->cuefile, skin->fifo_size,
                                 &cuefile_fifo,
                                 &text_packs, &num_packs, !skin->use_cdtext);
   if(ret <= 0)
     goto burn_failed;
   if(num_packs > 0) {
     if(skin->num_text_packs > 0) {
       fprintf(stderr, "cdrskin: WARNING : Option textfile= overrides CDTEXTFILE from option cuesheet=\n");
       if(text_packs != NULL)
         free(text_packs);
     } else {
       skin->text_packs= text_packs;
       skin->num_text_packs= num_packs;
     }
   }
   text_packs= NULL; /* Now either freed or attached */
   ret= Cdrskin_cdrtracks_from_session(skin, session, 0);
   if(ret <= 0)
     goto burn_failed;
   skin->cuefile_fifo= cuefile_fifo;
   cuefile_fifo= NULL;

 } else {
   for(i=0;i<skin->track_counter;i++) {
     hflag= (skin->verbosity>=Cdrskin_verbose_debuG);
     if(i==skin->track_counter-1)
       Cdrtrack_ensure_padding(skin->tracklist[i],hflag&1);

/* if(skin->fifo_size >= 256 * 1024) */

     hflag|= 4;
     if(profile_number == 0x09 || profile_number == 0x0a)
       hflag|= 8;

     ret= Cdrtrack_add_to_session(skin->tracklist[i],i,session,hflag);
     if(ret<=0) {
       fprintf(stderr,"cdrskin: FATAL : Cannot add track %d to session.\n",i+1);
       goto burn_failed;
     }
     Cdrtrack_get_size(skin->tracklist[i],&size,&padding,&sector_size,
                       &use_data_image_size,0);
     if(use_data_image_size==1) {
       /* still unfulfilled -isosize demand pending */
       needs_early_fifo_fill= 1;
     }
     non_audio|= (skin->tracklist[i]->track_type != BURN_AUDIO);
   }
 }

 if(skin->sheet_v07t_blocks > 0) {
   if(skin->num_text_packs > 0) {
     fprintf(stderr, "cdrskin: WARNING : Option textfile= or cuefile= overrides option input_sheet_v07t=\n");
   } else if(non_audio) {
     fprintf(stderr, "cdrskin: SORRY : Option input_sheet_v07t= works only if all tracks are -audio\n");
     goto burn_failed;
   } else {
     /* If cuefile and session has block 0, then start at block 1 */
     start_block= 0;
     if(skin->cuefile[0]) {
       for(i= 0x80; i < 0x8f; i++) {
         ret= burn_session_get_cdtext(session, 0, i, "", &payload, &length, 0);
         if(ret > 0 && length > 0)
       break;
       }
       if(i < 0x8f)
         start_block= 1;
     }
     block_no = start_block;
     for(i= 0; i < skin->sheet_v07t_blocks && block_no < 8; i++) {
       ret= Cdrskin_read_input_sheet_v07t(skin, skin->sheet_v07t_paths[i],
                                          block_no, session, 0);
       if(ret <= 0)
         goto burn_failed;
       block_no += ret;
     }
     if(i < skin->sheet_v07t_blocks) {
       fprintf(stderr, "cdrskin: WARNING : Too many CD-TEXT blocks. input_sheet_v07t= files ignored: %d\n",
               skin->sheet_v07t_blocks - i);
     }
   }
 }

#ifndef Cdrskin_extra_leaN
 /* Final decision on track size has to be made after eventual -isosize
    determination via fifo content.
 */
 if(needs_early_fifo_fill && !skin->tell_media_space) {
   int start_memorized;

   start_memorized= skin->fifo_start_at;
   /* try  ISO-9660 size recognition via fifo */
   if(32*2048<=skin->fifo_size)
     skin->fifo_start_at= 32*2048;
   else
     skin->fifo_start_at= skin->fifo_size;
   ret= Cdrskin_fill_fifo(skin,0);
   if(ret<=0)
     goto fifo_filling_failed;
   if((start_memorized>skin->fifo_start_at || start_memorized<=0) &&
      skin->fifo_start_at<skin->fifo_size)
     needs_early_fifo_fill= 2; /* continue filling fifo at normal stage */
   skin->fifo_start_at= start_memorized;
 }
#endif /* Cdrskin_extra_leaN */

 for(i=0;i<skin->track_counter;i++) {
   Cdrtrack_get_size(skin->tracklist[i],&size,&padding,&sector_size,
                     &use_data_image_size,0);
   if(use_data_image_size==1 && size<=0 && skin->tell_media_space)
     size= 1024*1024; /* a dummy size */
   ret= Cdrtrack_activate_image_size(skin->tracklist[i],&size,
                                     !!skin->tell_media_space);
   if(ret<=0) {
     Cdrtrack_get_source_path(skin->tracklist[i],
                              &source_path,&source_fd,&is_from_stdin,0);
     fprintf(stderr,
           "cdrskin: FATAL : Cannot determine -isosize of track source\n");
     fprintf(stderr,
           "cdrskin:         '%s'\n", source_path);
     {ret= 0; goto ex;}
   }
   Cdrtrack_get_size(skin->tracklist[i],&size,&padding,&sector_size,
                     &use_data_image_size,0);
   if(use_data_image_size==2 && skin->verbosity>=Cdrskin_verbose_debuG)
     ClN(fprintf(stderr,
            "cdrskin: DEBUG: track %2.2d : activated -isosize %.fs (= %.fb)\n",
            i+1, size/2048.0,size));
   if(size>0)
     skin->fixed_size+= size+padding;
   else
     skin->has_open_ended_track= 1;
   if(skin->tracklist[i]->isrc[0] &&
      skin->tracklist[i]->libburn_track != NULL) {
      ret= burn_track_set_isrc_string(skin->tracklist[i]->libburn_track,
                                      skin->tracklist[i]->isrc, 0);
      if(ret <= 0)
        goto ex;
   }
 }
 
 o= burn_write_opts_new(drive);
 burn_write_opts_set_perform_opc(o, 0);

/* growisofs stunt: assessment of media and start for next session */
 if((skin->grow_overwriteable_iso==1 || skin->grow_overwriteable_iso==2) &&
     skin->media_is_overwriteable) {
   /* Obtain ISO size from media, keep 64 kB head in memory */
   ret= Cdrskin_overwriteable_iso_size(skin,&iso_size,0);
   if(ret<0)
     goto ex;
   if(ret>0 && skin->write_start_address<0) {
     skin->write_start_address= ((double) iso_size)*2048.0;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf(
            "cdrskin: write start address by --grow_overwriteable_iso : %ds\n",
            iso_size));
   } else if(ret==0)
     skin->grow_overwriteable_iso= 3; /* do not patch ISO header later on */
 } 

 burn_write_opts_set_start_byte(o, skin->write_start_address);

 if(skin->media_is_overwriteable && skin->multi) {
   if(skin->grow_overwriteable_iso<=0) {
     fprintf(stderr, "cdrskin: FATAL : -multi cannot leave a recognizable end mark on this media.\n");
     fprintf(stderr, "cdrskin: HINT  : For ISO-9660 images try --grow_overwriteable_iso -multi\n");
     {ret= 0; goto ex;}
   }
   skin->multi= 0;
 }
 if(skin->multi && !skin->media_does_multi) {
   if(skin->prodvd_cli_compatible) {
     skin->multi= 0;
     if(skin->verbosity>=Cdrskin_verbose_progresS)
       fprintf(stderr, "cdrskin: NOTE : Ignored option -multi.\n");
   }
 }
 burn_write_opts_set_multi(o,skin->multi);
 burn_write_opts_set_fillup(o, skin->fill_up_media);

 burn_write_opts_set_force(o, !!skin->force_is_set);
 burn_write_opts_set_stream_recording(o, skin->stream_recording_is_set);

#ifdef Cdrskin_dvd_obs_default_64K
 if(skin->dvd_obs == 0)
   burn_write_opts_set_dvd_obs(o, 64 * 1024);
 else
#endif
   burn_write_opts_set_dvd_obs(o, skin->dvd_obs);
 burn_write_opts_set_obs_pad(o, skin->obs_pad);
 burn_write_opts_set_stdio_fsync(o, skin->stdio_sync);

 if(skin->dummy_mode) {
   fprintf(stderr,
           "cdrskin: NOTE : -dummy mode will prevent actual writing\n");
   burn_write_opts_set_simulate(o, 1);
 }
 burn_write_opts_set_underrun_proof(o,skin->burnfree);
 if(skin->num_text_packs > 0) {
   if(non_audio) {
     fprintf(stderr, "cdrskin: SORRY : Option textfile= works only if all tracks are -audio\n");
     goto burn_failed;
   }
   if(!!skin->force_is_set)
	text_flag= 1; /* No CRC verification or repairing */
   ret= burn_write_opts_set_leadin_text(o, skin->text_packs,
                                        skin->num_text_packs, text_flag);
   if(ret <= 0)
     goto burn_failed;
 }
 if(skin->mcn[0]) {
   burn_write_opts_set_mediacatalog(o, (unsigned char *) skin->mcn);
   burn_write_opts_set_has_mediacatalog(o, 1);
 }
 if(skin->cd_start_tno >= 1 && skin->cd_start_tno <= 99) {
   ret= burn_session_set_start_tno(session, skin->cd_start_tno, 0);
   if(ret <= 0)
     goto burn_failed;
 }
 ret= Cdrskin_activate_write_mode(skin,o,disc,0);
 if(ret<=0)
   goto burn_failed;

 if(skin->cdtext_test) {
   ret= Cdrskin_cdtext_test(skin, o, session, (skin->cdtext_test == 1));
   if(ret <= 0)
     goto ex;
   if(skin->cdtext_test >= 2) {
     fprintf(stderr,
       "cdrskin: Option --cdtext_dummy prevents actual burn run\n");
     ret= 1; goto ex;
   }
 }

 ret= Cdrskin_obtain_nwa(skin, &nwa,0);
 if(ret<=0)
   nwa= -1;
 if(skin->assert_write_lba>=0 && nwa!=skin->assert_write_lba) {
   fprintf(stderr,
       "cdrskin: FATAL : Option assert_write_lba= demands block number %10d\n",
       skin->assert_write_lba);
   fprintf(stderr,
       "cdrskin: FATAL : but predicted actual write start address is   %10d\n",
       nwa);
   {ret= 0; goto ex;}
 }

#ifndef Cdrskin_extra_leaN
 Cdrskin_announce_tracks(skin, burn_session_get_start_tno(session, 0), 0);
#endif

 if(skin->tell_media_space || skin->track_counter <= 0) {
   /* write capacity estimation and return without actual burning */

   free_space= burn_disc_available_space(drive,o);
   sprintf(msg,"%d\n",(int) (free_space/(off_t) 2048));
   Cdrskin_write_result_string(skin, msg, 0);
   if(skin->track_counter>0)
     fprintf(stderr,
       "cdrskin: NOTE : %s burn run suppressed by option --tell_media_space\n",
       skin->preskin->write_mode_name);
   {ret= 1; goto ex;}
 }

 if(skin->fixed_size > 0 && !skin->force_is_set) {
   free_space= burn_disc_available_space(drive,o);
   if(skin->fixed_size > free_space && free_space > 0) {
     fprintf(stderr,
 "cdrskin: FATAL : predicted session size %lus does not fit on media (%lus)\n",
             (unsigned long) ((skin->fixed_size + 2047.0) / 2048.0),
             (unsigned long) ((free_space + 2047) / 2048));
     ClN(fprintf(stderr,
              "cdrskin: HINT : This test may be disabled by option -force\n");)
     {ret= 0; goto ex;}
   }
 }

 Cdrskin_adjust_speed(skin,0);

#ifndef Cdrskin_extra_leaN
 Cdrskin_wait_before_action(skin,0);
 if(burn_is_aborting(0))
   {ret= 0; goto ex;}

 if(needs_early_fifo_fill==1)
   ret= 1;
 else if(skin->cuefile[0] != 0)
   ret= 1;
 else
   ret= Cdrskin_fill_fifo(skin,0);
 if(ret<=0) {
fifo_filling_failed:;
   fprintf(stderr,"cdrskin: FATAL : Filling of fifo failed\n");
   goto ex;
 }

#endif /* ! Cdrskin_extra_leaN */

 start_tno = burn_session_get_start_tno(session, 0);
 if(skin->verbosity>=Cdrskin_verbose_progresS && nwa>=0) {
   printf("Starting new track at sector: %d\n",nwa);
   fflush(stdout);
 }
 if(burn_is_aborting(0))
   {ret= 0; goto ex;}
 skin->drive_is_busy= 1;
 burn_disc_write(o, disc);
 if(skin->preskin->abort_handler==-1)
   Cleanup_set_handlers(Cleanup_handler_handlE, Cleanup_handler_funC,
                        Cleanup_handler_flaG);
 last_time= start_time= Sfile_microtime(0);

 burn_write_opts_free(o);
 o= NULL;

 while (burn_drive_get_status(drive, NULL) == BURN_DRIVE_SPAWNING) {

   /* >>> how do i learn about success or failure ? */

   ;
 }
 loop_counter= 0;
 while (1) {
    drive_status= burn_drive_get_status(drive, &p);
    if(drive_status==BURN_DRIVE_IDLE)
 break;

   /* >>> how do i learn about success or failure ? */

   if(loop_counter>0 || Cdrskin__is_aborting(0))
     Cdrskin_burn_pacifier(skin, start_tno,
                           drive_status,&p,start_time,&last_time,
                           &total_count,&last_count,&min_buffer_fill,0);

   if(max_track<skin->supposed_track_idx)
      max_track= skin->supposed_track_idx;


#ifndef Cdrskin_extra_leaN

   /* <<< debugging : artificial abort without a previous signal */;
   if(skin->abort_after_bytecount>=0.0 && 
      total_count>=skin->abort_after_bytecount) {
      /* whatever signal handling is installed: this thread is the boss now */
      fprintf(stderr,
       "cdrskin: DEVELOPMENT : synthetic abort by abort_after_bytecount=%.f\n",
              skin->abort_after_bytecount);
      skin->control_pid= getpid();
      ret= Cdrskin_abort_handler(skin,0,0);
      fprintf(stderr,"cdrskin: done (aborted)\n");
      exit(1);
   }

   if(Cdrskin__is_aborting(0))
     fifo_disabled= 1;
   if(skin->fifo==NULL || fifo_disabled) {
     usleep(20000);
   } else {

#ifdef Cdrskin_no_cdrfifO

     /* Should never happen as skin->fifo should be NULL */
     usleep(20000);

#else /* Cdrskin_no_cdrfifO */

     ret= Cdrfifo_try_to_work(skin->fifo,20000,NULL,NULL,0);
     if(ret<0) {
       int abh;

       abh= skin->preskin->abort_handler;
       if(abh!=2) 
         fprintf(stderr,
              "\ncdrskin: FATAL : Fifo encountered error during burn loop.\n");
       if(abh==0) {
         ret= -1; goto ex;
       } else if(abh==1 || abh==3 || abh==4 || abh==-1) {
         Cdrskin_abort_handler(skin,0,0);
         fprintf(stderr,"cdrskin: done (aborted)\n");
         exit(10);
       } else {
          if(skin->verbosity>=Cdrskin_verbose_debuG)
            fprintf(stderr,
                    "\ncdrskin_debug: Cdrfifo_try_to_work() returns %d\n",ret);
       }
     }
     if(ret==2) { /* <0 = error , 2 = work is done */
       if(skin->verbosity>=Cdrskin_verbose_debuG)
         fprintf(stderr,"\ncdrskin_debug: fifo ended work with ret=%d\n",ret);
       fifo_disabled= 1;
     }

#endif /* ! Cdrskin_no_cdrfifO */

   }

#else /* ! Cdrskin_extra_leaN */
   usleep(20000);
#endif /* Cdrskin_extra_leaN */

   loop_counter++;
 }
 skin->drive_is_busy= 0;
 if(skin->verbosity>=Cdrskin_verbose_progresS)
   printf("\n");

 if(Cdrskin__is_aborting(0))
   Cdrskin_abort(skin, 0); /* Never comes back */

 wrote_well = burn_drive_wrote_well(drive);
 if(skin->media_is_overwriteable && skin->grow_overwriteable_iso>0 &&
    wrote_well) {
   /* growisofs final stunt : update volume descriptors at start of media */
   ret= Cdrskin_grow_overwriteable_iso(skin,0);
   if(ret<=0)
     wrote_well= 0;
 }
 if(max_track<0) {
   printf("Track %-2.2d: Total bytes read/written: %.f/%.f (%.f sectors).\n",
          start_tno, total_count, total_count, total_count / sector_size);
 } else {
   Cdrtrack_get_size(skin->tracklist[max_track],&size,&padding,&sector_size,
                     &use_data_image_size,1);
   if (start_tno <= 0)
     start_tno = 1;
   printf("Track %-2.2d: Total bytes read/written: %.f/%.f (%.f sectors).\n",
         max_track + start_tno, size,size+padding,(size+padding)/sector_size);
 }
 if(skin->verbosity>=Cdrskin_verbose_progresS)
   printf("Writing  time:  %.3fs\n",Sfile_microtime(0)-start_time);


#ifndef Cdrskin_extra_leaN

#ifdef Cdrskin_use_libburn_fifO

 if(skin->fifo == NULL && skin->verbosity>=Cdrskin_verbose_progresS) {
   /* >>> this should rather be done for each track
          (for now this libburn_fifo should only be used with single track)
   */
   Cdrtrack_report_fifo(skin->tracklist[skin->track_counter - 1], 0);
 }

#endif /* Cdrskin_use_libburn_fifO */

#ifndef Cdrskin_no_cdrfifO

 if(skin->fifo!=NULL && skin->fifo_size>0 && wrote_well) {
   int dummy,final_fill;
   Cdrfifo_get_buffer_state(skin->fifo,&final_fill,&dummy,0);
   if(final_fill>0) {
fifo_full_at_end:;
     fprintf(stderr,
       "cdrskin: FATAL : Fifo still contains data after burning has ended.\n");
     fprintf(stderr,
       "cdrskin: FATAL : %.d bytes left.\n",final_fill);
     fprintf(stderr,
       "cdrskin: FATAL : This indicates an overflow of the last track.\n");
     fprintf(stderr,
     "cdrskin: NOTE : The media might appear ok but is probably truncated.\n");
     ret= -1; goto ex;
   }

#ifdef Cdrskin_libburn_leaves_inlet_opeN
   for(i= 0;i<skin->track_counter;i++) {
     ret= Cdrtrack_has_input_left(skin->tracklist[i],0);
     if(ret>0) {
       fprintf(stderr,
  "cdrskin: FATAL : Fifo outlet of track #%d is still buffering some bytes.\n",
               i+1);
       goto fifo_full_at_end;
     }
   }
#endif /* Cdrskin_libburn_leaves_inlet_opeN */

 }

 if(skin->verbosity>=Cdrskin_verbose_progresS) {
   if(skin->fifo!=NULL && skin->fifo_size>0) {
     int dummy;

     Cdrfifo_get_min_fill(skin->fifo,&total_min_fill,&dummy,0);
     fifo_percent= 100.0*((double) total_min_fill)/(double) skin->fifo_size;
     if(fifo_percent==0 && total_min_fill>0)
       fifo_percent= 1;
     Cdrfifo_get_cdr_counters(skin->fifo,&put_counter,&get_counter,
                              &empty_counter,&full_counter,0);
     fflush(stdout);
     fprintf(stderr,"Cdrskin: fifo had %.f puts and %.f gets.\n",
             put_counter,get_counter);
     fprintf(stderr,
  "Cdrskin: fifo was %.f times empty and %.f times full, min fill was %d%%.\n",
             empty_counter,full_counter,fifo_percent);
   }
 }

#endif /* ! Cdrskin_no_cdrfifO */

 if(skin->verbosity>=Cdrskin_verbose_progresS) {
   drive_status= burn_drive_get_status(drive, &p);
   if(p.buffer_min_fill<=p.buffer_capacity && p.buffer_capacity>0) {
     num= 100.0 * ((double) p.buffer_min_fill)/(double) p.buffer_capacity;
     if(num<min_buffer_fill)
       min_buffer_fill= num; 
   }
   if(min_buffer_fill>100)
     min_buffer_fill= 50;
   printf("Min drive buffer fill was %d%%\n", min_buffer_fill);
 }

#endif /* ! Cdrskin_extra_leaN */

 ret= 1;
 if(wrote_well) {
   if(skin->verbosity>=Cdrskin_verbose_progresS) 
     printf("cdrskin: burning done\n");
 } else
   ret= 0;
ex:;
 if(ret<=0) {
   if(skin->verbosity>=Cdrskin_verbose_progresS) 
     printf("cdrskin: %s failed\n",doing);
   fprintf(stderr,"cdrskin: FATAL : %s failed.\n",doing);
 }
 skin->drive_is_busy= 0;
 if(skin->verbosity>=Cdrskin_verbose_debuG)
   ClN(printf("cdrskin_debug: do_eject= %d\n",skin->do_eject));
 for(i= 0;i<skin->track_counter;i++)
   Cdrtrack_cleanup(skin->tracklist[i],0);
 if(o != NULL)
   burn_write_opts_free(o);
 if(cuefile_fifo != NULL)
   burn_source_free(cuefile_fifo);
 if(session != NULL)
   burn_session_free(session);
 if(disc != NULL)
   burn_disc_free(disc);
 return(ret);
}


#ifdef Libburn_develop_quality_scaN

int Cdrskin_qcheck(struct CdrskiN *skin, int flag)
{
 struct burn_drive *drive;
 int ret, rate_period, profile_number;
 char profile_name[80]; 

 printf("cdrskin: beginning to perform quality check on disc\n");
 ret= Cdrskin_grab_drive(skin,0);
 if(ret<=0)
   return(ret);
 drive= skin->drives[skin->driveno].drive;

 ret= burn_disc_get_profile(drive, &profile_number, profile_name);
 if(ret <= 0)
   profile_number= 0;
 if(profile_number != 0x08 && profile_number != 0x09 && profile_number != 0x0a)
   rate_period= 8;
 else
   rate_period= 75;
 if(skin->do_qcheck == 1) {
   ret= burn_nec_optiarc_rep_err_rate(drive, 0, rate_period, 0);
   if(ret<=0)
     return(ret);
 }
 return(1);
}

#endif /* Libburn_develop_quality_scaN */


/** Print lba of first track of last session and Next Writeable Address of
    the next unwritten session.
*/
int Cdrskin_msinfo(struct CdrskiN *skin, int flag)
{
 int num_sessions, session_no, ret, num_tracks, open_sessions= 0;
 int nwa= -123456789, lba= -123456789, aux_lba, is_bdr_pow= 0;
 char msg[80];
 enum burn_disc_status s;
 struct burn_drive *drive;
 struct burn_disc *disc= NULL;
 struct burn_session **sessions= NULL;
 struct burn_track **tracks;
 struct burn_toc_entry toc_entry;

 ret= Cdrskin_grab_drive(skin,0);
 if(ret<=0)
   return(ret);
 drive= skin->drives[skin->driveno].drive;
 s= burn_disc_get_status(drive);
 if(s!=BURN_DISC_APPENDABLE) {
   if(skin->grow_overwriteable_iso==1 || skin->grow_overwriteable_iso==2) {
     lba= 0;
     ret= Cdrskin_overwriteable_iso_size(skin,&nwa,0);
     if(ret>0) {
       s= BURN_DISC_APPENDABLE;
       goto put_out;
     }
   }
   Cdrskin_report_disc_status(skin,s,0);
   fprintf(stderr,"cdrskin: FATAL : -msinfo can only operate on appendable (i.e. -multi) discs\n");
   if(skin->grow_overwriteable_iso>0)
     fprintf(stderr,"cdrskin:         or on overwriteables with existing ISO-9660 file system.\n");
   {ret= 0; goto ex;}
 }
 is_bdr_pow= burn_drive_get_bd_r_pow(drive);
 if(is_bdr_pow) {
   fprintf(stderr,
      "cdrskin: FATAL : -msinfo cannot operate on POW formatted BD-R discs\n");
   {ret= 0; goto ex;}
 }
 disc= burn_drive_get_disc(drive);
 if(disc==NULL) {
   /* No TOC available. Try to inquire directly. */
   ret= burn_disc_get_msc1(drive,&lba);
   if(ret>0)
     goto obtain_nwa;
   fprintf(stderr,"cdrskin: FATAL : Cannot obtain info about CD content\n");
   {ret= 0; goto ex;}
 }
 sessions= burn_disc_get_sessions(disc,&num_sessions);
 open_sessions= burn_disc_get_incomplete_sessions(disc);
 for(session_no= 0; session_no < num_sessions + open_sessions; session_no++) {
   tracks= burn_session_get_tracks(sessions[session_no],&num_tracks);
   if(tracks==NULL || num_tracks<=0)
 continue;
   burn_track_get_entry(tracks[0],&toc_entry);
   if(toc_entry.extensions_valid&1) { /* DVD extension valid */
     if(session_no >= num_sessions) {
       if(!(toc_entry.extensions_valid & 4))
 continue; /* open session with no track status bits from libburn */
       if((toc_entry.track_status_bits & (1 << 14)) ||
          !((toc_entry.track_status_bits & (1 << 16)) ||
            ((toc_entry.track_status_bits & (1 << 17)) &&
             toc_entry.last_recorded_address > toc_entry.start_lba)))
 continue; /* Blank or not appendable and not recorded */
     }   
     lba= toc_entry.start_lba;
   } else {
     lba= burn_msf_to_lba(toc_entry.pmin,toc_entry.psec,toc_entry.pframe);
   }
 }
 if(lba==-123456789) {
   fprintf(stderr,"cdrskin: FATAL : Cannot find any track on CD\n");
   {ret= 0; goto ex;}
 }

obtain_nwa:;
 ret= Cdrskin_obtain_nwa(skin,&nwa,flag);
 if(ret<=0) {
   if (sessions == NULL) {
     fprintf(stderr,
           "cdrskin: SORRY : Cannot obtain next writeable address\n");
     {ret= 0; goto ex;}
   }
   ClN(fprintf(stderr,
       "cdrskin: NOTE : Guessing next writeable address from leadout\n"));
   burn_session_get_leadout_entry(sessions[num_sessions-1],&toc_entry);
   if(toc_entry.extensions_valid&1) { /* DVD extension valid */
     aux_lba= toc_entry.start_lba;
   } else {
     aux_lba= burn_msf_to_lba(toc_entry.pmin,toc_entry.psec,toc_entry.pframe);
   }
   if(num_sessions>0)
     nwa= aux_lba+6900;
   else
     nwa= aux_lba+11400;
 }

put_out:;
 sprintf(msg,"%d,%d\n",lba,nwa);
 Cdrskin_write_result_string(skin, msg, 0);

 if(strlen(skin->msifile)) {
   FILE *fp;

   fp = fopen(skin->msifile, "w");
   if(fp==NULL) {
     if(errno>0)
       fprintf(stderr,"cdrskin: %s (errno=%d)\n", strerror(errno), errno);
     fprintf(stderr,"cdrskin: FATAL : Cannot write msinfo to file '%s'\n",
             skin->msifile);
     {ret= 0; goto ex;}
   }
   fprintf(fp,"%d,%d",lba,nwa);
   fclose(fp);
 }
 ret= 1;
ex:;
 if(disc!=NULL)
   burn_disc_free(disc);
 return(ret);
}


/** Work around the failure of libburn to eject the tray.
    This employs a system(2) call and is therefore an absolute no-no for any
    pseudo user identities.
    @return <=0 error, 1 success
*/
int Cdrskin_eject(struct CdrskiN *skin, int flag)
{
 int i,ret,max_try= 5;

 if(!skin->do_eject)
   return(1);

 if((int) skin->n_drives <= skin->driveno || skin->driveno < 0)
   return(2);

 /* ??? A61012 : retry loop might now be obsolete 
                (a matching bug in burn_disc_write_sync() was removed ) */
 /* A61227 : A kindof race condition with -atip -eject on SuSE 9.3. Loop saved
             me. Waiting seems to help. I suspect the media demon. */

 for(i= 0;i<max_try;i++) {
   ret= Cdrskin_grab_drive(skin,2|((i<max_try-1)<<2)|16);
   if(ret>0 || i>=max_try-1)
 break;
   if(skin->verbosity>=Cdrskin_verbose_progresS)
     ClN(fprintf(stderr,
          "cdrskin: NOTE : Attempt #%d of %d failed to grab drive for eject\n",
          i+1,max_try));
   usleep(1000000);
 }
 if(ret>0) {
    ret= Cdrskin_release_drive(skin,1);
    if(ret<=0)
      goto sorry_failed_to_eject;
 } else {
sorry_failed_to_eject:;
   fprintf(stderr,"cdrskin: SORRY : Failed to finally eject tray.\n");
   return(0);
 }
 return(1);
}


/** Interpret all arguments of the program after libburn has been initialized
    and drives have been scanned. This call reports to stderr any valid 
    cdrecord options which are not implemented yet.
    @param flag Bitfield for control purposes: 
                bit0= do not finalize setup
                bit1= do not interpret (again) skin->preskin->pre_argv
    @return <=0 error, 1 success
*/
int Cdrskin_setup(struct CdrskiN *skin, int argc, char **argv, int flag)
{
 int i,k,l,ret, idx= -1, cd_start_tno;
 double value,grab_and_wait_value= -1.0, num;
 char *cpt,*value_pt,adr[Cdrskin_adrleN],*blank_mode= "", *argpt;
 struct stat stbuf;

 /* cdrecord 2.01 options which are not scheduled for implementation, yet */
 static char ignored_partial_options[][41]= {
   "timeout=", "debug=", "kdebug=", "kd=", "driver=", "ts=",
   "pregap=", "defpregap=",
   "pktsize=",
   ""
 };
 static char ignored_full_options[][41]= {
   "-d", "-silent", "-s", "-setdropts", "-prcap", 
   "-reset", "-abort", "-overburn", "-ignsize", "-useinfo",
   "-fix", "-nofix",
   "-raw", "-raw96p", "-raw16", "-raw96r",
   "-clone",
   "-cdi", 
   "-shorttrack", "-noshorttrack", "-packet", "-noclose",
   ""
 };

 /* are we pretending to be cdrecord ? */
 cpt= strrchr(argv[0],'/');
 if(cpt==NULL)
   cpt= argv[0];
 else
   cpt++;
 if(strcmp(cpt,"cdrecord")==0 && !(flag&1)) {
   fprintf(stderr,"\n");
   fprintf(stderr,
     "Note: This is not cdrecord by Joerg Schilling. Do not bother him.\n");
   fprintf(stderr,
     "      See cdrskin start message on stdout. See --help. See -version.\n");
   fprintf(stderr,"\n");
   /* allow automatic -tao to -sao redirection */
   skin->tao_to_sao_tsize=650*1024*1024;
 }

#ifndef Cdrskin_extra_leaN
 if(!(flag&2)) {
   if(skin->preskin->pre_argc>1) {
     ret= Cdrskin_setup(skin,skin->preskin->pre_argc,skin->preskin->pre_argv,
                        flag|1|2);
     if(ret<=0)
       return(ret);
   }
 }
#endif

 for (i= 1;i<argc;i++) {

   argpt= argv[i];
   if (strncmp(argpt, "--", 2) == 0 && strlen(argpt) > 3)
     argpt++;

   /* is this a known option which is not planned to be implemented ? */
   /* such an option will not be accepted as data source */
   for(k=0;ignored_partial_options[k][0]!=0;k++) {
     if(argpt[0]=='-')
       if(strncmp(argpt+1,ignored_partial_options[k],
                          strlen(ignored_partial_options[k]))==0)
         goto no_volunteer;
     if(strncmp(argpt,ignored_partial_options[k],
                      strlen(ignored_partial_options[k]))==0)
       goto no_volunteer;
   }
   for(k=0;ignored_full_options[k][0]!=0;k++) 
     if(strcmp(argpt,ignored_full_options[k])==0) 
       goto no_volunteer;
   if(0) {
no_volunteer:;
     skin->preskin->demands_cdrecord_caps= 1;
     if(skin->preskin->fallback_program[0])
       fprintf(stderr,"cdrskin: NOTE : Unimplemented option: '%s'\n",argpt);
     else  
       fprintf(stderr,"cdrskin: NOTE : ignoring unimplemented option : '%s'\n",
               argpt);
     fprintf(stderr,
       "cdrskin: NOTE : option is waiting for a volunteer to implement it.\n");
 continue;
   }

#ifndef Cdrskin_extra_leaN
   if(strncmp(argv[i],"abort_after_bytecount=",22)==0) {
     skin->abort_after_bytecount= Scanf_io_size(argv[i]+22,0);
     fprintf(stderr,
             "cdrskin: NOTE : will perform synthetic abort after %.f bytes\n",
             skin->abort_after_bytecount);

   } else if(strcmp(argv[i],"--abort_handler")==0) {
#else /* ! Cdrskin_extra_leaN */
   if(strcmp(argv[i],"--abort_handler")==0) {
#endif
     /* is handled in Cdrpreskin_setup() */;

   } else if(strncmp(argv[i],"-abort_max_wait=",16)==0) {
     value_pt= argv[i]+16;
     goto set_abort_max_wait;

   } else if(strncmp(argv[i],"abort_max_wait=",15)==0) {
     value_pt= argv[i]+15;
set_abort_max_wait:;
     value= Scanf_io_size(value_pt,0);
     if(value<0 || value>86400) {
       fprintf(stderr,
             "cdrskin: NOTE : ignored out-of-range value: abort_max_wait=%s\n",
             value_pt);
     } else {
       skin->abort_max_wait= value;
       if(skin->verbosity>=Cdrskin_verbose_cmD)
         printf(
            "cdrskin: maximum waiting time with abort handling : %d seconds\n",
            skin->abort_max_wait);
     }

   } else if(strcmp(argv[i],"--adjust_speed_to_drive")==0) {
     skin->adjust_speed_to_drive= 1;

   } else if(strcmp(argv[i],"--allow_emulated_drives")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argv[i],"--allow_setuid")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argv[i],"--allow_untested_media")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argv[i],"--any_track")==0) {
     skin->single_track= -1;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf(
   "cdrskin: --any_track : will accept any unknown option as track source\n"));

   } else if(strncmp(argv[i],"assert_write_lba=",17)==0) {
     value_pt= argv[i]+17;
     value= Scanf_io_size(value_pt,0);
     l= strlen(value_pt);
     if(l>1) if(isalpha(value_pt[l-1]))
       value/= 2048.0;
     skin->assert_write_lba= value; 

   } else if(strcmp(argpt,"-atip")==0) {
     if(skin->do_atip<1)
       skin->do_atip= 1;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: will put out some -atip style lines\n"));

   } else if(strcmp(argpt,"-audio")==0) {
     skin->track_type= BURN_AUDIO;
     skin->track_type_by_default= 0;

   } else if(strncmp(argpt,"-blank=",7)==0) {
     cpt= argpt + 7;
     goto set_blank;
   } else if(strncmp(argpt,"blank=",6)==0) {
     cpt= argpt + 6;
set_blank:;
     skin->blank_format_type= 0;
     blank_mode= cpt;
     if(strcmp(cpt,"all")==0 || strcmp(cpt,"disc")==0 
        || strcmp(cpt,"disk")==0) {
       skin->do_blank= 1;
       skin->blank_fast= 0;
       blank_mode= "all";
     } else if(strcmp(cpt,"fast")==0 || strcmp(cpt,"minimal")==0) { 
       skin->do_blank= 1;
       skin->blank_fast= 1;
       blank_mode= "fast";
     } else if(strcmp(cpt,"format_if_needed")==0) {
       skin->do_blank= 1;
       skin->blank_format_type= 6;
       skin->preskin->demands_cdrskin_caps= 1;
     } else if(strcmp(cpt,"format_overwrite")==0) {
       skin->do_blank= 1;
       skin->blank_format_type= 1|(1<<8);
       skin->blank_format_size= 128*1024*1024;
       skin->preskin->demands_cdrskin_caps= 1;
     } else if(strcmp(cpt,"format_overwrite_full")==0) {
       skin->do_blank= 1;
       skin->blank_format_type= 1|(1<<10);
       skin->blank_format_size= 0;
       skin->preskin->demands_cdrskin_caps= 1;
     } else if(strcmp(cpt,"format_overwrite_quickest")==0) {
       skin->do_blank= 1;
       skin->blank_format_type= 1;
       skin->blank_format_size= 0;
       skin->preskin->demands_cdrskin_caps= 1;
     } else if(strncmp(cpt,"format_defectmgt",16)==0) {
       skin->do_blank= 1;
       skin->blank_format_type= 4|(3<<9);  /* default payload size */
       skin->blank_format_size= 0;
       skin->preskin->demands_cdrskin_caps= 1;
       if(cpt[16]=='_') {
         cpt+= 17;
         if(strcmp(cpt,"none")==0)
           skin->blank_format_type= 4|(1<<13);
         else if(strcmp(cpt,"max")==0)
           skin->blank_format_type= 4;  /* smallest payload size above 0 */
         else if(strcmp(cpt,"min")==0)
           skin->blank_format_type= 4|(2<<9); /*largest payload size with mgt*/
         else if(strncmp(cpt,"payload_",8)==0) {
           skin->blank_format_size= Scanf_io_size(cpt+8,0);
           skin->blank_format_type= 4;
         } else if(strcmp(cpt,"cert_off")==0)
           skin->blank_format_no_certify= 1;
         else if(strcmp(cpt,"cert_on")==0)
           skin->blank_format_no_certify= 0;
         else
           goto unsupported_blank_option;
       }
       skin->preskin->demands_cdrskin_caps= 1;
     } else if(strncmp(cpt,"format_by_index_",16)==0) {
       sscanf(cpt+16, "%d", &idx);
       if(idx<0 || idx>255) { 
         fprintf(stderr,"cdrskin: SORRY : blank=%s provides unusable index\n",
                 cpt);
         return(0);
       }
       skin->do_blank= 1;
       skin->blank_format_type= 5|(2<<9)|(1<<15);
       skin->blank_format_index= idx;
       skin->blank_format_size= 0;
       skin->preskin->demands_cdrskin_caps= 1;

     } else if(strcmp(cpt,"deformat_sequential")==0) {
       skin->do_blank= 1;
       skin->blank_format_type= 2;
       skin->blank_fast= 0;
       skin->preskin->demands_cdrskin_caps= 1;
     } else if(strcmp(cpt,"deformat_sequential_quickest")==0) {
       skin->do_blank= 1;
       skin->blank_format_type= 2;
       skin->blank_fast= 1;
       skin->preskin->demands_cdrskin_caps= 1;
     } else if(strcmp(cpt,"as_needed")==0) {
       skin->do_blank= 1;
       skin->blank_format_type= 7;
     } else if(strcmp(cpt,"help")==0) {
       /* is handled in Cdrpreskin_setup() */;
 continue;
     } else { 
unsupported_blank_option:;
       fprintf(stderr,"cdrskin: FATAL : Blank option '%s' not supported yet\n",
                      cpt);
       return(0);
     }
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: blank mode : blank=%s\n",blank_mode));

   } else if(strcmp(argv[i],"--bragg_with_audio")==0) {
     /* OBSOLETE 0.2.3 : was handled in Cdrpreskin_setup() */;

   } else if(strncmp(argv[i], "-cd_start_tno=", 14) == 0) {
     value_pt= argv[i] + 14;
     goto set_cd_start_tno;
   } else if(strncmp(argv[i], "cd_start_tno=", 13) == 0) {
     value_pt= argv[i] + 13;
set_cd_start_tno:;
     cd_start_tno= -1;
     sscanf(value_pt, "%d", &cd_start_tno);
     if(cd_start_tno < 1 || cd_start_tno > 99) {
       fprintf(stderr,
         "cdrskin: FATAL : cd_start_tno= gives a number outside [1 ... 99]\n");
       return(0);
     }
     skin->cd_start_tno= cd_start_tno;

   } else if(strcmp(argv[i],"--cdtext_dummy")==0) {
     skin->cdtext_test= 2;

   } else if(strncmp(argv[i], "-cdtext_to_textfile=", 20) == 0) {
     value_pt= argv[i] + 20;
     goto set_cdtext_to_textfile;
   } else if(strncmp(argv[i], "cdtext_to_textfile=", 19) == 0) {
     value_pt= argv[i] + 19;
set_cdtext_to_textfile:;
     if(strlen(value_pt) >= sizeof(skin->cdtext_to_textfile_path)) {
       fprintf(stderr,
    "cdrskin: FATAL : cdtext_to_textfile=... too long. (max: %d, given: %d)\n",
    (int) sizeof(skin->cdtext_to_textfile_path)-1,(int) strlen(value_pt));
       return(0);
     }
     skin->do_cdtext_to_textfile= 1;
     strcpy(skin->cdtext_to_textfile_path, value_pt);

   } else if(strncmp(argv[i], "-cdtext_to_v07t=", 16) == 0) {
     value_pt= argv[i] + 16;
     goto set_cdtext_to_v07t;
   } else if(strncmp(argv[i], "cdtext_to_v07t=", 15) == 0) {
     value_pt= argv[i] + 15;
set_cdtext_to_v07t:;
     if(strlen(value_pt) >= sizeof(skin->cdtext_to_vt07_path)) {
       fprintf(stderr,
        "cdrskin: FATAL : cdtext_to_vt07=... too long. (max: %d, given: %d)\n",
        (int) sizeof(skin->cdtext_to_vt07_path)-1,(int) strlen(value_pt));
       return(0);
     }
     skin->do_cdtext_to_vt07= 1;
     strcpy(skin->cdtext_to_vt07_path, value_pt);

   } else if(strcmp(argv[i],"--cdtext_verbose")==0) {
     skin->cdtext_test= 1;

   } else if(strcmp(argpt,"-checkdrive")==0) {
     skin->do_checkdrive= 1;

   } else if(strcmp(argpt,"-copy")==0) {
     skin->track_modemods|= BURN_COPY;
     skin->track_modemods&= ~BURN_SCMS;

   } else if(strncmp(argpt, "-cuefile=", 9)==0) {
     value_pt= argpt + 9;
     goto set_cuefile;
   } else if(strncmp(argpt, "cuefile=", 8)==0) {
     value_pt= argpt + 8;
set_cuefile:;
     if(strlen(value_pt) >= sizeof(skin->cuefile)) {
       fprintf(stderr,
          "cdrskin: FATAL : cuefile=... too long. (max: %d, given: %d)\n",
          (int) sizeof(skin->cuefile) - 1, (int) strlen(value_pt));
       return(0);
     }
     strcpy(skin->cuefile, value_pt);
     skin->do_burn= 1;

   } else if(strcmp(argpt,"-data")==0) {
option_data:;
     /* All Subsequent Tracks Option */
     skin->cdxa_conversion= (skin->cdxa_conversion & ~0x7fffffff) | 0;
     skin->track_type= BURN_MODE1;
     skin->track_type_by_default= 0;

   } else if(strcmp(argv[i],"--demand_a_drive")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argv[i],"--devices")==0) {
     skin->do_devices= 1;

   } else if(strcmp(argv[i],"--device_links")==0) {
     skin->do_devices= 2;

#ifndef Cdrskin_extra_leaN

   } else if(strncmp(argv[i],"dev_translation=",16)==0) {
     if(argv[i][16]==0) {
       fprintf(stderr,
         "cdrskin: FATAL : dev_translation= : missing separator character\n");
       return(0);
     }
     ret= Cdradrtrn_add(skin->adr_trn,argv[i]+17,argv[i]+16,1);
     if(ret==-2)
       fprintf(stderr,
           "cdrskin: FATAL : address_translation= : cannot allocate memory\n");
     else if(ret==-1)
       fprintf(stderr,
             "cdrskin: FATAL : address_translation= : table full (%d items)\n",
               Cdradrtrn_leN);
     else if(ret==0)
       fprintf(stderr,
   "cdrskin: FATAL : address_translation= : no address separator '%c' found\n",
               argv[i][17]);
     if(ret<=0)
       return(0);

#endif /* ! Cdrskin_extra_leaN */


   } else if(strncmp(argpt,"-dev=",5)==0) {
     /* is handled in Cdrpreskin_setup() */;
   } else if(strncmp(argpt,"dev=",4)==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strncmp(argv[i],"direct_write_amount=",20)==0) {
     skin->direct_write_amount= Scanf_io_size(argv[i]+20,0);
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: amount for direct writing : %.f\n",
                  skin->direct_write_amount));
     if(skin->direct_write_amount>=0.0) {
       skin->do_direct_write= 1;
       printf("cdrskin: NOTE : Direct writing will only use first track source and no fifo.\n");
       skin->preskin->demands_cdrskin_caps= 1;
     } else
       skin->do_direct_write= 0;

   } else if(strcmp(argv[i],"--drive_abort_on_busy")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argv[i],"--drive_blocking")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argv[i],"--drive_not_exclusive")==0 ||
             strcmp(argv[i],"--drive_not_f_setlk")==0 ||
             strcmp(argv[i],"--drive_not_o_excl")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strncmp(argv[i],"drive_scsi_dev_family=",22)==0) {
     /* is handled in Cdrpreskin_setup() */;
     if(skin->verbosity>=Cdrskin_verbose_debuG &&
        skin->preskin->drive_scsi_dev_family!=0)
       ClN(fprintf(stderr,"cdrskin_debug: drive_scsi_dev_family= %d\n",
                        skin->preskin->drive_scsi_dev_family));

   } else if(strcmp(argv[i],"--drive_scsi_exclusive")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strncmp(argpt, "-driveropts=", 12)==0) {
     value_pt= argpt + 12;
     goto set_driveropts;
   } else if(strncmp(argpt, "driveropts=", 11)==0) {
     value_pt= argpt + 11;
set_driveropts:;
     if(strcmp(value_pt,"burnfree")==0 || strcmp(value_pt,"burnproof")==0) {
       skin->burnfree= 1;
       if(skin->verbosity>=Cdrskin_verbose_cmD)
         ClN(printf("cdrskin: burnfree : on\n"));
     } else if(strcmp(argpt+11,"noburnfree")==0 ||
               strcmp(argpt+11,"noburnproof")==0 ) {
       skin->burnfree= 0;
       if(skin->verbosity>=Cdrskin_verbose_cmD)
         ClN(printf("cdrskin: burnfree : off\n"));
     } else if(strcmp(argpt+11,"help")==0) {
       /* handled in Cdrpreskin_setup() */;
     } else 
       goto ignore_unknown;

   } else if(strcmp(argpt,"-dummy")==0) {
     skin->dummy_mode= 1;

   } else if(strncmp(argv[i], "-dvd_obs=", 9)==0) {
     value_pt= argv[i] + 9;
     goto dvd_obs;
   } else if(strncmp(argv[i], "dvd_obs=", 8)==0) {
     value_pt= argv[i] + 8;
dvd_obs:;
     if(strcmp(value_pt, "default") == 0)
       num= 0;
     else
       num = Scanf_io_size(value_pt,0);
     if(num != 0 && num != 32768 && num != 65536) {
       fprintf(stderr,
       "cdrskin: SORRY : Option dvd_obs= accepts only sizes 0, 32k, 64k\n");
     } else
       skin->dvd_obs= num;

   } else if(strcmp(argpt,"-eject")==0) {
     skin->do_eject= 1;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: eject after work : on\n"));

   } else if(strncmp(argv[i],"eject_device=",13)==0) {
     if(strlen(argv[i]+13)>=sizeof(skin->eject_device)) {
       fprintf(stderr,
          "cdrskin: FATAL : eject_device=... too long. (max: %d, given: %d)\n",
          (int) sizeof(skin->eject_device)-1,(int) strlen(argv[i]+13));
       return(0);
     }
     strcpy(skin->eject_device,argv[i]+13);
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: ignoring obsolete  eject_device=%s\n",
              skin->eject_device));

   } else if(strncmp(argv[i],"-extract_audio_to=", 18)==0) {
     value_pt= argpt + 18;
     goto extract_audio_to;
   } else if(strncmp(argpt, "extract_audio_to=", 17) == 0) {
     value_pt= argpt + 17;
extract_audio_to:;
     if(strlen(value_pt) >= sizeof(skin->extract_audio_dir)) {
       fprintf(stderr,
               "cdrskin: FAILURE : extract_audio_to=... is much too long.\n");
       return(0);
     }
     skin->do_extract_audio= 1;
     strcpy(skin->extract_audio_dir, value_pt);

   } else if(strncmp(argv[i],"-extract_tracks=", 16)==0) {
     value_pt= argpt + 16;
     goto extract_tracks;
   } else if(strncmp(argpt, "extract_tracks=", 15) == 0) {
     value_pt= argpt + 15;
extract_tracks:;
     value= 0.0;
     for(cpt= value_pt; ; cpt++) {
       if(*cpt >= '0' && *cpt <= '9') {
         value= value * 10 + *cpt - '0';
       } else {
         if(value >= 1.0 && value <= 99.0) {
           skin->extract_audio_tracks[(int) value]= 1;
           if(skin->verbosity >= Cdrskin_verbose_cmD)
             fprintf(stderr, "cdrskin: Will extract track number %.f\n",
                             value);
         } else {
           fprintf(stderr,
           "cdrskin: WARNING : extract_tracks= with unsuitable number: %.f\n",
                   value);
         }
         if(*cpt == 0)
     break;
         value= 0;
       }
     }

   } else if(strncmp(argv[i],"-extract_basename=", 18)==0) {
     value_pt= argpt + 18;
     goto extract_basename;
   } else if(strncmp(argpt, "extract_basename=", 17) == 0) {
     value_pt= argpt + 17;
extract_basename:;
     if(strchr(value_pt, '/') != NULL) {
       fprintf(stderr,
         "cdrskin: FAILURE : extract_basename=... may not contain '/'\n");
       return(0);
     }
     if(strlen(value_pt) > 248) {
       fprintf(stderr,
         "cdrskin: FAILURE : Oversized extract_basename=... (Max. 248)\n");
       return(0);
     }
     strcpy(skin->extract_basename, value_pt);

   } else if(strcmp(argv[i],"--extract_dap") == 0) {
     skin->extract_flags|= 8;

#ifndef Cdrskin_extra_leaN

   } else if(strcmp(argv[i],"--fifo_disable")==0) {
     skin->fifo_enabled= 0;
     skin->fifo_size= 0;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: option fs=... disabled\n"));

   } else if(strcmp(argv[i],"--fifo_start_empty")==0) { /* obsoleted */
     skin->fifo_start_at= 0;

   } else if(strncmp(argv[i],"fifo_start_at=",14)==0) {
     value= Scanf_io_size(argv[i]+14,0);
     if(value>1024.0*1024.0*1024.0)
       value= 1024.0*1024.0*1024.0;
     else if(value<0)
       value= 0;
     skin->fifo_start_at= value;

   } else if(strcmp(argv[i],"--fifo_per_track")==0) {
     skin->fifo_per_track= 1;

#endif /* ! Cdrskin_extra_leaN */

   } else if(strcmp(argv[i],"--fill_up_media")==0) {
     skin->fill_up_media= 1;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf(
               "cdrskin: will fill up last track to full free media space\n"));

   } else if(strcmp(argpt,"-force")==0) {
     skin->force_is_set= 1;

   } else if(strcmp(argpt,"-format")==0) {
     skin->do_blank= 1;
     skin->blank_format_type= 3|(1<<10);
     skin->blank_format_size= 0;
     skin->force_is_set= 1;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf(
       "cdrskin: will format DVD+RW by blank=format_overwrite_full -force\n"));

   } else if(strcmp(argv[i],"--four_channel")==0) {
     skin->track_modemods|= BURN_4CH;

#ifndef Cdrskin_extra_leaN

   } else if(strncmp(argpt, "-fs=", 4) == 0) {
     value_pt= argpt + 4;
     goto fs_equals;
   } else if(strncmp(argpt, "fs=", 3) == 0) {
     value_pt= argpt + 3;
fs_equals:;
     if(skin->fifo_enabled) {
       value= Scanf_io_size(value_pt,0);
       if(value<0.0 || value>1024.0*1024.0*1024.0) {
         fprintf(stderr,
                 "cdrskin: FATAL : fs=N expects a size between 0 and 1g\n");
         return(0);
       }
       skin->fifo_size= value;
       if(skin->verbosity>=Cdrskin_verbose_cmD)
         printf("cdrskin: fifo size : %d\n",skin->fifo_size);
     }

   } else if(strncmp(argv[i],"grab_drive_and_wait=",20)==0) {
     value_pt= argv[i]+20;
     grab_and_wait_value= Scanf_io_size(value_pt,0);
     skin->preskin->demands_cdrskin_caps= 1;

   } else if(strncmp(argpt, "-gracetime=", 11) == 0) {
     value_pt= argpt + 11;
     goto gracetime_equals;
   } else if(strncmp(argpt, "gracetime=", 10) == 0) {
     value_pt= argpt + 10;
gracetime_equals:;
     sscanf(value_pt,"%d",&(skin->gracetime));

   } else if(strncmp(argv[i],"--grow_overwriteable_iso",24)==0) {
     skin->grow_overwriteable_iso= 1;
     skin->use_data_image_size= 1;
     skin->preskin->demands_cdrskin_caps= 1;

#else /* ! Cdrskin_extra_leaN */

   } else if(
      strcmp(argv[i],"--fifo_disable")==0 ||
      strcmp(argv[i],"--fifo_start_empty")==0 ||
      strncmp(argv[i],"fifo_start_at=",14)==0 ||
      strcmp(argv[i],"--fifo_per_track")==0 ||
      strncmp(argv[i],"-fs=",4)==0 ||
      strncmp(argv[i],"fs=",3)==0 ||
      strncmp(argv[i],"-gracetime=",11)==0 ||
      strncmp(argv[i],"gracetime=",10)==0) {
     fprintf(stderr,
             "cdrskin: NOTE : lean version ignores option: '%s'\n",
             argv[i]);

#endif /* Cdrskin_extra_leaN */

   } else if(strcmp(argv[i],"--help")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argv[i],"-help")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argv[i],"--ignore_signals")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argpt,"-immed")==0) {
     skin->modesty_on_drive= 1;
     skin->min_buffer_percent= 75;
     skin->max_buffer_percent= 95;

   } else if(strncmp(argpt, "-index=", 7) == 0) {
     value_pt= argpt + 7;
     goto set_index;
   } else if(strncmp(argpt, "index=", 6) == 0) {
     value_pt= argpt + 6;
set_index:;
     if(skin->index_string != NULL)
       free(skin->index_string);
     skin->index_string= strdup(value_pt);
     if(skin->index_string == NULL) {
       fprintf(stderr, "cdrskin: FATAL : Out of memory\n");
       return(-1);
     }

   } else if(strncmp(argv[i], "input_sheet_v07t=", 17)==0) {
     if(skin->sheet_v07t_blocks >= 8) {
       fprintf(stderr,
            "cdrskin: SORRY : Too many input_sheet_v07t= options. (Max. 8)\n");
       return(0);
     }
     if(argv[i][17] == 0) {
       fprintf(stderr,
        "cdrskin: SORRY : Missing file path after option input_sheet_v07t=\n");
       return(0);
     }
     if(strlen(argv[i] + 17) > Cdrskin_adrleN) {
       fprintf(stderr,
       "cdrskin: SORRY : File path too long after option input_sheet_v07t=\n");
       return(0);
     }
     strcpy(skin->sheet_v07t_paths[skin->sheet_v07t_blocks], argv[i] + 17);
     skin->sheet_v07t_blocks++;
     skin->preskin->demands_cdrskin_caps= 1;

   } else if(strcmp(argpt,"-inq")==0) {
     skin->do_checkdrive= 2;

   } else if(strcmp(argpt,"-isosize")==0) {
     skin->use_data_image_size= 1;

   } else if(strncmp(argpt, "-isrc=", 6) == 0) {
     value_pt= argpt + 6;
     goto set_isrc;
   } else if(strncmp(argpt, "isrc=", 5) == 0) {
     value_pt= argpt + 5;
set_isrc:;
     if(strlen(value_pt) != 12) {
       fprintf(stderr,
          "cdrskin: SORRY : isrc=... is not exactly 12 characters long.\n");
       return(0);
     }
     memcpy(skin->next_isrc, value_pt, 13);

   } else if(strcmp(argv[i],"--list_formats")==0) {
     skin->do_list_formats= 1;
     skin->preskin->demands_cdrskin_caps= 1;

   } else if(strcmp(argv[i],"--list_ignored_options")==0) {
     /* is also handled in Cdrpreskin_setup() */;

     printf("cdrskin: List of all ignored options:\n");
     for(k=0;ignored_partial_options[k][0]!=0;k++)
       printf("%s\n",ignored_partial_options[k]);
     for(k=0;ignored_full_options[k][0]!=0;k++) 
       printf("%s\n",ignored_full_options[k]);
     printf("\n");

   } else if(strcmp(argv[i],"--list_speeds")==0) {
     skin->do_list_speeds= 1;
     skin->preskin->demands_cdrskin_caps= 1;

   } else if(strcmp(argv[i],"--list_features")==0) {
     skin->do_list_features= 1;
     skin->preskin->demands_cdrskin_caps= 1;

   } else if(strncmp(argv[i],"fallback_program=",17)==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argpt,"-load")==0) {
     skin->do_load= 1;

   } else if(strcmp(argpt,"-lock")==0) {
     skin->do_load= 2;

   } else if(strcmp(argv[i],"--long_toc")==0) {
     skin->do_atip= 3;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: will put out some -atip style lines plus -toc\n"));

   } else if(strncmp(argpt,"-mcn=", 5) == 0) {
     value_pt= argpt + 5;
     goto set_mcn;
   } else if(strncmp(argpt,"mcn=", 4) == 0) {
     value_pt= argpt + 4;
set_mcn:;
     if(strlen(value_pt) != 13) {
       fprintf(stderr,
          "cdrskin: SORRY : mcn=... is not exactly 13 characters long.\n");
       return(0);
     }
     memcpy(skin->mcn, value_pt, 14);

   } else if(strncmp(argpt, "-minbuf=", 8) == 0) {
     value_pt= argpt + 8;
     goto minbuf_equals;
   } else if(strncmp(argpt, "minbuf=", 7) == 0) {
     value_pt= argpt + 7;
minbuf_equals:;
     skin->modesty_on_drive= 1;
     sscanf(value_pt,"%lf",&value);
     if (value<25 || value>95) {
       fprintf(stderr,
               "cdrskin: FATAL : minbuf= value must be between 25 and 95\n");
       return(0);
     }
     skin->min_buffer_percent= value;
     skin->max_buffer_percent= 95;
     ClN(printf("cdrskin: minbuf=%d percent desired buffer fill\n",
                skin->min_buffer_percent));

   } else if(strcmp(argpt,"-minfo") == 0 ||
             strcmp(argpt,"-media-info") == 0) {
     skin->do_atip= 4;

   } else if(strcmp(argpt, "-mode2") == 0) {
     fprintf(stderr,
             "cdrskin: NOTE : defaulting option -mode2 to option -data\n");
     goto option_data;

   } else if(strncmp(argv[i],"modesty_on_drive=",17)==0) {
     value_pt= argv[i]+17;
     if(*value_pt == '0' || strncmp(value_pt, "off", 3) == 0) {
       skin->modesty_on_drive= 0;
       if(skin->verbosity>=Cdrskin_verbose_cmD)
         ClN(printf(
               "cdrskin: modesty_on_drive=0 : buffer waiting by os driver\n"));
     } else if(*value_pt=='1' || strncmp(value_pt, "on", 2) == 0) {
       skin->modesty_on_drive= 1;
       if(skin->verbosity>=Cdrskin_verbose_cmD)
         ClN(printf(
                 "cdrskin: modesty_on_drive=1 : buffer waiting by libburn\n"));
     } else if(*value_pt=='-' && argv[i][18]=='1') {
       skin->modesty_on_drive= -1;
       if(skin->verbosity>=Cdrskin_verbose_cmD)
         ClN(printf(
       "cdrskin: modesty_on_drive=-1 : buffer waiting as libburn defaults\n"));
     } else {
       fprintf(stderr,
               "cdrskin: FATAL : modesty_on_drive= must be -1, 0 or 1\n");
       return(0);
     }
     while(1) {
       value_pt= strchr(value_pt,':');
       if(value_pt==NULL)
     break;
       value_pt++;
       if(strncmp(value_pt,"min_percent=",12)==0) {
         sscanf(value_pt+12,"%lf",&value);
         if (value<25 || value>100) {
           fprintf(stderr,
                   "cdrskin: FATAL : modest_on_drive= min_percent value must be between 25 and 100\n");
           return(0);
         }
         skin->min_buffer_percent= value;
         if(skin->verbosity >= Cdrskin_verbose_cmD)
           ClN(printf(
                  "cdrskin: modesty_on_drive : %d percent min buffer fill\n",
                  skin->min_buffer_percent));
       } else if(strncmp(value_pt,"max_percent=",12)==0) {
         sscanf(value_pt+12,"%lf",&value);
         if (value<25 || value>100) {
           fprintf(stderr,
                   "cdrskin: FATAL : modest_on_drive= max_percent value must be between 25 and 100\n");
           return(0);
         }
         skin->max_buffer_percent= value;
         if(skin->verbosity >= Cdrskin_verbose_cmD)
           ClN(printf(
                  "cdrskin: modesty_on_drive : %d percent max buffer fill\n",
                  skin->max_buffer_percent));
       } else if(strncmp(value_pt, "min_usec=", 9) == 0) {
         sscanf(value_pt + 9, "%lf", &value);
         if(value < 0)
           value= 0;
         skin->min_buffer_usec= value;
         if(skin->verbosity >= Cdrskin_verbose_cmD)
           ClN(printf(
            "cdrskin: modesty_on_drive : %d microseconds minimum sleep time\n",
                  skin->min_buffer_usec));
       } else if(strncmp(value_pt,"max_usec=", 9)==0) {
         sscanf(value_pt + 9, "%lf", &value);
         if(value < 0)
           value= 0;
         skin->max_buffer_usec= value;
         if(skin->verbosity >= Cdrskin_verbose_cmD)
           ClN(printf(
            "cdrskin: modesty_on_drive : %d microseconds maximum sleep time\n",
                  skin->max_buffer_usec));
       } else if(strncmp(value_pt,"timeout_sec=", 12)==0) {
         sscanf(value_pt + 9, "%lf", &value);
         if(value < 0)
           value= 0;
         skin->buffer_timeout_sec= value;
         if(skin->verbosity >= Cdrskin_verbose_cmD)
           ClN(printf(
                  "cdrskin: modesty_on_drive : %d seconds fallback timeout\n",
                  skin->max_buffer_usec));
       } else {
         fprintf(stderr,
                "cdrskin: SORRY : modest_on_drive= unknown option code : %s\n",
                value_pt);
       }
     }
     skin->preskin->demands_cdrskin_caps= 1;

   } else if(strcmp(argpt,"-multi")==0) {
     skin->multi= 1;

   } else if(strncmp(argpt, "-msifile=", 9) == 0) {
     value_pt= argpt + 9;
     goto msifile_equals;
   } else if(strncmp(argpt, "msifile=", 8) == 0) {
     value_pt= argpt + 8;
msifile_equals:;
     if(strlen(value_pt)>=sizeof(skin->msifile)) {
       fprintf(stderr,
          "cdrskin: FATAL : msifile=... too long. (max: %d, given: %d)\n",
          (int) sizeof(skin->msifile)-1,(int) strlen(value_pt));
       return(0);
     }
     strcpy(skin->msifile, value_pt);
     skin->do_msinfo= 1;

   } else if(strcmp(argpt,"-msinfo")==0) {
     skin->do_msinfo= 1;

#ifdef Libburn_develop_quality_scaN

   } else if(strcmp(argv[i],"--nec_optiarc_qcheck")==0) {
     skin->do_qcheck= 1;

#endif /* Libburn_develop_quality_scaN */

   } else if(strcmp(argv[i],"--no_abort_handler")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argv[i],"--no_blank_appendable")==0) {
     skin->no_blank_appendable= 1;

   } else if(strcmp(argv[i],"--no_convert_fs_adr")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argv[i],"--no_load")==0) {
     skin->do_load= -1;

   } else if(strcmp(argv[i],"--no_rc")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argpt,"-nocopy")==0) {
     skin->track_modemods&= ~BURN_COPY;

   } else if(strcmp(argpt,"-nopad")==0) {
     skin->padding= 0.0;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: padding : off\n"));

   } else if(strcmp(argpt,"-nopreemp")==0) {
     skin->track_modemods&= ~BURN_PREEMPHASIS;

   } else if(strcmp(argv[i],"--obs_pad")==0) {
     skin->obs_pad= 1;

   } else if(strcmp(argv[i],"--old_pseudo_scsi_adr")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argv[i], "--pacifier_with_newline") == 0) {
     skin->pacifier_with_newline= 1;

   } else if(strcmp(argpt,"-pad")==0) {
     skin->padding= 15*2048;
     skin->set_by_padsize= 0;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: padding : %.f\n",skin->padding));

   } else if(strncmp(argpt, "-padsize=", 9) == 0) {
     value_pt= argpt + 9;
     goto set_padsize;
   } else if(strncmp(argpt, "padsize=", 8) == 0) {
     value_pt= argpt + 8;
set_padsize:;
     skin->padding= Scanf_io_size(value_pt, 0);
     skin->set_by_padsize= 1;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: padding : %.f\n",skin->padding));

   } else if(strcmp(argpt,"-preemp")==0) {
     skin->track_modemods|= BURN_PREEMPHASIS;

   } else if(strcmp(argv[i],"--prodvd_cli_compatible")==0) {
     skin->prodvd_cli_compatible= 1;

   } else if(strcmp(argpt,"-sao")==0 || strcmp(argpt,"-dao")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argpt,"-scanbus")==0) {
     skin->do_scanbus= 1;

   } else if(strcmp(argpt,"-scms")==0) {
     skin->track_modemods|= BURN_SCMS;

   } else if(strcmp(argv[i],"--single_track")==0) {
     skin->single_track= 1;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf(
"cdrskin: --single_track : will only accept last argument as track source\n"));
     skin->preskin->demands_cdrskin_caps= 1;

   } else if(strncmp(argv[i], "-sao_postgap=", 13) == 0) {
     value_pt= argv[i] + 13;
     goto set_sao_postgap;
   } else if(strncmp(argv[i], "sao_postgap=", 12) == 0) {
     value_pt= argv[i] + 12;
set_sao_postgap:;
     skin->sao_postgap= -1;
     if(strcmp(value_pt, "off") == 0)
       skin->sao_postgap = -1;
     else
       sscanf(value_pt, "%d", &(skin->sao_postgap));
     if(skin->sao_postgap < 0) {
       fprintf(stderr,
            "cdrskin: SORRY : sao_postgap must be \"off\" or a number >= 0\n");
       return(0);
     }

   } else if(strncmp(argv[i], "-sao_pregap=", 12) == 0) {
     value_pt= argv[i] + 12;
     goto set_sao_pregap;
   } else if(strncmp(argv[i], "sao_pregap=", 11) == 0) {
     value_pt= argv[i] + 11;
set_sao_pregap:;
     skin->sao_pregap= -1;
     if(strcmp(value_pt, "off") == 0)
       skin->sao_pregap = -1;
     else
       sscanf(value_pt, "%d", &(skin->sao_pregap));
     if(skin->sao_pregap < 0) {
       fprintf(stderr,
             "cdrskin: SORRY : sao_pregap must be \"off\" or a number >= 0\n");
       return(0);
     }

   } else if(strncmp(argpt, "-speed=", 7) == 0) {
     value_pt= argpt + 7;
     goto set_speed;
   } else if(strncmp(argpt, "speed=", 6) == 0) {
     value_pt= argpt + 6;
set_speed:;
     if(strcmp(value_pt,"any")==0)
       skin->x_speed= -1;
     else
       sscanf(value_pt,"%lf",&(skin->x_speed));
     if(skin->x_speed<1.0 && skin->x_speed!=0.0 && skin->x_speed!=-1) {
       fprintf(stderr,"cdrskin: FATAL : speed= must be -1, 0 or at least 1\n");
       return(0);
     }
     if(skin->x_speed<0)
       skin->preskin->demands_cdrskin_caps= 1;

     /* >>> cdrecord speed=0 -> minimum speed , libburn -> maximum speed */;

     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: speed : %f\n",skin->x_speed));

   } else if(strncmp(argv[i], "-stdio_sync=", 12)==0) {
     value_pt= argv[i] + 12;
     goto stdio_sync;
   } else if(strncmp(argv[i], "stdio_sync=", 11)==0) {
     value_pt= argv[i] + 11;
stdio_sync:;
     if(strcmp(value_pt, "default") == 0 || strcmp(value_pt, "on") == 0)
       num= 0;
     else if(strcmp(value_pt, "off") == 0)
       num= -1;
     else
       num = Scanf_io_size(value_pt,0);
     if(num > 0)
       num/= 2048;
     if(num != -1 && num != 0 && (num < 32 || num > 512 * 1024)) {
       fprintf(stderr,
 "cdrskin: SORRY : Option stdio_sync= accepts only sizes -1, 0, 32k ... 1g\n");
     } else
       skin->stdio_sync= num;

   } else if(strncmp(argv[i],"-stream_recording=",18)==0) {
     value_pt= argv[i]+18;
     goto set_stream_recording;
   } else if(strncmp(argv[i],"stream_recording=",17)==0) {
     value_pt= argv[i]+17;
set_stream_recording:;
     if(strcmp(value_pt, "on")==0)
       skin->stream_recording_is_set= 1;
     else if(value_pt[0] >= '0' && value_pt[0] <= '9') {
       num= Scanf_io_size(value_pt, 0);
       num/= 2048.0;
       if(num >= 16 && num <= 0x7FFFFFFF)
         skin->stream_recording_is_set= num;
       else
         skin->stream_recording_is_set= 0;
     } else
       skin->stream_recording_is_set= 0;
   } else if(strcmp(argpt,"-swab")==0) {
     skin->swap_audio_bytes= 0;

   } else if(strcmp(argpt,"-tao")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strncmp(argv[i],"tao_to_sao_tsize=",17)==0) {
     skin->tao_to_sao_tsize= Scanf_io_size(argv[i]+17,0);
     if(skin->tao_to_sao_tsize>Cdrskin_tracksize_maX)
       goto track_too_large;
     skin->preskin->demands_cdrskin_caps= 1;

#ifndef Cdrskin_extra_leaN
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       printf("cdrskin: size default for non-tao write modes: %.f\n",
              skin->tao_to_sao_tsize);
#endif /* ! Cdrskin_extra_leaN */

   } else if(strcmp(argv[i],"--tell_media_space")==0) {
     skin->tell_media_space= 1;
     skin->preskin->demands_cdrskin_caps= 1;

   } else if(strcmp(argpt, "-text") == 0) {
     skin->use_cdtext= 1;

   } else if(strncmp(argpt, "-textfile=", 10) == 0) {
     value_pt= argpt + 10;
     goto set_textfile;
   } else if(strncmp(argpt ,"textfile=", 9) == 0) {
     value_pt= argpt + 9;
set_textfile:;
     ret= Cdrskin_read_textfile(skin, value_pt, 0);
     if(ret <= 0)
       return(ret);

   } else if(strncmp(argpt, "-textfile_to_v07t=", 18) == 0) {
     value_pt= argpt + 18;
     goto textfile_to_v07t;
   } else if(strncmp(argpt ,"textfile_to_v07t=", 17) == 0) {
     value_pt= argpt + 17;
textfile_to_v07t:;
     ret= Cdrskin_read_textfile(skin, value_pt, 0);
     if(ret <= 0)
       return(ret);
     ret= Cdrskin_print_text_packs(skin, skin->text_packs,
                                   skin->num_text_packs, (1 << 4) | 2);
     if(ret <= 0)
       return(ret);
     if(i != 1 || argc != 2)
       fprintf(stderr, "cdrskin: WARNING : Program run ended by option textfile_to_v07t=. Other options may have been ignored.\n");
     return(2);

   } else if(strcmp(argpt,"-toc")==0) {
     skin->do_atip= 2;
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: will put out some -atip style lines plus -toc\n"));

   } else if(strncmp(argpt, "-tsize=", 7) == 0) {
     value_pt= argpt + 7;
     goto set_tsize;
   } else if(strncmp(argpt, "tsize=", 6) == 0) {
     value_pt= argpt + 6;
set_tsize:;
     skin->fixed_size= Scanf_io_size(value_pt,0);
     if(skin->fixed_size>Cdrskin_tracksize_maX) {
track_too_large:;
       fprintf(stderr,"cdrskin: FATAL : Track size too large\n");
       return(0);
     }
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: fixed track size : %.f\n",skin->fixed_size));
     if(skin->smallest_tsize<0 || skin->smallest_tsize>skin->fixed_size)
       skin->smallest_tsize= skin->fixed_size;

   } else if(strcmp(argv[i],"--two_channel")==0) {
     skin->track_modemods&= ~BURN_4CH;

   } else if(strncmp(argv[i], "use_immed_bit=", 14) == 0) {
     if(strcmp(argv[i] + 14, "on") == 0) {
       skin->use_immed= 1;
     } else if(strcmp(argv[i] + 14, "off") == 0) {
       skin->use_immed= -1;
     } else if(strcmp(argv[i] + 14, "default") == 0) {
       skin->use_immed= 0;
     } else {
       fprintf(stderr, "cdrskin: FATAL : use_immed_bits= must be \"on\", \"off\", or \"default\"\n");
       return(0);
     }

   } else if(strcmp(argv[i],"-V")==0 || strcmp(argpt, "-Verbose")==0) {
     /* is handled in Cdrpreskin_setup() */;
   } else if(strcmp(argv[i],"-v")==0 || strcmp(argpt,"-verbose")==0) {
     /* is handled in Cdrpreskin_setup() */;
   } else if(strcmp(argv[i],"-vv")==0 || strcmp(argv[i],"-vvv")==0 ||
             strcmp(argv[i],"-vvvv")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strcmp(argpt,"-version")==0) {
     /* is handled in Cdrpreskin_setup() and should really not get here */;

   } else if(strcmp(argpt,"-waiti")==0) {
     /* is handled in Cdrpreskin_setup() */;

   } else if(strncmp(argv[i],"write_start_address=",20)==0) {
     skin->write_start_address= Scanf_io_size(argv[i]+20,0);
     if(skin->verbosity>=Cdrskin_verbose_cmD)
       ClN(printf("cdrskin: write start address : %.f\n",
                  skin->write_start_address));
     skin->preskin->demands_cdrskin_caps= 1;

   } else if(strcmp(argpt, "-xa") == 0) {
     fprintf(stderr,"cdrskin: NOTE : defaulting option -xa to option -data\n");
     goto option_data;

   } else if(strcmp(argpt, "-xa1") == 0) {
     /* All Subsequent Tracks Option */
     skin->cdxa_conversion= (skin->cdxa_conversion & ~0x7fffffff) | 1;
     skin->track_type= BURN_MODE1;
     skin->track_type_by_default= 0;

   } else if(strcmp(argpt, "-xa2") == 0) {
     fprintf(stderr,
             "cdrskin: NOTE : defaulting option -xa2 to option -data\n");
     goto option_data;

   } else if(strcmp(argv[i], "--xa1-ignore") == 0) {
     skin->cdxa_conversion|= (1 << 31);

   } else if( i==argc-1 ||
             (skin->single_track==0 && strchr(argv[i],'=')==NULL 
               && !(argv[i][0]=='-' && argv[i][1]!=0) ) ||
             (skin->single_track==-1)) {
     if(strlen(argv[i])>=sizeof(skin->source_path)) {
       fprintf(stderr,
            "cdrskin: FATAL : Source address too long. (max: %d, given: %d)\n",
               (int) sizeof(skin->source_path)-1,(int) strlen(argv[i]));
       return(0);
     }
     strcpy(skin->source_path,argv[i]);
     if(strcmp(skin->source_path,"-")==0) {
       if(skin->stdin_source_used) {
         fprintf(stderr,
    "cdrskin: FATAL : \"-\" (stdin) can be used as track source only once.\n");
         return(0);
       }
       skin->stdin_source_used= 1;
     } else if(argv[i][0]=='#' && (argv[i][1]>='0' && argv[i][1]<='9')) {
       if(skin->preskin->allow_fd_source==0) { 
         fprintf(stderr,
              "cdrskin: SORRY : '%s' is a reserved source path with cdrskin\n",
              argv[i]);
         fprintf(stderr,
      "cdrskin: SORRY : which would use an open file descriptor as source.\n");
         fprintf(stderr,
            "cdrskin: SORRY : Its usage is dangerous and disabled for now.\n");
         return(0);
       }
     } else {
       if(stat(skin->source_path,&stbuf)!=-1) {
         if((stbuf.st_mode&S_IFMT)==S_IFREG)
           ;
         else if((stbuf.st_mode&S_IFMT)==S_IFDIR) {
           fprintf(stderr,
                   "cdrskin: FATAL : Source address is a directory: '%s'\n",
                   skin->source_path);
           return(0);
         }
       }
     }

     if(skin->track_counter>=Cdrskin_track_maX) {
       fprintf(stderr,"cdrskin: FATAL : Too many tracks given. (max %d)\n",
               Cdrskin_track_maX);
       return(0);
     }
     ret= Cdrtrack_new(&(skin->tracklist[skin->track_counter]),skin,
                       skin->track_counter,
                       (strcmp(skin->source_path,"-")==0)<<1);
     if(ret<=0) {
       fprintf(stderr,
               "cdrskin: FATAL : Creation of track control object failed.\n");
       return(ret);
     }
     if(skin->next_isrc[0])
       memcpy(skin->tracklist[skin->track_counter]->isrc, skin->next_isrc, 13);
     skin->tracklist[skin->track_counter]->index_string= skin->index_string;
     skin->tracklist[skin->track_counter]->sao_pregap= skin->sao_pregap;
     skin->tracklist[skin->track_counter]->sao_postgap= skin->sao_postgap;
     skin->index_string= NULL;
     skin->sao_postgap= skin->sao_pregap= -1;
     skin->track_counter++;
     skin->use_data_image_size= 0;
     if(skin->verbosity>=Cdrskin_verbose_cmD) {
       if(strcmp(skin->source_path,"-")==0)
         printf("cdrskin: track %d data source : '-'  (i.e. standard input)\n",
                skin->track_counter);
       else
         printf("cdrskin: track %d data source : '%s'\n",
                skin->track_counter,skin->source_path);
     }
     /* reset track options */
     if(skin->set_by_padsize)
       skin->padding= 0; /* cdrecord-ProDVD-2.01b31 resets to 30k
                            the man page says padsize= is reset to 0
                            Joerg Schilling will change in 2.01.01 to 0 */
     skin->fixed_size= 0;
     skin->next_isrc[0]= 0;
   } else {
ignore_unknown:;
     if(skin->preskin->fallback_program[0])
       fprintf(stderr,"cdrskin: NOTE : Unknown option : '%s'\n",argv[i]);
     else
       fprintf(stderr,"cdrskin: NOTE : ignoring unknown option : '%s'\n",
               argv[i]);
     skin->preskin->demands_cdrecord_caps= 1;
   }
 }

 if(flag&1) /* no finalizing yet */
   return(1);

 if(skin->preskin->fallback_program[0] &&
    skin->preskin->demands_cdrecord_caps>0 &&
    skin->preskin->demands_cdrskin_caps<=0) {
   fprintf(stderr,"cdrskin: NOTE : Unsupported options found.\n");	
   fprintf(stderr,
           "cdrskin: NOTE : Will delegate job to fallback program '%s'.\n",
           skin->preskin->fallback_program);
   return(0);
 }

#ifndef Cdrskin_extra_leaN
 if(skin->verbosity>=Cdrskin_verbose_cmD) {
   if(skin->preskin->abort_handler==1)
     printf("cdrskin: installed abort handler.\n");
   else if(skin->preskin->abort_handler==2) 
     printf("cdrskin: will try to ignore any signals.\n");
   else if(skin->preskin->abort_handler==3)
     printf("cdrskin: installed hard abort handler.\n");
   else if(skin->preskin->abort_handler==4)
     printf("cdrskin: installed soft abort handler.\n");
   else if(skin->preskin->abort_handler==-1)
     printf("cdrskin: will install abort handler in eventual burn loop.\n");
 }
#endif /* ! Cdrskin_extra_leaN */

 if(strlen(skin->preskin->raw_device_adr)>0 ||
    strlen(skin->preskin->device_adr)>0) {
   if(strlen(skin->preskin->device_adr)>0)
     cpt= skin->preskin->device_adr;
   else
     cpt= skin->preskin->raw_device_adr;
   if(strcmp(cpt,"ATA")!=0 && strcmp(cpt,"ATAPI")!=0 && strcmp(cpt,"SCSI")!=0){
     ret= Cdrskin_dev_to_driveno(skin,cpt,&(skin->driveno),0);
     if(ret<=0)
       return(ret);
     if(skin->verbosity>=Cdrskin_verbose_cmD) {
       ret= burn_drive_get_adr(&(skin->drives[skin->driveno]), adr);
       if(ret<=0)
         adr[0]= 0;
       printf("cdrskin: active drive number : %d  '%s'\n",
              skin->driveno,adr);
     }
   }
 }
 if(grab_and_wait_value>0) {
   Cdrskin_grab_drive(skin,16);
   for(k= 0; k<grab_and_wait_value; k++) {
     fprintf(stderr, "%scdrskin: holding drive grabbed since %d seconds%s",
             skin->pacifier_with_newline ? "" : "\r", k,
             skin->pacifier_with_newline ? "\n" : "                 ");
     usleep(1000000);
   }
   fprintf(stderr,
        "%scdrskin: held drive grabbed for %d seconds                      \n",
        skin->pacifier_with_newline ? "" : "\r", k);
   Cdrskin_release_drive(skin,0);
 }
     
 if(skin->track_counter>0) {
   skin->do_burn= 1;

#ifndef Cdrskin_no_cdrfifO
   if(!skin->do_direct_write) {
     ret= Cdrskin_attach_fifo(skin,0);
     if(ret<=0)
       return(ret);
   }
#endif /* ! Cdrskin_no_cdrfifO */

 }
 return(1);
}


/** Initialize libburn, create a CdrskiN program run control object,
    set eventual device whitelist, and obtain the list of available drives.
    @param o Returns the CdrskiN object created
    @param lib_initialized Returns whether libburn was initialized here
    @param exit_value Returns after error the proposal for an exit value
    @param flag bit0= do not scan for devices
    @return <=0 error, 1 success
*/
int Cdrskin_create(struct CdrskiN **o, struct CdrpreskiN **preskin,
                   int *exit_value, int flag)
{
 int ret, stdio_drive= 0, mem;
 struct CdrskiN *skin;
 char reason[4096];

 *o= NULL;
 *exit_value= 0;

 if(strlen((*preskin)->device_adr)>0) {       /* disable scan for all others */
   ClN(printf(
       "cdrskin: NOTE : greying out all drives besides given dev='%s'\n",
       (*preskin)->device_adr));
   burn_drive_add_whitelist((*preskin)->device_adr);
   if(strncmp((*preskin)->device_adr, "stdio:", 6)==0) {
     ret= Cdrpreskin__allows_emulated_drives((*preskin)->device_adr+6,reason,0);
     if((*preskin)->allow_emulated_drives && ret>0) {
       stdio_drive= 1;
       (*preskin)->demands_cdrskin_caps= 1;
     } else if((*preskin)->allow_emulated_drives) {
       fprintf(stderr,"cdrskin: SORRY : dev=stdio:... rejected despite --allow_emulated_drives\n");
       fprintf(stderr,"cdrskin: SORRY : Reason: %s.\n", reason);
     } else {
       fprintf(stderr,"cdrskin: SORRY : dev=stdio:... works only with option --allow_emulated_drives\n");
       if(ret<=0) {
         fprintf(stderr,"cdrskin: SORRY : but: %s.\n", reason);
         fprintf(stderr,
                "cdrskin: SORRY : So this option would not help anyway.\n");
       }
     }
     if(!stdio_drive) {
       Cdrpreskin_consider_normal_user(0);
       {*exit_value= 2; goto ex;}
     }
   }
 }

 ret= Cdrskin_new(&skin,*preskin,1);
 if(ret<=0) {
   fprintf(stderr,"cdrskin: FATAL : Creation of control object failed\n");
   {*exit_value= 2; goto ex;}
 }
 *preskin= NULL; /* the preskin object now is under management of skin */
 *o= skin;
 if(skin->preskin->abort_handler==1 || skin->preskin->abort_handler==3 || 
    skin->preskin->abort_handler==4)
   Cleanup_set_handlers(Cleanup_handler_handlE, Cleanup_handler_funC,
                        Cleanup_handler_flaG);
 else if(skin->preskin->abort_handler==2)
   Cleanup_set_handlers(Cleanup_handler_handlE, Cleanup_handler_funC,
                        2 | 8);

 if(!(flag & 1))
   printf("cdrskin: scanning for devices ...\n");
 fflush(stdout);

 if(skin->preskin->verbosity<Cdrskin_verbose_debuG)
   burn_msgs_set_severities("NEVER", "NOTE", "cdrskin: ");

 /* In cdrskin there is not much sense in queueing library messages.
    It is done here only for testing it from time to time */
 Cdrpreskin_queue_msgs(skin->preskin,1);

 if(stdio_drive) {
   mem= skin->drive_is_busy;
   skin->drive_is_busy= 1;
   ret= burn_drive_scan_and_grab(&(skin->drives),skin->preskin->device_adr,0);
   skin->drive_is_busy= mem;
   if(Cdrskin__is_aborting(0)) {
     fprintf(stderr,"cdrskin: ABORT : Startup aborted\n");
     Cdrskin_abort(skin, 0); /* Never comes back */
   }
   if(ret <= 0) {
     fprintf(stderr,"cdrskin: FATAL : Failed to grab emulated stdio-drive\n");
     {*exit_value= 2; goto ex;}
   }
   skin->n_drives= 1;
   skin->driveno= 0;
   burn_drive_release(skin->drives[0].drive, 0);
 } else if(flag & 1){
   skin->n_drives= 0;
   skin->driveno= 0;
 } else {
   while (!burn_drive_scan(&(skin->drives), &(skin->n_drives))) {
     usleep(20000);
     /* >>> ??? set a timeout ? */
   }
   if(skin->n_drives <= 0)
     skin->driveno= -1;
 }

 burn_msgs_set_severities(skin->preskin->queue_severity,
                          skin->preskin->print_severity, "cdrskin: ");

 /* This prints the eventual queued messages */
 Cdrpreskin_queue_msgs(skin->preskin,0);

 if(!(flag & 1))
   printf("cdrskin: ... scanning for devices done\n");
 fflush(stdout);
ex:;
 return((*exit_value)==0);
}


/** Perform the activities which were ordered by setup
    @param skin Knows what to do
    @param exit_value Returns the proposal for an exit value
    @param flag Unused yet
    @return <=0 error, 1 success
*/
int Cdrskin_run(struct CdrskiN *skin, int *exit_value, int flag)
{
 int ret;

 *exit_value= 0;

 if(skin->preskin->allow_setuid==0 && getuid()!=geteuid()) {
   fprintf(stderr,"\n");
   fprintf(stderr,"cdrskin: WARNING : THIS PROGRAM WAS TREATED WITH chmod u+s WHICH IS INSECURE !\n");
   fprintf(stderr,
    "cdrskin: HINT : Consider to allow rw-access to the writer device and\n");
   fprintf(stderr,
    "cdrskin: HINT : to run cdrskin under your normal user identity.\n");
   fprintf(stderr,
    "cdrskin: HINT : Option  --allow_setuid  disables this safety check.\n");
   fprintf(stderr,"\n");
 }

 if(skin->do_devices) {
   if(skin->n_drives<=0 && skin->preskin->scan_demands_drive)
     {*exit_value= 4; goto no_drive;}
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_scanbus(skin, 1 | (2 * (skin->do_devices == 2)));
   if(ret<=0) {
     fprintf(stderr,"cdrskin: FATAL : --devices failed.\n");
     {*exit_value= 4; goto ex;}
   }
 }

 if(skin->do_scanbus) {
   if(skin->n_drives<=0 && skin->preskin->scan_demands_drive)
     {*exit_value= 5; goto no_drive;}
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_scanbus(skin,0);
   if(ret<=0)
     fprintf(stderr,"cdrskin: FATAL : -scanbus failed.\n");
   {*exit_value= 5*(ret<=0); goto ex;}
 }
 if(skin->do_load > 0) {
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_grab_drive(skin,8);
   if(ret>0) {
     if(skin->do_load==2 && !skin->do_eject) {
       printf(
   "cdrskin: NOTE : Option -lock orders program to exit with locked tray.\n");
       printf(
 "cdrskin: HINT : Run cdrskin with option -eject to unlock the drive tray.\n");
     } else if(!skin->do_eject)
       printf(
  "cdrskin: NOTE : option -load orders program to exit after loading tray.\n");
     Cdrskin_release_drive(skin,(skin->do_load==2)<<1);
   }
   {*exit_value= 14*(ret<=0); goto ex;}
 }
 if(skin->do_checkdrive) {
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_checkdrive(skin,"",(skin->do_checkdrive==2)<<1);
   {*exit_value= 6*(ret<=0); goto ex;}
 }
 if(skin->do_msinfo) {
   if(skin->n_drives<=0)
     {*exit_value= 12; goto no_drive;}
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_msinfo(skin,0);
   if(ret<=0)
     {*exit_value= 12; goto ex;}
 }
 if(skin->do_atip) {
   if(skin->n_drives<=0)
     {*exit_value= 7; goto no_drive;}
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_atip(skin, skin->do_atip == 4 ? 4 :
                                (skin->do_atip>1) | (2 * (skin->do_atip > 2)));
   if(ret<=0)
     {*exit_value= 7; goto ex;}
 }
 if(skin->do_cdtext_to_textfile) {
   ret= Cdrskin_cdtext_to_file(skin, skin->cdtext_to_textfile_path, 15);
   if(ret<=0)
     {*exit_value= 18; goto ex;}
 }
 if(skin->do_cdtext_to_vt07) {
   ret= Cdrskin_cdtext_to_file(skin, skin->cdtext_to_vt07_path, 1);
   if(ret<=0)
     {*exit_value= 19; goto ex;}
 }
 if(skin->do_extract_audio) {
   ret= Cdrskin_extract_audio_to_dir(skin, skin->extract_audio_dir,
                                     skin->extract_basename,
                                     skin->extract_audio_tracks,
                                     skin->extract_flags & 8);
   if(ret<=0)
     {*exit_value= 19; goto ex;}
 }
 if(skin->do_list_speeds) {
   if(skin->n_drives<=0)
     {*exit_value= 17; goto no_drive;}
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_list_speeds(skin, 0);
   if(ret<=0)
     {*exit_value= 17; goto ex;}
 }
 if(skin->do_list_formats) {
   if(skin->n_drives<=0)
     {*exit_value= 16; goto no_drive;}
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_list_formats(skin, 0);
   if(ret<=0)
     {*exit_value= 16; goto ex;}
 }
 if(skin->do_list_features) {
   if(skin->n_drives<=0)
     {*exit_value= 20; goto no_drive;}
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_list_features(skin, 0);
   if(ret<=0)
     {*exit_value= 20; goto ex;}
 }
 if(skin->do_blank) {
   if(skin->n_drives<=0)
     {*exit_value= 8; goto no_drive;}
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_blank(skin,0);
   if(ret<=0)
     {*exit_value= 8; goto ex;}
 }
 if(skin->do_direct_write) {
   skin->do_burn= 0;
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_direct_write(skin,0);
   if(ret<=0)
     {*exit_value= 13; goto ex;}
 }
 if(skin->do_burn || skin->tell_media_space) {
   if(skin->n_drives<=0)
     {*exit_value= 10; goto no_drive;}
   if(Cdrskin__is_aborting(0))
     goto ex;
   ret= Cdrskin_burn(skin,0);
   if(ret<=0)
     {*exit_value= 10; goto ex;}
 }

#ifdef Libburn_develop_quality_scaN

 if(skin->do_qcheck) {
   ret= Cdrskin_qcheck(skin, 0);
   if(ret<=0)
     {*exit_value= 15; goto ex;}
 }

#endif /* Libburn_develop_quality_scaN */

ex:;
 if(Cdrskin__is_aborting(0))
   Cdrskin_abort(skin, 0); /* Never comes back */
 return((*exit_value)==0);
no_drive:;
 fprintf(stderr,"cdrskin: FATAL : This run would need an accessible drive\n");
 goto ex;
}


int main(int argc, char **argv)
{
 int ret,exit_value= 0,lib_initialized= 0,i,result_fd= -1, do_not_scan;
 struct CdrpreskiN *preskin= NULL, *h_preskin= NULL;
 struct CdrskiN *skin= NULL;
 char *lean_id= "";
#ifdef Cdrskin_extra_leaN
 lean_id= ".lean";
#endif

 /* For -msinfo: Redirect normal stdout to stderr */
 for(i=1; i<argc; i++)
   if(strcmp(argv[i],"-msinfo")==0 ||
      strcmp(argv[i],"--tell_media_space")==0 ||
      strcmp(argv[i],"dev=stdio:/dev/fd/1")==0 ||
      strcmp(argv[i],"-dev=stdio:/dev/fd/1")==0
)
 break;
 if(i<argc) {
   result_fd= dup(1);
   close(1);
   dup2(2,1);
 }
 
 printf("cdrskin %s%s : limited cdrecord compatibility wrapper for libburn\n",
        Cdrskin_prog_versioN,lean_id);
 fflush(stdout);

 ret= Cdrpreskin_new(&preskin,0);
 if(ret<=0) {
   fprintf(stderr,"cdrskin: FATAL : Creation of control object failed\n");
   {exit_value= 2; goto ex;}
 }
 Cdrpreskin_set_result_fd(preskin,result_fd,0);

 /* <<< A60925: i would prefer to do this later, after it is clear that no
       -version or -help cause idle end. But address conversion and its debug
       messaging need libburn running */
 ret= Cdrpreskin_initialize_lib(preskin,0);
 if(ret<=0) {
   fprintf(stderr,"cdrskin: FATAL : Initialization of burn library failed\n");
   {exit_value= 2; goto ex;}
 }
 lib_initialized= 1;

 ret= Cdrpreskin_setup(preskin,argc,argv,0);
 if(ret<=0)
   {exit_value= 11; goto ex;}
 if(ret==2)
   {exit_value= 0; goto ex;}
 do_not_scan= preskin->do_not_scan; /* preskin will vanish in Cdrskin_create */
 ret= Cdrskin_create(&skin,&preskin,&exit_value, preskin->do_not_scan);
 if(ret<=0)
   {exit_value= 2; goto ex;}
 if(skin->n_drives<=0 && !do_not_scan) {
   fprintf(stderr,"cdrskin: NOTE : No usable drive detected.\n");
   if(getuid()!=0) {
     fprintf(stderr,
      "cdrskin: HINT : Run this program as superuser with option --devices\n");
     fprintf(stderr,
      "cdrskin: HINT : Allow rw-access to the dev='...' file of the burner.\n");
     fprintf(stderr,
      "cdrskin: HINT : Busy drives are invisible. (Busy = open O_EXCL)\n");
   }
 }

 ret= Cdrskin_setup(skin,argc,argv,0);
 if(ret<=0)
   {exit_value= 3; goto ex;}
 if(skin->verbosity>=Cdrskin_verbose_cmD)
   ClN(printf("cdrskin: called as :  %s\n",argv[0]));

 if(skin->verbosity>=Cdrskin_verbose_debuG) {
#ifdef Cdrskin_new_api_tesT
   ClN(fprintf(stderr,"cdrskin_debug: Compiled with option -experimental\n"));
#endif
 }

 if(!Cdrskin__is_aborting(0))
   Cdrskin_run(skin,&exit_value,0);

ex:;
 if(Cdrskin__is_aborting(0))
   Cdrskin_abort(skin, 0); /* Never comes back */

 if(preskin!=NULL)
   h_preskin= preskin;
 else if(skin!=NULL)
   h_preskin= skin->preskin;
 if(h_preskin!=NULL) {
   if(skin!=NULL)
     if(skin->verbosity>=Cdrskin_verbose_debuG)
       ClN(fprintf(stderr,
       "cdrskin_debug: demands_cdrecord_caps= %d , demands_cdrskin_caps= %d\n",
       h_preskin->demands_cdrecord_caps, h_preskin->demands_cdrskin_caps));

   if(exit_value && h_preskin->demands_cdrecord_caps>0 &&
      h_preskin->demands_cdrskin_caps<=0) {              /* prepare fallback */
     /* detach preskin from managers which would destroy it */
     preskin= NULL;
     if(skin!=NULL)
       skin->preskin= NULL;
   } else
     h_preskin= NULL;                                    /* prevent fallback */
 }
 if(skin!=NULL) {
   Cleanup_set_handlers(NULL,NULL,1);
   if(skin->preskin!=NULL)
     Cdrskin_eject(skin,0);
   Cdrskin_destroy(&skin,0);
 }
 Cdrpreskin_destroy(&preskin,0);
 if(lib_initialized)
   burn_finish();
 if(h_preskin!=NULL)
   Cdrpreskin_fallback(h_preskin,argc,argv,0); /* never come back */
 exit(exit_value);
}
