
/*
  cc -g -c isoburn.c
*/

/*
  Class core of libisoburn.

  Copyright 2007 Vreixo Formoso Lopes <metalpain2002@yahoo.es>
             and Thomas Schmitt <scdbackup@gmx.net>
*/

/* ( derived from stub generated by CgeN on  Sat, 01 Sep 2007 12:04:36 GMT ) */

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

#ifndef Xorriso_standalonE

#include <libburn/libburn.h>

#include <libisofs/libisofs.h>

#else /* ! Xorriso_standalonE */

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

#endif /* Xorriso_standalonE */


#include "libisoburn.h"

#include "isoburn.h"

/* No more: version numbers out of configure.ac
   major.minor.micro now comes from libisoburn.h
#include "../version.h"
*/

/* --------------------------  isoburn  ----------------------- */


/* The global list of isoburn objects. Usually there is only one.
   >>> we are not ready for multiple control threads yet. See >>> mutex .
   Multiple burns under one control thread should work.
*/
struct isoburn *isoburn_list_start= NULL;


int isoburn_new(struct isoburn **objpt, int flag)
{
 struct isoburn *o;
 int i, ret;

 *objpt= o= (struct isoburn *) malloc(sizeof(struct isoburn));
 if(o==NULL) {
   burn_msgs_submit(0x00060000,
                    "Cannot allocate memory for isoburn control object",
                    0, "FATAL", NULL);
   return(-1);
 }

 o->drive= NULL;
 o->emulation_mode= 0;
 o->min_start_byte= 0;
 o->nwa= 0;
 o->truncate= 0;

#ifdef Libisoburn_no_fifO
 o->iso_source= NULL;
#else
 o->fifo= NULL;
#endif

 o->wrote_well= -1;
 o->fabricated_disc_status= BURN_DISC_UNREADY;
 for(i=0;i<65536;i++)
   o->target_iso_head[i]= 0;
 o->image= NULL;
 o->read_pacifier= NULL;
 o->read_pacifier_handle= NULL;
 o->prev= NULL;
 o->next= NULL;
 ret= iso_image_new("ISOIMAGE", &o->image);
 if(ret<0) {
   isoburn_report_iso_error(ret, "Cannot create image", 0, "FATAL", 0);
   goto failed;
 }
 isoburn_link(o, isoburn_list_start, 1);
 return(1);
failed:;
 isoburn_destroy(objpt, 0);
 return(-1);
}


