/*
 * 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 AMIGA
#include <exec/memory.h>
#ifdef LATTICE
#include <proto/utility.h>
#include <proto/exec.h>
#include <proto/dos.h>
#endif
#if defined(__GNUC__) || defined(AZTEC_C)
#include <clib/utility_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#endif
#endif

#ifndef AMIGA
#include <ctype.h>
#endif

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

/* Location and length of extent of cdtv.tm file: */
static unsigned long cdtv_trademark_extent = 0;
static unsigned long cdtv_trademark_length = 0;

/* 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))
#define FILL_NULL(X)    memset(vol_desc.X, 0, 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 int xfwrite(void * buffer, int count, int size, FILE * file)
{
  if (fwrite(buffer, count, size, file) == size) return size;
  fprintf(stderr,"cannot write raw file\n");
  exit(1);
}

#define DEFERRED_CHUNK_SIZE 65000

static struct directory_entry **deferred = NULL;
static unsigned int deferred_count = 0;
static unsigned int deferred_size = 0;

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;
  static unsigned long cnt = 0;

  dpnt = node;

  while (dpnt){
    cnt++;
    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;
  };
#ifdef AMIGA
  if (progress_indicator) {
    printf ("Assigning addresses for directories (%d%% complete);"
            " free mem: %lu   \r",
	    pr_dir_count ? (int)(((cnt-1)*100)/pr_dir_count) : 100,
	    AvailMem (MEMF_ANY));
    fflush (stdout);
  }
#endif
}

static void FDECL3(write_one_file, char *, filename, unsigned int, size, FILE *, outfile){
  FILE * infile;
#ifdef AMIGA
#ifdef LATTICE
  __far static char buffer[SECTOR_SIZE * NSECT];
#else
  static char buffer[SECTOR_SIZE * NSECT];
#endif
#else
  char buffer[SECTOR_SIZE * NSECT];
#endif
  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(verbose && (last_extent_written % 1000) < use/SECTOR_SIZE) {
	    fprintf(stderr,"%d..", last_extent_written);
#ifdef AMIGA
	    fflush (stderr);
#endif
	  }
	  remain -= use;
  };
  fclose(infile);
}

static void FDECL1(write_files, FILE *, outfile){
  unsigned int i;
  for (i=0; i<deferred_count; i++) {
#ifdef AMIGA
    if (progress_indicator &&
        ((i&31) == 0 || i+1 == deferred_count)) {
      printf ("Writing files (%d%% complete); free mem: %lu   \r",
      	      (int) ((100*(i+1))/deferred_count),
      	      AvailMem (MEMF_ANY));
      fflush (stdout);
    }
#endif
#ifdef AMIGA
	  if(deferred[i]->is_a_table) {
#else
	  if(deferred[i]->inode == TABLE_INODE) {
#endif
		  xfwrite(deferred[i]->table, 1, ROUND_UP(deferred[i]->size),
		          outfile);
		  table_size += deferred[i]->size;
	  } else {
	  	  static char whole_path[1024];
		  strcpy (whole_path, deferred[i]->filedir->whole_name);
#ifdef AMIGA
	      	  if(strlen(whole_path) && whole_path[strlen(whole_path)-1] != ':')
		    strcat(whole_path, "/");
#else
	      	  if(strlen(whole_path)) strcat(whole_path, "/");
#endif
		  strcat (whole_path, deferred[i]->name);
		  write_one_file(whole_path, deferred[i]->size, outfile);
	  };
  };
  if (progress_indicator)
    putchar ('\n');
}

/* compare strings of unsigned char: */

#ifdef AMIGA
typedef unsigned char UChar;

static int stringcmp (UChar *str1, UChar *str2)
{
  for (;;) {
    register unsigned char a = *str1++;
    register unsigned char b = *str2++;
    if (a < b)
      return -1;
    if (a > b)
      return 1;
    if (a == 0)
      return 0;
  }
}
#endif

