/*
 * Program write.c - dump memory  structures to  file for iso9660 filesystem.

   Written by Eric Youngdale (1993).

   Copyright 1993 Yggdrasil Computing, Incorporated

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <string.h>
#include <stdlib.h>
#include "mkisofs.h"
#include "iso9660.h"
#include <time.h>
#include <errno.h>

#ifdef __svr4__
extern char * strdup(const char *);
#endif


/* Max number of sectors we will write at  one time */
#define NSECT 16

/* Counters for statistics */

static int table_size = 0;
static int total_dir_size = 0;
static int rockridge_size = 0;
static struct directory ** pathlist;
static next_path_index = 1;

/* Used to fill in some  of the information in the volume descriptor. */
static struct tm *local;

/* Routines to actually write the disc.  We write sequentially so that
   we could write a tape, or write the disc directly */


#define FILL_SPACE(X)   memset(vol_desc.X, ' ', sizeof(vol_desc.X))

void FDECL2(set_721, char *, pnt, unsigned int, i){
 	pnt[0] = i & 0xff;
	pnt[1] = (i >> 8) &  0xff;
}

void FDECL2(set_722, char *, pnt, unsigned int, i){
	pnt[0] = (i >> 8) &  0xff;
 	pnt[1] = i & 0xff;
}

void FDECL2(set_723, char *, pnt, unsigned int, i){
 	pnt[3] = pnt[0] = i & 0xff;
	pnt[2] = pnt[1] = (i >> 8) &  0xff;
}

void FDECL2(set_731, char *, pnt, unsigned int, i){
 	pnt[0] = i & 0xff;
	pnt[1] = (i >> 8) &  0xff;
	pnt[2] = (i >> 16) &  0xff;
	pnt[3] = (i >> 24) &  0xff;
}

void FDECL2(set_732, char *, pnt, unsigned int, i){
 	pnt[3] = i & 0xff;
	pnt[2] = (i >> 8) &  0xff;
	pnt[1] = (i >> 16) &  0xff;
	pnt[0] = (i >> 24) &  0xff;
}

int FDECL1(get_733, char *, p){
	return ((p[0] & 0xff)
		| ((p[1] & 0xff) << 8)
		| ((p[2] & 0xff) << 16)
		| ((p[3] & 0xff) << 24));
}

void FDECL2(set_733, char *, pnt, unsigned int, i){
 	pnt[7] = pnt[0] = i & 0xff;
	pnt[6] = pnt[1] = (i >> 8) &  0xff;
	pnt[5] = pnt[2] = (i >> 16) &  0xff;
	pnt[4] = pnt[3] = (i >> 24) &  0xff;
}

static xfwrite(void * buffer, int count, int size, FILE * file)
{
  if (!fwrite(buffer, count, size, file) != size) return;
  fprintf(stderr,"cannot write raw file\n"); 
  exit(1);
}

struct deferred_write{
  struct deferred_write * next;
  char * table;
  unsigned int extent;
  unsigned int size;
  char * name;
};

static struct deferred_write * dw_head = NULL, * dw_tail = NULL;

static struct directory_entry * sort_dir;

static unsigned int last_extent_written  =0;
static struct iso_primary_descriptor vol_desc;
static path_table_index;

/* We recursively walk through all of the directories and assign extent
   numbers to them.  We have already assigned extent numbers to everything that
   goes in front of them */

void FDECL1(assign_directory_addresses, struct directory *, node){
  struct directory * dpnt;
  int dir_size;

  dpnt = node;

  while (dpnt){
    dpnt->extent = last_extent;
    dpnt->path_index = next_path_index++;
    dir_size = (dpnt->size + (SECTOR_SIZE - 1)) >> 11;

    last_extent += dir_size;
    if(dpnt->subdir) assign_directory_addresses(dpnt->subdir);
    dpnt = dpnt->next;
  };
}

