You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8830 lines
277 KiB

cdrskin.c , Copyright 2006-2011 Thomas Schmitt <>
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 :
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/ 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" resp.
"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 are willfully
tangled at toplevel to form an irrevocable commitment to true open source
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 as a whole.
cdrskin is originally inspired by libburn-0.2/test/burniso.c :
(c) Derek Foreman <> and Ben Jansens <>
Compilation within cdrskin-* :
cd cdrskin
cc -g -I.. -DCdrskin_build_timestamP='...' \
-o cdrskin cdrskin.c cdrfifo.c cleanup.c \
-L../libburn/.libs -lburn -lpthread
cd ..
cc -g -I. -DCdrskin_build_timestamP='...' \
-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 \
/** The official program version */
#ifndef Cdrskin_prog_versioN
#define Cdrskin_prog_versioN "1.1.7"
/** The official libburn interface revision to use.
(May get changed further below)
#ifndef Cdrskin_libburn_majoR
#define Cdrskin_libburn_majoR 1
#ifndef Cdrskin_libburn_minoR
#define Cdrskin_libburn_minoR 1
#ifndef Cdrskin_libburn_micrO
#define Cdrskin_libburn_micrO 7
/** The source code release timestamp */
#include "cdrskin_timestamp.h"
#ifndef Cdrskin_timestamP
#define Cdrskin_timestamP "-none-given-"
/** The binary build timestamp is to be set externally by the compiler */
#ifndef Cdrskin_build_timestamP
#define Cdrskin_build_timestamP "-none-given-"
#ifdef Cdrskin_libburn_versioN
#undef Cdrskin_libburn_versioN
/** use this to accomodate to the CVS version as of Feb 20, 2006
#define Cdrskin_libburn_cvs_A60220_tS 1
#ifdef Cdrskin_libburn_cvs_A60220_tS
#define Cdrskin_libburn_versioN "0.2.tsA60220"
#define Cdrskin_libburn_no_burn_preset_device_opeN 1
#ifndef Cdrskin_oldfashioned_api_usE
#define Cdrskin_oldfashioned_api_usE 1
#endif /* Cdrskin_libburn_cvs_A60220_tS */
#ifdef Cdrskin_libburn_1_1_6
#define Cdrskin_libburn_versioN "1.1.6"
#define Cdrskin_libburn_from_pykix_svN 1
#ifdef Cdrskin_libburn_1_1_7
#define Cdrskin_libburn_versioN "1.1.7"
#define Cdrskin_libburn_from_pykix_svN 1
#ifndef Cdrskin_libburn_versioN
#define Cdrskin_libburn_1_1_6
#define Cdrskin_libburn_versioN "1.1.6"
#define Cdrskin_libburn_from_pykix_svN 1
#ifdef Cdrskin_libburn_1_1_6
#undef Cdrskin_libburn_majoR
#undef Cdrskin_libburn_minoR
#undef Cdrskin_libburn_micrO
#define Cdrskin_libburn_majoR 1
#define Cdrskin_libburn_minoR 1
#define Cdrskin_libburn_micrO 6
#ifdef Cdrskin_libburn_1_1_7
#undef Cdrskin_libburn_majoR
#undef Cdrskin_libburn_minoR
#undef Cdrskin_libburn_micrO
#define Cdrskin_libburn_majoR 1
#define Cdrskin_libburn_minoR 1
#define Cdrskin_libburn_micrO 7
#ifdef Cdrskin_libburn_from_pykix_svN
#ifndef Cdrskin_oldfashioned_api_usE
/* 0.2.2 */
#define Cdrskin_libburn_does_ejecT 1
#define Cdrskin_libburn_has_drive_get_adR 1
#define Cdrskin_progress_track_does_worK 1
#define Cdrskin_is_erasable_on_load_does_worK 1
#define Cdrskin_grab_abort_does_worK 1
/* 0.2.4 */
#define Cdrskin_allow_libburn_taO 1
#define Cdrskin_libburn_has_is_enumerablE 1
#define Cdrskin_libburn_has_convert_fs_adR 1
#define Cdrskin_libburn_has_convert_scsi_adR 1
#define Cdrskin_libburn_has_burn_msgS 1
#define Cdrskin_libburn_has_burn_aborT 1
#define Cdrskin_libburn_has_cleanup_handleR 1
#define Cdrskin_libburn_has_audioxtR 1
#define Cdrskin_libburn_has_get_start_end_lbA 1
#define Cdrskin_libburn_has_burn_disc_unsuitablE 1
#define Cdrskin_libburn_has_read_atiP 1
#define Cdrskin_libburn_has_buffer_progresS 1
/* 0.2.6 */
#define Cdrskin_libburn_has_pretend_fulL 1
#define Cdrskin_libburn_has_multI 1
#define Cdrskin_libburn_has_buffer_min_filL 1
/* 0.3.0 */
#define Cdrskin_atip_speed_is_oK 1
#define Cdrskin_libburn_has_get_profilE 1
#define Cdrskin_libburn_has_set_start_bytE 1
#define Cdrskin_libburn_has_wrote_welL 1
/* 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 */
#endif /* ! Cdrskin_oldfashioned_api_usE */
#endif /* Cdrskin_libburn_from_pykix_svN */
/* These macros activate cdrskin workarounds for deficiencies resp.
problematic features of libburn which hopefully will change in
future. */
/** Work around the fact that neither /dev/sg0 (kernel 2.4 + ide-scsi) nor
/dev/hdc (kernel 2.6) get ejected by */
#ifndef Cdrskin_libburn_does_ejecT
#define Cdrskin_burn_drive_eject_brokeN 1
/** Work around the fact that after loading media speed report is wrong */
#ifndef Cdrskin_atip_speed_is_oK
#define Cdrskin_atip_speed_brokeN 1
/** Work around the fact that burn_drive_get_status() always reports to do
track 0 in */
#ifndef Cdrskin_progress_track_does_worK
#define Cdrskin_progress_track_brokeN 1
/** Work around the fact that a drive interrupted at burn_drive_grab() never
leaves status BURN_DRIVE_GRABBING in */
#ifndef Cdrskin_grab_abort_does_worK
#define Cdrskin_grab_abort_brokeN 1
/** Work around the fact that a freshly loaded tray with media reports
arbitrary media erasability in */
#ifndef Cdrskin_is_erasable_on_load_does_worK
#define Cdrskin_is_erasable_on_load_is_brokeN 1
/** reports of big trouble without
padding any track to a full sector
#define Cdrskin_all_tracks_with_sector_paD 1
/** 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
#define ClN(x) x
#ifdef Cdrskin_use_libburn_fifO
# define Cdrskin_no_cdrfifO 1
/** 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"
#ifdef Cdrskin_libburn_has_cleanup_handleR
#define Cleanup_set_handlers burn_set_signal_handling
#define Cleanup_app_handler_T burn_abort_handler_t
#include "cleanup.h"
#ifdef Cdrskin_use_libburn_cleanuP
#undef Cdrskin_use_libburn_cleanuP
# 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
#ifdef Cdrskin_use_libburn_cleanuP
#define Cleanup_handler_funC NULL
#define Cleanup_handler_handlE "cdrskin: "
#define Cleanup_handler_flaG 48
#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 */
#ifndef Cdrskin_oldfashioned_api_usE
#define Cdrskin_adrleN BURN_DRIVE_ADR_LEN
#define Cdrskin_adrleN 80
/** 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++]= '\'';
/* 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;
/** 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;
ret= -1.0;
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 a double representing seconds and microseconds since 1 Jan 1970 */
double Sfile_microtime(int flag)
struct timeval tv;
struct timezone tz;
return((double) (tv.tv_sec+1.0e-6*tv.tv_usec));
#ifndef Cdrskin_extra_leaN
/** 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;
/** Destroy a synthetic argument array */
int Sfile_destroy_argv(int *argc, char ***argv, int flag)
int i;
if(*argc>0 && *argv!=NULL){
free((char *) *argv);
*argc= 0;
*argv= NULL;
/** 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];
free((char *) *argidx);
free((char *) *arglno);
*argidx= *arglno= NULL;
for(pass=0;pass<2;pass++) {
argcount= 1;
maxl= strlen(progname)+1;
else {
(*argv)[0]= (char *) calloc(1, strlen(progname)+1);
{ret= -1; goto ex;}
} else {
argcount= 0;
maxl= 1;
for(i=0; i<filename_count;i++) {
fp= fopen(filenames[i],"rb");
if(fp==NULL) {
{ret= 0; goto ex;}
line_no= 0;
while(Sfile_fgets(buf,sizeof(buf)-1,fp)!=NULL) {
l= strlen(buf);
if(l==0 || buf[0]=='#')
maxl= l;
} else {
if(argcount >= *argc)
(*argv)[argcount]= (char *) calloc(1, l+1);
{ret= -1; goto ex;}
(*argidx)[argcount]= i;
(*arglno)[argcount]= line_no;
fclose(fp); fp= NULL;
*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;
/** 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;
home= getenv("HOME");
if((int) (strlen(home) + strlen(filename) + 1) >= fa_size)
#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(strncmp((char *) (data+1),"CD001",5)!=0)
sectors= data[80] | (data[81]<<8) | (data[82]<<16) | (data[83]<<24);
*size_in_bytes= sectors*2048.0;
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)
data[87-i]= data[80+i]= (sectors >> (8*i)) & 0xff;
int Wait_for_input(int fd, int microsec, int flag)
struct timeval wt;
fd_set rds,wts,exs;
int ready;
wt.tv_sec= microsec/1000000;
wt.tv_usec= microsec%1000000;
ready= select(fd+1,&rds,&wts,&exs,&wt);
/* --------------------------------------------------------------------- */
/** 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);
for(i= 0;i<Cdradrtrn_leN;i++) {
o->from_address[i]= NULL;
o->to_address[i]= NULL;
o->fill_counter= 0;
/** Release from memory a device address translator object */
int Cdradrtrn_destroy(struct CdradrtrN **o, int flag)
int i;
struct CdradrtrN *trn;
trn= *o;
for(i= 0;i<trn->fill_counter;i++) {
free((char *) trn);
*o= NULL;
/** 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(flag&1) {
to_pt= strchr(buf,to[0]);
*(to_pt)= 0;
from_pt= buf;
} else {
from_pt= from;
to_pt= to;
if(strlen(from)>=Cdrskin_adrleN || strlen(to)>=Cdrskin_adrleN)
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 ||