int FDECL2(compare_dirs, const struct directory_entry **, r, const struct directory_entry **, l) {
  static char left[40];
  static char right[40];
  char *cp;
  strcpy (left, (*l)->isorec.iso_name);
  strcpy (right, (*r)->isorec.iso_name);
  if (((*l)->isorec.flags[0] & 2) == 0) {
    if ((cp = strrchr (left, ';')) != 0) {
      *cp = 0;
    }
  }
  if (((*r)->isorec.flags[0] & 2) == 0) {
    if ((cp = strrchr (right, ';')) != 0) {
      *cp = 0;
    }
  }
#ifdef AMIGA
  return stringcmp(right, left);
#else
  return strcmp(right, left);
#endif
}

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 *),
	(int(*)(const void*, const void*)) 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 int compare_extension (const struct directory_entry **e1,
			      const struct directory_entry **e2)
{
  char *ext1, *ext2;
  int res;

  if ((*e1)->isorec.iso_name[0] < 2 || (*e2)->isorec.iso_name[0] < 2)
    return (*e1)->isorec.iso_name[0] - (*e2)->isorec.iso_name[0];

  ext1 = strrchr ((*e1)->isorec.iso_name, '.');
  ext2 = strrchr ((*e2)->isorec.iso_name, '.');

  if (ext1 && !ext2)
    return 1;
  if (!ext1 && ext2)
    return -1;
  if (ext1 && ext2) {
#ifdef AMIGA
    if (res = stringcmp ((UChar*) ext1+1, (UChar*) ext2+1))
#else
    if ((res = strcmp (ext1+1, ext2+1)))
#endif
      return res;
  }
#ifdef AMIGA
  return stringcmp ((UChar*) (*e1)->isorec.iso_name,
    		    (UChar*) (*e2)->isorec.iso_name);
#else
  return strcmp ((*e1)->isorec.iso_name,
		 (*e2)->isorec.iso_name);
#endif
}

static void FDECL1(assign_file_addresses, struct directory *, dpnt){
  struct directory * finddir;
  struct directory_entry * s_entry;
#ifndef AMIGA
  struct file_hash *s_hash;
#endif
  char whole_path[1024];
  int entries;
  struct directory_entry **entry_array;
  int i;
#ifdef AMIGA
  static unsigned long cnt = 1;
#endif

  while (dpnt){
    /* compute number of entries: */
    for (entries=0, s_entry=dpnt->contents; s_entry; s_entry=s_entry->next)
      entries++;

    entry_array = (struct directory_entry **)
      malloc (sizeof (struct directory_entry *) * entries);
    for (i=0, s_entry=dpnt->contents; s_entry; s_entry=s_entry->next)
      entry_array[i++] = s_entry;

    if (sort_extents)
      qsort (entry_array, entries, sizeof (struct directory_entry *),
             (int(*)(const void*, const void*)) compare_extension);

    for (i=0; i<entries; i++) {
      s_entry = entry_array[i];

#ifndef AMIGA
      /* 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;
      };
#endif
      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->size = ROUND_UP(finddir->size);
#ifndef AMIGA
	s_entry->starting_block = finddir->extent;
	add_hash(s_entry);
#endif
	total_dir_size += s_entry->size;
	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);
	    
	    s_entry->size = ROUND_UP(dpnt->size);
#ifndef AMIGA
	    /* Set these so that the hash table has the correct information */
	    s_entry->starting_block = dpnt->extent;
	    add_hash(s_entry);
	    s_entry->starting_block = dpnt->extent;
#endif
	    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);
	    
	    s_entry->size = ROUND_UP(dpnt->parent->size);
#ifndef AMIGA	    
	    /* Set these so that the hash table has the correct information */
	    s_entry->starting_block = dpnt->parent->extent;
	    add_hash(s_entry);	    
	    s_entry->starting_block = dpnt->parent->extent;
#endif
	    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 */
	
#ifdef AMIGA
	  if (progress_indicator) {
	    if ((cnt & 31) == 0 || cnt == pr_file_count) {
	      printf ("Assigning addresses for files (%d%% complete);"
	              " free mem: %lu   \r",
      		      pr_file_count ? (int) ((100*cnt)/pr_file_count) : 100,
		      AvailMem (MEMF_ANY));
	      fflush (stdout);
	    }
	    cnt++;
	  }
#endif

	  if(s_entry->size) {
	    if (deferred_count == deferred_size) {
	      deferred_size += DEFERRED_CHUNK_SIZE;
	      if (deferred)
	        deferred =
		   realloc (deferred,
			    deferred_size * sizeof (struct directory_entry*));
	      else
	        deferred =
		   malloc (deferred_size * sizeof (struct directory_entry*));
	      if (!deferred) {
	        fprintf (stderr, "cannot allocate file table - out of memory!\n");
		exit (1);
	      }
	    }
	    deferred[deferred_count++] = s_entry;
	    set_733(s_entry->isorec.extent, last_extent);
#ifndef AMIGA
	    s_entry->starting_block = last_extent;
	    add_hash(s_entry);
#endif
#ifdef AMIGA
	    if (dpnt == root &&
	        Stricmp ((UBYTE*) s_entry->name, (UBYTE*) "cdtv.tm") == 0) {
	      cdtv_trademark_extent = last_extent;
	      cdtv_trademark_length = s_entry->size;
	    }
#endif

	    last_extent += ROUND_UP(s_entry->size) >> 11;
	    if (verbose >= 2) {
	      char *whole = s_entry->filedir->whole_name;
	      char last = whole[0] ? whole[strlen(whole)-1] : 0;
	      fprintf(stderr,"%d %d %s%s%s\n", last_extent,
		      last_extent-1,
		      whole, (last == ':' || last == '/') ? "" : "/",
		      s_entry->name);
	    }
#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);
#ifndef AMIGA
	      fprintf(stderr,"Starting block is %d\n", s_entry->starting_block);
#endif
	      fprintf(stderr,"Reported file size is %d extents\n", s_entry->size);
	      exit(1);
	    };
	  } else
	    set_733(s_entry->isorec.extent, 0);
	};
      };
    };
    free (entry_array);
    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;