static void FDECL3(write_one_file, char *, filename, unsigned int, size, FILE *, outfile){
  FILE * infile;
  char buffer[SECTOR_SIZE * NSECT];
  int use;
  int remain;
  if ((infile = fopen(filename, "rb")) == NULL) {
#ifdef sun
      fprintf(stderr, "cannot open %s: (%d)\n", filename, errno);
#else
      fprintf(stderr, "cannot open %s: %s\n", filename, strerror(errno));
#endif
      exit(1);
  }
  remain = size;

  while(remain > 0){
	  use =  (remain >  SECTOR_SIZE * NSECT - 1 ? NSECT*SECTOR_SIZE : remain);
	  use = ROUND_UP(use); /* Round up to nearest sector boundary */
	  memset(buffer, 0, use);
	  if (fread(buffer, 1, use, infile) == 0) {
	    fprintf(stderr,"cannot read from %s\n",filename); 
	    exit(1);
	  }
	  xfwrite(buffer, 1, use, outfile);
	  last_extent_written += use/SECTOR_SIZE;
	  if((last_extent_written % 1000) < use/SECTOR_SIZE) fprintf(stderr,"%d..", last_extent_written);
	  remain -= use;
  };
  fclose(infile);
}

static void FDECL1(write_files, FILE *, outfile){
  struct deferred_write * dwpnt, *dwnext;
  dwpnt = dw_head;
  while(dwpnt){
	  if(dwpnt->table) {
		  xfwrite(dwpnt->table,  1, ROUND_UP(dwpnt->size), outfile);
		  table_size += dwpnt->size;
		  fprintf(stderr,"Size %d ", dwpnt->size);
		  free(dwpnt->table);
	  } else {

		  write_one_file(dwpnt->name, dwpnt->size, outfile);
		  free(dwpnt->name);
	  };

		  dwnext = dwpnt;
		  dwpnt = dwpnt->next;
		  free(dwnext);
  };
}

#if 0
static void dump_filelist(){
  struct deferred_write * dwpnt;
  dwpnt = dw_head;
  while(dwpnt){
    fprintf(stderr, "File %s\n",dwpnt->name);
    dwpnt = dwpnt->next;
  };
  fprintf(stderr,"\n");
};
#endif

int FDECL2(compare_dirs, const struct directory_entry **, r, const struct directory_entry **, l) {
  return strcmp((*r)->isorec.name, (*l)->isorec.name);;
  
}

void FDECL1(sort_directory, struct directory_entry **, sort_dir){
  int dcount = 0;
  int i, len;
  struct directory_entry * s_entry;
  struct directory_entry ** sortlist;

  s_entry = *sort_dir;
  while(s_entry){
    dcount++;
    s_entry = s_entry->next;
  };
  /* OK, now we know how many there are.  Build a vector for sorting. */

  sortlist =   (struct directory_entry **) 
    malloc(sizeof(struct directory_entry *) * dcount);

  dcount = 0;
  s_entry = *sort_dir;
  while(s_entry){
    sortlist[dcount] = s_entry;
    len = s_entry->isorec.name_len[0];
    s_entry->isorec.name[len] = 0;
    dcount++;
    s_entry = s_entry->next;
  };
  
  qsort(sortlist, dcount, sizeof(struct directory_entry *), compare_dirs);

  /* Now reassemble the linked list in the proper sorted order */
  for(i=0; i<dcount-1; i++)
    sortlist[i]->next = sortlist[i+1];

  sortlist[dcount-1]->next = NULL;
  *sort_dir = sortlist[0];

  free(sortlist);

}

void generate_root_record(){
  time_t ctime;

  time (&ctime);
  local = localtime(&ctime);

  root_record.length[0] = 1 + sizeof(struct iso_directory_record);
  root_record.ext_attr_length[0] = 0;
  set_733(root_record.extent, root->extent);
  set_733(root_record.size, ROUND_UP(root->size));
  iso9660_date(root_record.date, ctime);
  root_record.flags[0] = 2;
  root_record.file_unit_size[0] = 0;
  root_record.interleave[0] = 0;
  set_723(root_record.volume_sequence_number, 1);
  root_record.name_len[0] = 1;
}