int isoburn_destroy(struct isoburn **objpt, int flag)
{
 struct isoburn *o;

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

 /* >>> mutex */

 if(o==isoburn_list_start)
   isoburn_list_start= o->next;
 if(o->prev!=NULL)
   o->prev->next= o->next;
 if(o->next!=NULL)
   o->next->prev= o->prev;

 /* >>> end mutex */

 if(o->image!=NULL)
   iso_image_unref(o->image);

#ifdef Libisoburn_no_fifO
 if(o->iso_source!=NULL)
   burn_source_free(o->iso_source);
#else
 if(o->fifo!=NULL)
   burn_source_free(o->fifo);
#endif /* ! Libisoburn_no_fifO */


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


int isoburn_destroy_all(struct isoburn **objpt, int flag)
{
 struct isoburn *o,*n;

 o= *objpt;
 if(o==NULL)
   return(0);
 for(;o->prev!=NULL;o= o->prev);
 for(;o!=NULL;o= n) {
   n= o->next;
   isoburn_destroy(&o,0);
 }
 *objpt= NULL;
 return(1);
}


int isoburn_get_target_image(struct isoburn *o, IsoImage **pt, int flag)
{
 *pt= o->image;
 return(1);
}


int isoburn_get_prev(struct isoburn *o, struct isoburn **pt, int flag)
{
 *pt= o->prev;
 return(1);
}


int isoburn_get_next(struct isoburn *o, struct isoburn **pt, int flag)
{
 *pt= o->next;
 return(1);
}


int isoburn_link(struct isoburn *o, struct isoburn *link, int flag)
/*
  bit0= insert as link->prev rather than as link->next
*/
{

 /* >>> mutex */

 if(isoburn_list_start==NULL ||
    (isoburn_list_start==link && (flag&1)))
   isoburn_list_start= o;
 if(o->prev!=NULL)
   o->prev->next= o->next;
 if(o->next!=NULL)
   o->next->prev= o->prev;
 o->prev= o->next= NULL;
 if(link==NULL)
   return(1);
 if(flag&1) {
   o->next= link;
   o->prev= link->prev;
   if(o->prev!=NULL)
     o->prev->next= o;
   link->prev= o;
 } else {
   o->prev= link;
   o->next= link->next;
   if(o->next!=NULL)
     o->next->prev= o;
   link->next= o;
 }

 /* >>> end mutex */

 return(1);
}


int isoburn_count(struct isoburn *o, int flag)
/* flag: bit1= count from start of list */
{
 int counter= 0;

 if(flag&2)
   for(;o->prev!=NULL;o= o->prev);
 for(;o!=NULL;o= o->next)
   counter++;
 return(counter);
}


int isoburn_by_idx(struct isoburn *o, int idx, struct isoburn **pt, int flag)
/* flag: bit0= fetch first (idx<0) or last (idx>0) item in list
         bit1= address from start of list */
{
 int i,abs_idx;
 struct isoburn *npt;

 if(flag&2)
   for(;o->prev!=NULL;o= o->prev);
 abs_idx= (idx>0?idx:-idx);
 *pt= o;
 for(i= 0;(i<abs_idx || (flag&1)) && *pt!=NULL;i++) {
   if(idx>0)
     npt= o->next;
   else
     npt= o->prev;
   if(npt==NULL && (flag&1))
 break;
   *pt= npt;
 }
 return(*pt!=NULL);
}


int isoburn_find_by_drive(struct isoburn **pt, struct burn_drive *d, int flag)
{
 struct isoburn *o;

 *pt= NULL;
 for(o= isoburn_list_start;o!=NULL;o= o->next)
   if(o->drive==d) {
     *pt= o;
     return(1);
   }
 return(0);
}

static
int isoburn_prepare_disc_aux(struct burn_drive *d, struct burn_disc **disc,
                             struct isoburn_imgen_opts *opts, int new_img)
{
 struct burn_source *wsrc;
 struct burn_session *session;
 struct burn_track *track;
 struct isoburn *o;
 IsoWriteOpts *wopts= NULL;
 enum burn_disc_status state;
 int ret, fifo_chunks;

 ret= isoburn_find_emulator(&o, d, 0);
 if(ret<0 || o==NULL)
   {ret= -1; goto ex;}
 o->wrote_well= 0; /* early end will be registered as failure */

 state = isoburn_disc_get_status(d);
 if (state != BURN_DISC_BLANK && state != BURN_DISC_APPENDABLE
     && (state != BURN_DISC_FULL || ! new_img)) {
   /* unsuitable status */
   burn_msgs_submit(0x00060000, "Unsuitable media state", 0, "FAILURE", NULL);
   {ret= -2; goto ex;}
 }
 
 fifo_chunks= 32;
 if(opts->fifo_size >= 64*1024 && opts->fifo_size <= 1024.0 * 1024.0 * 1024.0){
   fifo_chunks= opts->fifo_size/2048;
   if(fifo_chunks*2048 < opts->fifo_size)
     fifo_chunks++;
 }

 ret = iso_write_opts_new(&wopts, 0);
 if (ret < 0) {
   isoburn_report_iso_error(ret, "Cannot create iso_write_opts", 0, "FATAL",0);
   goto ex;
 }
 iso_write_opts_set_iso_level(wopts, opts->level);
 iso_write_opts_set_rockridge(wopts, opts->rockridge);
 iso_write_opts_set_joliet(wopts, opts->joliet);
 iso_write_opts_set_iso1999(wopts, opts->iso1999);
 iso_write_opts_set_omit_version_numbers(wopts, opts->omit_version_numbers);
 iso_write_opts_set_allow_deep_paths(wopts, opts->allow_deep_paths);
 iso_write_opts_set_allow_longer_paths(wopts, opts->allow_longer_paths);
 iso_write_opts_set_max_37_char_filenames(wopts, opts->max_37_char_filenames);
 iso_write_opts_set_no_force_dots(wopts, opts->no_force_dots);
 iso_write_opts_set_allow_lowercase(wopts, opts->allow_lowercase);
 iso_write_opts_set_allow_full_ascii(wopts, opts->allow_full_ascii);
 iso_write_opts_set_relaxed_vol_atts(wopts, 1);
 iso_write_opts_set_joliet_longer_paths(wopts, opts->joliet_longer_paths);
 iso_write_opts_set_sort_files(wopts, opts->sort_files);
 iso_write_opts_set_replace_mode(wopts, opts->replace_dir_mode,
                opts->replace_file_mode, opts->replace_uid, opts->replace_gid);
 iso_write_opts_set_default_dir_mode(wopts, opts->dir_mode);
 iso_write_opts_set_default_file_mode(wopts, opts->file_mode);
 iso_write_opts_set_default_uid(wopts, opts->uid);
 iso_write_opts_set_default_gid(wopts, opts->gid);
 iso_write_opts_set_output_charset(wopts, opts->output_charset);
#ifdef Libisoburn_no_fifO
 iso_write_opts_set_fifo_size(wopts, fifo_chunks);
#endif /* Libisoburn_no_fifO */
 
 if (new_img) {
   iso_write_opts_set_ms_block(wopts, 0);
   opts->effective_lba= 0;
   iso_write_opts_set_appendable(wopts, 0);
   iso_write_opts_set_overwrite_buf(wopts, NULL);
 } else {
   int lba, nwa;
   ret = isoburn_disc_track_lba_nwa(d, NULL, 0, &lba, &nwa);
   if (ret != 1) {
     burn_msgs_submit(0x00060000, "Cannot determine next writeable address", 0,
                      "FAILURE", NULL);
     {ret= -3; goto ex;}
   }
   if (nwa == 0 && state == BURN_DISC_APPENDABLE) {
     /* invalid nwa */
     burn_msgs_submit(0x00060000, "Encountered 0 as next writeable address", 0,
                      "FAILURE", NULL);
     {ret= -4; goto ex;}
   }
   iso_write_opts_set_ms_block(wopts, nwa);
   opts->effective_lba= nwa;
   iso_write_opts_set_appendable(wopts, 1);
   iso_write_opts_set_overwrite_buf(wopts, o->target_iso_head);
 }

 ret = iso_image_create_burn_source(o->image, wopts, &wsrc);
 if (ret < 0) {
   isoburn_report_iso_error(ret, "Cannot create burn source", 0, "FAILURE", 0);
   {ret= -1; goto ex;}
 }
 
 /* TODO check return values for failure. propertly clean-up on error */

#ifdef Libisoburn_no_fifO
 o->iso_source= wsrc;
#else
 o->fifo = burn_fifo_source_new(wsrc, 2048, fifo_chunks, 0);
 burn_source_free(wsrc);
 if (o->fifo == NULL) {
   burn_msgs_submit(0x00060000, "Cannot attach fifo", 0, "FATAL", NULL);
   {ret= -1; goto ex;}
 }
#endif /* ! Libisoburn_no_fifO */

 *disc = burn_disc_create();
 session = burn_session_create();
 burn_disc_add_session(*disc, session, BURN_POS_END);
 track = burn_track_create();

#ifdef Libisoburn_no_fifO
 burn_track_set_source(track, o->iso_source);
#else
 burn_track_set_source(track, o->fifo);
#endif /* ! Libisoburn_no_fifO */

 burn_session_add_track(session, track, BURN_POS_END);

 /* give up local references */
 burn_track_free(track);
 burn_session_free(session);

 o->wrote_well= -1; /* neutral */
 ret= 1;
ex:
 if(wopts!=NULL)
   {iso_write_opts_free(wopts); wopts= NULL;}
 return ret;
}

int isoburn_prepare_disc(struct burn_drive *d, struct burn_disc **disc,
                         struct isoburn_imgen_opts *opts)
{
	return isoburn_prepare_disc_aux(d, disc, opts, 0);
}

int isoburn_prepare_new_image(struct burn_drive *d, struct burn_disc **disc,
                         struct isoburn_imgen_opts *opts,
                         struct burn_drive *out_drive)
{
 int ret;
 struct isoburn *in_o, *out_o;

 ret= isoburn_prepare_disc_aux(d, disc, opts, 1);
 if (ret<=0)
   return ret;

#ifdef Libisoburn_no_fifO
 /* Hand over source reference for optional fifo status inquiry */
 if(out_drive==NULL)
   return 1;
 ret= isoburn_find_emulator(&out_o, out_drive, 0);
 if(ret<0 || out_o==NULL)
   return 1;
 ret= isoburn_find_emulator(&in_o, d, 0);
 if(ret<0 || in_o==NULL)
   return 1; /* then without fifo status inquiry */
 if(out_o->iso_source!=NULL)
   burn_source_free(out_o->iso_source);
 out_o->iso_source= in_o->iso_source;
 in_o->iso_source= NULL;
#endif /* Libisoburn_no_fifO */

 return 1; 
}


/* API @since 0.1.0
   @param flag bit0= this is a regular end, not an abort
                     give up source reference
*/
int isoburn_cancel_prepared_write(struct burn_drive *d,
                                  struct burn_drive *output_drive, int flag)
{
 int ret;
 struct isoburn *o= NULL;

 if(output_drive!=NULL) {
   ret= isoburn_find_emulator(&o, output_drive, 0);
   if(ret<0 || o==NULL)
     o= NULL;
   else if(o->iso_source==NULL)
     o= NULL;
 }
 if(o==NULL) {
   ret= isoburn_find_emulator(&o, d, 0);
   if(ret<0)
     return(-1);
   if(o==NULL)
     return(0);
   if(o->iso_source==NULL)
     return(0);
 }
 if(o->iso_source->read!=NULL)
   return(0);
 if(o->iso_source->version<1)
   return(0);
 o->iso_source->cancel(o->iso_source);
 burn_source_free(o->iso_source);
 o->iso_source= NULL;
 return(1);
}


/* API @since 0.1.0 */
int isoburn_sync_after_write(struct burn_drive *d,
                             struct burn_drive *output_drive, int flag)
{
 return isoburn_cancel_prepared_write(d, output_drive, 1);
}


void isoburn_version(int *major, int *minor, int *micro)
{
 *major= isoburn_header_version_major;
 *minor= isoburn_header_version_minor;
 *micro= isoburn_header_version_micro;

/* No more: values from version.h generated from version.h.in and
            macro values defined in configure.ac

 *major = ISOBURN_MAJOR_VERSION;
 *minor = ISOBURN_MINOR_VERSION;
 *micro = ISOBURN_MICRO_VERSION;
*/
}


int isoburn_is_compatible(int major, int minor, int micro, int flag)
{
 int own_major, own_minor, own_micro;

 isoburn_version(&own_major, &own_minor, &own_micro);
 return(own_major > major ||
        (own_major == major && (own_minor > minor ||
         (own_minor == minor && own_micro >= micro))));
}


/* ----------------------------------------------------------------------- */
/*
  Options for image reading.
*/
/* ----------------------------------------------------------------------- */


int isoburn_ropt_new(struct isoburn_read_opts **new_o, int flag)
{
 struct isoburn_read_opts *o;

 o= (*new_o)= calloc(1, sizeof(struct isoburn_read_opts));
 if(o==NULL) {
   burn_msgs_submit(0x00060000, "Cannot allocate memory for read options",
                    0, "FATAL", NULL);
   return(-1);
 }
 o->norock= 0;
 o->nojoliet= 0;
 o->noiso1999= 1;
 o->preferjoliet= 0;
 o->uid= geteuid();
 o->gid= getegid();
 o->mode= 0444;
 o->dirmode= 0555;
 o->input_charset= NULL;
 o->hasRR= 0;
 o->hasJoliet= 0;
 o->hasIso1999= 0;
 o->hasElTorito= 0;
 o->size= 0;
 o->pretend_blank= 1;
 return(1);
}


int isoburn_ropt_destroy(struct isoburn_read_opts **o, int flag)
{
 if(*o==NULL)
   return(0);
 free(*o);
 *o= NULL;
 return(1);
}


int isoburn_ropt_set_extensions(struct isoburn_read_opts *o, int ext)
{
 o->norock= !!(ext&1);
 o->nojoliet= !!(ext&2);
 o->noiso1999= !!(ext&4);
 o->preferjoliet= !!(ext&8);
 o->pretend_blank= !!(ext&16);
 return(1);
}


int isoburn_ropt_get_extensions(struct isoburn_read_opts *o, int *ext)
{
 *ext= (!!o->norock) | ((!!o->nojoliet)<<1) | ((!!o->noiso1999)<<2) |
       ((!!o->preferjoliet)<<3) | ((!!o->pretend_blank)<<4);
 return(1);
}


int isoburn_ropt_set_default_perms(struct isoburn_read_opts *o,
                                   uid_t uid, gid_t gid, mode_t mode)
{
 mode_t dirmode;

 o->uid= uid;
 o->gid= gid;
 o->mode= mode;
 dirmode= mode;
 if(dirmode & S_IRUSR)
   dirmode|= S_IXUSR;
 if(dirmode & S_IRGRP)
   dirmode|= S_IXGRP;
 if(dirmode & S_IROTH)
   dirmode|= S_IXOTH;
 o->dirmode= dirmode;
 return(1);
}


int isoburn_ropt_get_default_perms(struct isoburn_read_opts *o,
                                   uid_t *uid, gid_t *gid, mode_t *mode)
{
 *uid= o->uid;
 *gid= o->gid;
 *mode= o->mode;
 return(1);
}


int isoburn_ropt_set_default_dirperms(struct isoburn_read_opts *o,
                                       mode_t mode)
{
 o->dirmode= mode;
 return(1);
}


int isoburn_ropt_get_default_dirperms(struct isoburn_read_opts *o,
                                      mode_t *mode)
{
 *mode= o->dirmode;
 return(1);
}


int isoburn_ropt_set_input_charset(struct isoburn_read_opts *o,
                                   char *input_charset)
{
 o->input_charset= input_charset;
 return(1);
}


int isoburn_igopt_get_in_charset(struct isoburn_read_opts *o,
                                 char **input_charset)
{
 *input_charset= o->input_charset;
 return(1);
}


int isoburn_ropt_get_size_what(struct isoburn_read_opts *o,
                               uint32_t *size, int *has_what)
{
 *size= o->size;
 *has_what= (!!o->hasRR) | ((!!o->hasJoliet)<<1) |
            ((!!o->hasIso1999)<<2) | ((!!o->hasElTorito)<<3); 
 return(1);
}


/* ----------------------------------------------------------------------- */
/*
  Options for image generation by libisofs and image transport to libburn.
*/
/* ----------------------------------------------------------------------- */


int isoburn_igopt_new(struct isoburn_imgen_opts **new_o, int flag)
{
 struct isoburn_imgen_opts *o;

 o= (*new_o)= calloc(1, sizeof(struct isoburn_imgen_opts));
 if(o==NULL) {
   burn_msgs_submit(0x00060000,
                    "Cannot allocate memory for image generation options",
                    0, "FATAL", NULL);
   return(-1);
 }
 o->level= 2;
 o->rockridge= 1;
 o->joliet= 0;
 o->iso1999= 0;
 o->omit_version_numbers= 0;
 o->allow_deep_paths= 1;
 o->allow_longer_paths= 0;
 o->max_37_char_filenames= 0;
 o->no_force_dots= 0;
 o->allow_lowercase= 0;
 o->allow_full_ascii= 0;
 o->joliet_longer_paths= 0;
 o->sort_files= 0;
 o->replace_dir_mode= 0;
 o->replace_file_mode= 0;
 o->replace_uid= 0;
 o->replace_gid= 0;
 o->dir_mode= 0555;
 o->file_mode= 0444;
 o->uid= 0;
 o->gid= 0;
 o->output_charset= 0;
 o->fifo_size= 4*1024*1024;
 o->effective_lba= -1;
 return(1);
}


int isoburn_igopt_destroy(struct isoburn_imgen_opts **o, int flag)
{
 if(*o==NULL)
   return(0);
 free(*o);
 *o= NULL;
 return(1);
}


int isoburn_igopt_set_level(struct isoburn_imgen_opts *o, int level)
{
 o->level= level;
 return(1);
}


int isoburn_igopt_get_level(struct isoburn_imgen_opts *o, int *level)
{
 *level= o->level;
 return(1);
}


int isoburn_igopt_set_extensions(struct isoburn_imgen_opts *o, int ext)
{
 o->rockridge= !!(ext&1);
 o->joliet= !!(ext&2);
 o->iso1999= !!(ext&4);
 return(1);
}


int isoburn_igopt_get_extensions(struct isoburn_imgen_opts *o, int *ext)
{
 *ext= (!!o->rockridge) | ((!!o->joliet)<<1) | ((!!o->iso1999)<<2);
 return(1);
}


int isoburn_igopt_set_relaxed(struct isoburn_imgen_opts *o, int relax)
{
 o->omit_version_numbers= !!(relax&1);
 o->allow_deep_paths= !!(relax&2);
 o->allow_longer_paths= !!(relax&4);
 o->max_37_char_filenames= !!(relax&8);
 o->no_force_dots= !!(relax&16);
 o->allow_lowercase= !!(relax&32);
 o->allow_full_ascii= !!(relax&64);
 o->joliet_longer_paths= !!(relax&128);
 return(1);
}


int isoburn_igopt_get_relaxed(struct isoburn_imgen_opts *o, int *relax)
{
 *relax= (!!o->omit_version_numbers)    | ((!!o->allow_deep_paths)<<1) |
         ((!!o->allow_longer_paths)<<2) | ((!!o->max_37_char_filenames)<<3) |
         ((!!o->no_force_dots)<<4)      | ((!!o->allow_lowercase)<<5) |
         ((!!o->allow_full_ascii)<<6)   | ((!!o->joliet_longer_paths)<<7);
 return(1);
}


int isoburn_igopt_set_sort_files(struct isoburn_imgen_opts *o, int value)
{
 o->sort_files= !!(value&1);
 return(1);
}


int isoburn_igopt_get_sort_files(struct isoburn_imgen_opts *o, int *value)
{
 *value= !!o->sort_files;
 return(1);
}


int isoburn_igopt_set_over_mode(struct isoburn_imgen_opts *o,
                               int replace_dir_mode, int replace_file_mode,
                               mode_t dir_mode, mode_t file_mode)
{
 o->replace_dir_mode= replace_dir_mode%3;
 o->replace_file_mode= replace_file_mode%3;
 o->dir_mode= dir_mode;
 o->file_mode= file_mode;
 return(1);
}


int isoburn_igopt_get_over_mode(struct isoburn_imgen_opts *o,
                               int *replace_dir_mode, int *replace_file_mode,
                               mode_t *dir_mode, mode_t *file_mode)
{
 *replace_dir_mode= o->replace_dir_mode%3;
 *replace_file_mode= o->replace_file_mode%3;
 *dir_mode= o->dir_mode;
 *file_mode= o->file_mode;
 return(1);
}


int isoburn_igopt_set_over_ugid(struct isoburn_imgen_opts *o,
                               int replace_uid, int replace_gid,
                               uid_t uid, gid_t gid)
{
 o->replace_uid= replace_uid%3;
 o->replace_gid= replace_gid%3;
 o->uid= uid;
 o->gid= gid;
 return(1);
}

int isoburn_igopt_get_over_ugid(struct isoburn_imgen_opts *o,
                               int *replace_uid, int *replace_gid,
                               uid_t *uid, gid_t *gid)
{
 *replace_uid= o->replace_uid%3;
 *replace_gid= o->replace_gid%3;
 *uid= o->uid;
 *gid= o->gid;
 return(1);
}


int isoburn_igopt_set_out_charset(struct isoburn_imgen_opts *o,
                                 char *output_charset)
{
 o->output_charset= output_charset;
 return(1);
}


int isoburn_igopt_get_out_charset(struct isoburn_imgen_opts *o,
                                 char **output_charset)
{
 *output_charset= o->output_charset;
 return(1);
}


int isoburn_igopt_set_fifo_size(struct isoburn_imgen_opts *o, int fifo_size)
{
 o->fifo_size= fifo_size;
 return(1);
}


int isoburn_igopt_get_fifo_size(struct isoburn_imgen_opts *o, int *fifo_size)
{
 *fifo_size= o->fifo_size;
 return(1);
}


int isoburn_igopt_get_effective_lba(struct isoburn_imgen_opts *o, int *lba)
{
 *lba= o->effective_lba;
 return(1);
}