#ifdef AMIGA
  static unsigned long cnt = 1;
#endif

#ifdef AMIGA
  if (progress_indicator) {
    printf ("Generate directory %lu; free mem: %lu   \r",
      	    cnt, AvailMem (MEMF_ANY));
    Delay (5);
    fflush (stdout);
  }
  cnt++;
#endif

  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, ISO_REC_SIZE);
    memcpy (directory_buffer + dir_index + ISO_REC_SIZE,
	    s_entry->isorec.iso_name,
	    s_entry->isorec.name_len[0]);
    dir_index += ISO_REC_SIZE + 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;
#ifdef AMIGA
  return Stricmp ((STRPTR) (*r)->self->isorec.iso_name,
  		  (STRPTR) (*l)->self->isorec.iso_name);
#else
  return strcmp((*r)->self->isorec.iso_name, (*l)->self->isorec.iso_name);
#endif
  
}

void generate_path_tables(){
  struct directory * dpnt;
  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 *),
	  (int(*)(const void*, const void*)) 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);
    };
	    
    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++){
#ifdef AMIGA
      path_table_l[path_table_index] = ToUpper (de->isorec.iso_name[i]);
      path_table_m[path_table_index] = ToUpper (de->isorec.iso_name[i]);
#else
      path_table_l[path_table_index] = toupper (de->isorec.iso_name[i]);
      path_table_m[path_table_index] = toupper (de->isorec.iso_name[i]);
#endif
      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);
}

void preallocate_space (FILE *outfile)
{
  long oldpos = ftell (outfile);
  int numblocks = last_extent - oldpos / 2048;
  int i;
  static char empty[2048];

  fprintf (stderr, "Preallocating space for output file...\n");
  memset (empty, 0, sizeof (empty));
  for (i=0; i<numblocks; i++)
    if (!fwrite (empty, sizeof (empty), 1, outfile)) {
      fprintf (stderr, ">>>> WARNING: not enough space on output device\n");
      break;
    }
  if (fseek (outfile, oldpos, SEEK_SET) == -1) {
    fprintf (stderr, "cannot seek() on output file\n");
    perror ("");
    exit (1);
  }
}

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

  if (verbose)
    printf ("assigning file addresses\n");

  assign_file_addresses(root);
  if (progress_indicator)
    putchar ('\n');

  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));
  if (system_identifier)
    memcpy(vol_desc.system_id, system_identifier, strlen (system_identifier));
  else
#ifdef AMIGA
    memcpy(vol_desc.system_id, "AMIGA", sizeof("AMIGA"));
#else
#ifdef __QNX__
    memcpy(vol_desc.system_id, "QNX", sizeof("QNX"));
#else
    memcpy(vol_desc.system_id, "LINUX", sizeof("LINUX"));
#endif
#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_NULL(application_data);
  if (cdtv_trademark_file) {
    vol_desc.application_data[1] = 'F';
    vol_desc.application_data[2] = 'S';
    vol_desc.application_data[5] = 'T';
    vol_desc.application_data[6] = 'M';
    vol_desc.application_data[8] = 0x14;
    vol_desc.application_data[11] = cdtv_trademark_length >> 8;
    vol_desc.application_data[12] = cdtv_trademark_length & 255;
    vol_desc.application_data[13] = cdtv_trademark_extent >> 24;
    vol_desc.application_data[14] = (cdtv_trademark_extent >> 16) & 255;
    vol_desc.application_data[15] = (cdtv_trademark_extent >> 8) & 255;
    vol_desc.application_data[16] = cdtv_trademark_extent & 255;
  }

  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
  if (verbose)
    printf ("writing directories\n");

  generate_iso9660_directories(root, outfile);
  if (progress_indicator)
    putchar ('\n');

  if(extension_record) xfwrite(extension_record, 1, SECTOR_SIZE, outfile);
  
  /* Now write all of the files that we need. */
  if (verbose)
    printf ("writing files\n");

  fprintf(stderr,"Total extents being written = %d\n", last_extent);
  if (preallocate)
    preallocate_space (outfile);
  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;
}