static void FDECL1(assign_file_addresses, struct directory *, dpnt){
  struct directory * finddir;
  struct directory_entry * s_entry;
  struct file_hash *s_hash;
  struct deferred_write * dwpnt;
  char whole_path[1024];

  while (dpnt){
    s_entry = dpnt->contents;
    for(s_entry = dpnt->contents; s_entry; s_entry = s_entry->next){
      
      /* This saves some space if there are symlinks present */
      s_hash = find_hash(s_entry->dev, s_entry->inode);
      if(s_hash){
        if(verbose)
	  fprintf(stderr, "Cache hit for %s/%s\n",s_entry->filedir->de_name, 
		  s_entry->name);
        set_733(s_entry->isorec.extent, s_hash->starting_block);
        set_733(s_entry->isorec.size, s_hash->size);
        continue;
      };
      if (strcmp(s_entry->name,".") && strcmp(s_entry->name,"..") && 
	  s_entry->isorec.flags[0] == 2){
	finddir = dpnt->subdir;
	while(1==1){
	  if(finddir->self == s_entry) break;
	  finddir = finddir->next;
	  if(!finddir) {fprintf(stderr,"Fatal goof\n"); exit(1);};
	};
	set_733(s_entry->isorec.extent, finddir->extent);
	s_entry->starting_block = finddir->extent;
	s_entry->size = ROUND_UP(finddir->size);
	total_dir_size += s_entry->size;
	add_hash(s_entry);
	set_733(s_entry->isorec.size, ROUND_UP(finddir->size));
      } else {
        if(strcmp(s_entry->name,".") ==0 || strcmp(s_entry->name,"..") == 0) {
	  if(strcmp(s_entry->name,".") == 0) {
	    set_733(s_entry->isorec.extent, dpnt->extent);
	    
	    /* Set these so that the hash table has the correct information */
	    s_entry->starting_block = dpnt->extent;
	    s_entry->size = ROUND_UP(dpnt->size);
	    
	    add_hash(s_entry);
	    s_entry->starting_block = dpnt->extent;
	    set_733(s_entry->isorec.size, ROUND_UP(dpnt->size));
	  } else {
	    if(dpnt == root) total_dir_size += root->size;
	    set_733(s_entry->isorec.extent, dpnt->parent->extent);
	    
	    /* Set these so that the hash table has the correct information */
	    s_entry->starting_block = dpnt->parent->extent;
	    s_entry->size = ROUND_UP(dpnt->parent->size);
	    
	    add_hash(s_entry);
	    s_entry->starting_block = dpnt->parent->extent;
	    set_733(s_entry->isorec.size, ROUND_UP(dpnt->parent->size));
	  };
        } else {
	  /* Now we schedule the file to be written.  This is all quite
	     straightforward, just make a list and assign extents as we go.
	     Once we get through writing all of the directories, we should
	     be ready write out these files */
	
	  if(s_entry->size) {
	    dwpnt = (struct deferred_write *) 
	      malloc(sizeof(struct deferred_write));
	    if(dw_tail){
	      dw_tail->next = dwpnt;
	      dw_tail = dwpnt;
	    } else {
	      dw_head = dwpnt;
	      dw_tail = dwpnt;
	    };
 	    if(s_entry->inode  ==  TABLE_INODE) {
	      dwpnt->table = s_entry->table;
	      dwpnt->name = NULL;
	    } else {
	      dwpnt->table = NULL;
	      strcpy(whole_path, s_entry->filedir->whole_name);
	      if(strlen(whole_path)) strcat(whole_path, "/");
	      strcat(whole_path, s_entry->name);
	      dwpnt->name = strdup(whole_path);
	    };
	    dwpnt->next = NULL;
	    dwpnt->size = s_entry->size;
	    dwpnt->extent = last_extent;
	    set_733(s_entry->isorec.extent, last_extent);
	    s_entry->starting_block = last_extent;
	    add_hash(s_entry);
	    last_extent += ROUND_UP(s_entry->size) >> 11;
	    if(verbose)
	      fprintf(stderr,"%d %d %s\n", s_entry->starting_block,
		      last_extent-1, whole_path);
#ifdef DBG_ISO
	    if((ROUND_UP(s_entry->size) >> 11) > 500){
	      fprintf(stderr,"Warning: large file %s\n", whole_path);
	      fprintf(stderr,"Starting block is %d\n", s_entry->starting_block);
	      fprintf(stderr,"Reported file size is %d extents\n", s_entry->size);
	      
	    };
#endif
	    if(last_extent > (700000000 >> 11)) {  /* More than 700Mb? Punt */
	      fprintf(stderr,"Extent overflow processing file %s\n", whole_path);
	      fprintf(stderr,"Starting block is %d\n", s_entry->starting_block);
	      fprintf(stderr,"Reported file size is %d extents\n", s_entry->size);
	      exit(1);
	    };
	  } else
	    set_733(s_entry->isorec.extent, 0);
	};
      };
    };
    if(dpnt->subdir) assign_file_addresses(dpnt->subdir);
    dpnt = dpnt->next;
  };
}

void FDECL2(generate_one_directory, struct directory *, dpnt, FILE *, outfile){
  unsigned int total_size;
  char * directory_buffer;
  struct directory_entry * s_entry, *s_entry_d;
  int new_reclen;
  unsigned int dir_index;

  total_size = (dpnt->size + (SECTOR_SIZE - 1)) &  ~(SECTOR_SIZE - 1);
  directory_buffer = (char *) malloc(total_size);
  memset(directory_buffer, 0, total_size);
  dir_index = 0;

  s_entry = dpnt->contents;
  while(s_entry) {

    /* We do not allow directory entries to cross sector boundaries.  Simply
       pad, and then start the next entry at the next sector */
    new_reclen = s_entry->isorec.length[0];
    if ((dir_index & (SECTOR_SIZE - 1)) + new_reclen >= SECTOR_SIZE)
      dir_index = (dir_index + (SECTOR_SIZE - 1)) & 
	~(SECTOR_SIZE - 1);

    memcpy(directory_buffer + dir_index, &s_entry->isorec, 
	   sizeof(struct iso_directory_record) -
	   sizeof(s_entry->isorec.name) + s_entry->isorec.name_len[0]);
     dir_index += sizeof(struct iso_directory_record) - 
      sizeof (s_entry->isorec.name)+ s_entry->isorec.name_len[0];

    /* Add the Rock Ridge attributes, if present */
    if(s_entry->rr_attr_size){
      if(dir_index & 1)
	directory_buffer[dir_index++] = 0;

      rockridge_size += s_entry->rr_attr_size;
      memcpy(directory_buffer + dir_index, s_entry->rr_attributes, 
	     s_entry->rr_attr_size);
      dir_index += s_entry->rr_attr_size;

    };
    if(dir_index & 1)
	    directory_buffer[dir_index++] = 0;

    s_entry_d = s_entry;
    s_entry = s_entry->next;
    free (s_entry_d);
  };
  sort_dir = NULL;

  if(dpnt->size != dir_index)
    fprintf(stderr,"Unexpected directory length %d %d %s\n",dpnt->size, 
	    dir_index, dpnt->de_name);
  xfwrite(directory_buffer, 1, total_size, outfile);
  last_extent_written += total_size >> 11;
  free(directory_buffer);
}

static void FDECL1(build_pathlist, struct directory *, node){
  struct directory * dpnt;

  dpnt = node;

  while (dpnt){
    pathlist[dpnt->path_index] = dpnt;
    if(dpnt->subdir) build_pathlist(dpnt->subdir);
    dpnt = dpnt->next;
  };
}

int FDECL2(compare_paths, const struct directory **, r, const struct directory **, l) {
  if((*r)->parent->path_index < (*l)->parent->path_index) return -1;
  if((*r)->parent->path_index > (*l)->parent->path_index) return 1;
  return strcmp((*r)->self->isorec.name, (*l)->self->isorec.name);
  
}

void generate_path_tables(){
  struct directory * dpnt;
  char * npnt, *npnt1;
  int namelen;
  struct directory_entry * de;
  int fix;
  int tablesize;
  int i,j;
  /* First allocate memory for the tables and initialize the memory */

  tablesize = path_blocks << 11;
  path_table_m = (char *) malloc(tablesize);
  path_table_l = (char *) malloc(tablesize);
  memset(path_table_l, 0, tablesize);
  memset(path_table_m, 0, tablesize);

  /* Now start filling in the path tables.  Start with root directory */
  path_table_index = 0;
  pathlist = (struct directory **) malloc(sizeof(struct directory *) * next_path_index);
  memset(pathlist, 0, sizeof(struct directory *) * next_path_index);
  build_pathlist(root);

  do{
    fix = 0;
    qsort(&pathlist[1], next_path_index-1, sizeof(struct directory *), compare_paths);

    for(j=1; j<next_path_index; j++)
      if(pathlist[j]->path_index != j){
	pathlist[j]->path_index = j;
	fix++;
      };
  } while(fix);

  for(j=1; j<next_path_index; j++){
    dpnt = pathlist[j];
    if(!dpnt){
      fprintf(stderr,"Entry %d not in path tables\n", j);
      exit(1);
    };
    npnt = dpnt->de_name;
    if(*npnt == 0 || dpnt == root) npnt = ".";  /* So the root comes out OK */
    npnt1 = strrchr(npnt, '/');
    if(npnt1) npnt = npnt1 + 1;
	    
    de = dpnt->self;
    if(!de) {fprintf(stderr,"Fatal goof\n"); exit(1);};


    namelen = de->isorec.name_len[0];

    path_table_l[path_table_index] = namelen;
    path_table_m[path_table_index] = namelen;
    path_table_index += 2;
    set_731(path_table_l + path_table_index, dpnt->extent); 
    set_732(path_table_m + path_table_index, dpnt->extent); 
    path_table_index += 4;
    set_721(path_table_l + path_table_index, dpnt->parent->path_index); 
    set_722(path_table_m + path_table_index, dpnt->parent->path_index); 
    path_table_index += 2;
    for(i =0; i<namelen; i++){
      path_table_l[path_table_index] = de->isorec.name[i];
      path_table_m[path_table_index] = de->isorec.name[i];
      path_table_index++;
    };
    if(path_table_index & 1) path_table_index++;  /* For odd lengths we pad */
  };
  free(pathlist);
  if(path_table_index != path_table_size)
    fprintf(stderr,"Path table lengths do not match %d %d\n",path_table_index,
	    path_table_size);
}

int FDECL2(iso_write, FILE *, outfile, char *, volid){
  char buffer[2048];
  char iso_time[17];
  int should_write;
  int i;

  assign_file_addresses(root);

  memset(buffer, 0, sizeof(buffer));

  /* This will break  in the year  2000, I supose, but there is no good way
     to get the top two digits of the year. */
  sprintf(iso_time, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d00", 1900 + local->tm_year,
	  local->tm_mon+1, local->tm_mday,
	  local->tm_hour, local->tm_min, local->tm_sec);

  /* First, we output 16 sectors of all zero */

  for(i=0; i<16; i++)
    xfwrite(buffer, 1, sizeof(buffer), outfile);

  last_extent_written += 16;

  /* Next we write out the primary descriptor for the disc */
  memset(&vol_desc, 0, sizeof(vol_desc));
  vol_desc.type[0] = ISO_VD_PRIMARY;
  memcpy(vol_desc.id, ISO_STANDARD_ID, sizeof(ISO_STANDARD_ID));
  vol_desc.version[0] = 1;

  memset(vol_desc.system_id, ' ', sizeof(vol_desc.system_id));
#ifdef __QNX__
  memcpy(vol_desc.system_id, "QNX", sizeof("QNX"));
#else
  memcpy(vol_desc.system_id, "LINUX", sizeof("LINUX"));
#endif

  memset(vol_desc.volume_id, ' ', sizeof(vol_desc.volume_id));
  memcpy(vol_desc.volume_id, volid, strlen(volid));

  should_write = last_extent;
  set_733(vol_desc.volume_space_size, last_extent);
  set_723(vol_desc.volume_set_size, 1);
  set_723(vol_desc.volume_sequence_number, 1);
  set_723(vol_desc.logical_block_size, 2048);

  /* The path tables are used by DOS based machines to cache directory
     locations */

  set_733(vol_desc.path_table_size, path_table_size);
  set_731(vol_desc.type_l_path_table, path_table[0]);
  set_731(vol_desc.opt_type_l_path_table, path_table[1]);
  set_732(vol_desc.type_m_path_table, path_table[2]);
  set_732(vol_desc.opt_type_m_path_table, path_table[3]);

  /* Now we copy the actual root directory record */

  memcpy(vol_desc.root_directory_record, &root_record, 
	 sizeof(struct iso_directory_record) + 1);

  /* The rest is just fluff.  It looks nice to fill in many of these fields,
     though */

  FILL_SPACE(volume_set_id);
  FILL_SPACE(publisher_id);
  if(publisher)  memcpy(vol_desc.publisher_id,  publisher, strlen(publisher));
  FILL_SPACE(preparer_id);
  if(preparer)  memcpy(vol_desc.preparer_id,  preparer, strlen(preparer));
  FILL_SPACE(application_id);
  FILL_SPACE(copyright_file_id);
  FILL_SPACE(abstract_file_id);
  FILL_SPACE(bibliographic_file_id);
  FILL_SPACE(creation_date);
  FILL_SPACE(modification_date);
  FILL_SPACE(expiration_date);
  FILL_SPACE(effective_date);
  vol_desc.file_structure_version[0] = 1;
  FILL_SPACE(application_data);

  memcpy(vol_desc.creation_date,  iso_time, 16);
  memcpy(vol_desc.modification_date,  iso_time, 16);
  memcpy(vol_desc.expiration_date, "0000000000000000", 16);
  memcpy(vol_desc.effective_date,  iso_time,  16);

  /* For some reason, Young Minds writes this twice.  Aw, what the heck */
  xfwrite(&vol_desc, 1, 2048, outfile);
  xfwrite(&vol_desc, 1, 2048, outfile);
  last_extent_written += 2;

  /* Now write the end volume descriptor.  Much simpler than the other one */
  memset(&vol_desc, 0, sizeof(vol_desc));
  vol_desc.type[0] = ISO_VD_END;
  memcpy(vol_desc.id, ISO_STANDARD_ID, sizeof(ISO_STANDARD_ID));
  vol_desc.version[0] = 1;
  xfwrite(&vol_desc, 1, 2048, outfile);
  xfwrite(&vol_desc, 1, 2048, outfile);
  last_extent_written += 2;

  /* Next we write the path tables */
  xfwrite(path_table_l, 1, path_blocks << 11, outfile);
  xfwrite(path_table_l, 1, path_blocks << 11, outfile);
  xfwrite(path_table_m, 1, path_blocks << 11, outfile);
  xfwrite(path_table_m, 1, path_blocks << 11, outfile);
  last_extent_written += 4*path_blocks;
  free(path_table_l);
  free(path_table_m);
  path_table_l = NULL;
  path_table_m = NULL;

  /* OK, all done with that crap.  Now write out the directories.
     This is where the fur starts to fly, because we need to keep track of
     each file as we find it and keep track of where we put it. */

#ifdef DBG_ISO
  fprintf(stderr,"Total directory extents being written = %d\n", last_extent);
#endif
#if 0 
 generate_one_directory(root, outfile);
#endif
  generate_iso9660_directories(root, outfile);

  if(extension_record) xfwrite(extension_record, 1, SECTOR_SIZE, outfile);


  /* Now write all of the files that we need. */
  fprintf(stderr,"Total extents being written = %d\n", last_extent);
  write_files(outfile);


  /* Hard links throw us off here */
  if(should_write != last_extent){
    fprintf(stderr,"Number of extents written not what was predicted.  Please fix.\n");
    fprintf(stderr,"Predicted = %d, written = %d\n", should_write, last_extent);
  };

  fprintf(stderr,"Total translation table size: %d\n", table_size);
  fprintf(stderr,"Total rockridge attributes bytes: %d\n", rockridge_size);
  fprintf(stderr,"Total directory bytes: %d\n", total_dir_size);
  fprintf(stderr,"Path table size(bytes): %d\n", path_table_size);
#ifdef DEBUG
  fprintf(stderr, "next extent, last_extent, last_extent_written %d %d %d\n",
	  next_extent, last_extent, last_extent_written);
#endif
  return 0;
}
