/*----------------------------------------------------------------------*/
/*			LHarc Add Command				*/
/*		This is part of LHarc UNIX Archiver Driver		*/
/*									*/
/*		Copyright(C) MCMLXXXIX  Yooichi.Tagawa			*/
/*									*/
/*  V0.00  Original				1988.05.23  Y.Tagawa	*/
/*  V1.00  Fixed				1989.09.22  Y.Tagawa	*/
/*  V1.02  Bug fix				1990.01.19  Y.Tagawa	*/
/*  V0.03  LHa for UNIX				1991.12.05  M.Oki	*/
/*----------------------------------------------------------------------*/

#include "lharc.h"

extern int encode_lzhuf ();
extern int encode_stored_crc ();

static char new_archive_name_buffer [ FILENAME_LENGTH ];
static char *new_archive_name;
FILE *temporary_fp = NULL;

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




static void
add_one (fp, nafp, hdr)
     FILE *fp, *nafp;
     LzHeader *hdr;
{
  long header_pos, next_pos, org_pos, data_pos;
  long v_original_size, v_packed_size;

  reading_filename = hdr->name;
  writting_filename = temporary_name;

  if (!fp && generic_format)	/* [generic] doesn't need directory info. */
    return;
  header_pos = ftell (nafp);
  write_header (nafp, hdr);	/* DUMMY */

  if (hdr->original_size == 0)	/* empty file or directory */
      return;			/* previous write_header is not DUMMY. (^_^) */

  org_pos = ftell (fp);
  data_pos = ftell (nafp);

  hdr->crc = encode_lzhuf (fp, nafp, hdr->original_size,
			   &v_original_size, &v_packed_size, hdr->name, hdr->method);

  if (v_packed_size < v_original_size)
    {
      next_pos = ftell (nafp);
    }
  else
    {				/* retry by stored method */
      fseek (fp, org_pos, SEEK_SET);
      fseek (nafp, data_pos, SEEK_SET);
      hdr->crc = encode_stored_crc (fp, nafp, hdr->original_size,
				    &v_original_size, &v_packed_size);
      fflush (nafp);
      next_pos = ftell (nafp);
#ifndef NOFTRUNCATE
      ftruncate (fileno (nafp), next_pos);
#endif
      bcopy (LZHUFF0_METHOD, hdr->method, METHOD_TYPE_STRAGE);
    }
  hdr->original_size = v_original_size;
  hdr->packed_size = v_packed_size;
  fseek (nafp, header_pos, SEEK_SET);
  write_header (nafp, hdr);
  fseek (nafp, next_pos, SEEK_SET);
}


FILE *
append_it (name, oafp, nafp)
     char *name;
     FILE *oafp, *nafp;
{
  LzHeader ahdr, hdr;
  FILE *fp;
  long old_header;
  int cmp;
  int filec;
  char **filev;
  int i;
  struct stat stbuf;
  boolean directory;

  if (stat (name, &stbuf) < 0)
    {
      error ("cannot access", name); /* See cleaning_files, Why? */
      return oafp;
    }

  directory = is_directory (&stbuf);

  init_header (name, &stbuf, &hdr);

  if (!directory && !noexec)
    fp = xfopen (name, READ_BINARY);
  else
    fp = NULL;

  while (oafp)
    {
      old_header = ftell (oafp);
      if (!get_header (oafp, &ahdr))
	{
	  fclose (oafp);
	  oafp = NULL;
	  break;
	}
      else
	{
	  cmp = STRING_COMPARE (ahdr.name, hdr.name);
	  if (cmp < 0)
	    {			/* SKIP */
	      /* copy old to new */
	      if (!noexec)
		{
		  fseek (oafp, old_header, SEEK_SET);
		  copy_old_one (oafp, nafp, &ahdr);
		}
	      else
		fseek (oafp, ahdr.packed_size, SEEK_CUR);
	    }
	  else if (cmp == 0)
	    {			/* REPLACE */
	      /* drop old archive's */
	      fseek (oafp, ahdr.packed_size, SEEK_CUR);
	      break;
	    }
	  else			/* cmp > 0, INSERT */
	    {
	      fseek (oafp, old_header, SEEK_SET);
	      break;
	    }
	}
    }

  if (update_if_newer)
    {
      if (!oafp ||		/* not in archive */
	  cmp > 0 ||		/* // */
	  ahdr.unix_last_modified_stamp < /* newer than archive's */
	  hdr.unix_last_modified_stamp)
	{
	  if (noexec)
	    printf ("ADD %s\n", name);
	  else
	    add_one (fp, nafp, &hdr);
	}
      else /* cmp == 0 */
	{			/* copy old to new */
	  if (!noexec)
	    {
	      fseek (oafp, old_header, SEEK_SET);
	      copy_old_one (oafp, nafp, &ahdr);
	    }
	}
    }
  else
    {
      if (!oafp || cmp > 0)	/* not in archive or dropped */
	{
	  if (noexec)
	    printf ("ADD %s\n", name);
	  else
	    add_one (fp, nafp, &hdr);
	}
      else /* cmp == 0 */	/* replace */
	{
	  if (noexec)
	    printf ("REPLACE\n");
	  else
	    add_one (fp, nafp, &hdr);
	}
    }

  if (!directory)
    {
      if (!noexec)
	fclose (fp);
    }
  else
    {			/* recurcive call */
      if (find_files (name, &filec, &filev))
	{
	  for (i = 0; i < filec; i ++)
	    oafp = append_it (filev[i], oafp, nafp);
	  free_files (filec, filev);
	}
    }
  return oafp;
}

static void
find_update_files (oafp)
     FILE *oafp;		/* old archive */
{
  char name[FILENAME_LENGTH];
  struct string_pool sp;
  LzHeader hdr;
  long pos;
  struct stat stbuf;
  int len;

  pos = ftell (oafp);

  init_sp (&sp);
  while (get_header (oafp, &hdr))
    {
      if ((hdr.unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_REGULAR)
	{
	  if (stat (hdr.name, &stbuf) >= 0) /* exist ? */
	    add_sp (&sp, hdr.name, strlen (hdr.name) + 1);
	}
      else if ((hdr.unix_mode & UNIX_FILE_TYPEMASK) == UNIX_FILE_DIRECTORY)
	{
	  strcpy (name, hdr.name);
	  len = strlen (name);
	  if (len > 0 && name[len - 1] == '/')
	    name[--len] = '\0'; /* strip tail '/' */
	  if (stat (name, &stbuf) >= 0) /* exist ? */
	    add_sp (&sp, name, len+1);
	}
      fseek (oafp, hdr.packed_size, SEEK_CUR);
    }

  fseek (oafp, pos, SEEK_SET);

  finish_sp (&sp, &cmd_filec, &cmd_filev);
}

static void
delete (oafp, nafp)
     FILE *oafp, *nafp;
{
  LzHeader ahdr;
  long old_header_pos;

  old_header_pos = ftell (oafp);
  while (get_header (oafp, &ahdr))
    {
      if (need_file (ahdr.name))
	{			/* skip */
	  fseek (oafp, ahdr.packed_size, SEEK_CUR);
	  if (noexec)
	    printf ("DELETE %s\n", ahdr.name);
	  else if (verbose)
	    printf ("Delete %s\n", ahdr.name);
	}
      else
	{			/* copy */
	  if (noexec)
	    {
	      fseek (oafp, ahdr.packed_size, SEEK_CUR);
	    }
	  else
	    {
	      fseek (oafp, old_header_pos, SEEK_SET);
	      copy_old_one (oafp, nafp, &ahdr);
	    }
	}
      old_header_pos = ftell (oafp);
    }
  return;
}


/*----------------------------------------------------------------------*/
/*									*/
/*----------------------------------------------------------------------*/
static FILE *
build_temporary_file ()
{
  int old_umask;
  FILE *afp;

  build_temporary_name ();
  signal (SIGINT, interrupt);
  signal (SIGHUP, interrupt);

  old_umask = umask (077);
  afp = xfopen (temporary_name, WRITE_BINARY);
  remove_temporary_at_error = TRUE;
  temporary_fp = afp;
  umask (old_umask);

  return afp;
}

static void
build_backup_file ()
{
  
  build_backup_name (backup_archive_name, archive_name);
  if (!noexec)
    {
      signal (SIGINT, SIG_IGN);
      signal (SIGHUP, SIG_IGN);
      if (rename (archive_name, backup_archive_name) < 0)
		fatal_error (archive_name);
      recover_archive_when_interrupt = TRUE;
      signal (SIGINT, interrupt);
      signal (SIGHUP, interrupt);
    }
}

static void
report_archive_name_if_different ()
{
  if (!quiet && new_archive_name == new_archive_name_buffer)
    {
      /* warning at old archive is SFX */
      printf ("New archive file is \"%s\"\n", new_archive_name);
    }
}

#ifdef TMP_FILENAME_TEMPLATE
void
temporary_to_new_archive_file (new_archive_size)
     long new_archive_size;
{
  FILE *oafp, *nafp;

  oafp = xfopen (temporary_name, READ_BINARY);
  if (!strcmp(new_archive_name, "-"))
    {
      nafp = stdout;
      writting_filename = "starndard output";
    }
  else
    {
      nafp = xfopen (new_archive_name, WRITE_BINARY);
      writting_filename = archive_name;
    }
  reading_filename = temporary_name;
  copyfile (oafp, nafp, new_archive_size, 0);
  if (nafp != stdout) fclose (nafp);
  fclose (oafp);

  recover_archive_when_interrupt = FALSE;
  unlink (temporary_name);

  remove_temporary_at_error = FALSE;
}
#else
temporary_to_new_archive_file (new_archive_size)
long new_archive_size;
{
  char *p;
  p = (char *)rindex(new_archive_name,'/');
  p = p ? p+1 : new_archive_name;
  unlink ( new_archive_name );
  if ( rename ( temporary_name , p )<0 ) {
    fprintf(stderr, "Can't rename temporary_name '%s'\n", new_archive_name);
    exit(1);
  }
}
#endif

static void
set_archive_file_mode ()
{
  int umask_value;
  struct stat stbuf;

  if (archive_file_gid < 0)
    {
      umask (umask_value = umask (0));
      archive_file_mode = (~umask_value) & 0666; /* rw-rw-rw- */
      if (stat (".", &stbuf) >= 0)
	archive_file_gid = stbuf.st_gid;
    }
  if (archive_file_gid >= 0)
    chown (new_archive_name, getuid (), archive_file_gid);

  chmod (new_archive_name, archive_file_mode);
}


/*----------------------------------------------------------------------*/
/*		REMOVE FILE/DIRECTORY					*/
/*----------------------------------------------------------------------*/

static void remove_files ();

static void
remove_one (name)
     char *name;
{
  struct stat stbuf;
  int filec;
  char **filev;

  if (stat (name, &stbuf) < 0)
    {
      warning ("Cannot access", name);
    }
  else if (is_directory (&stbuf))
    {
      if (find_files (name, &filec, &filev))
	{
	  remove_files (filec, filev);
	  free_files (filec, filev);
	}
      else
	warning ("Cannot open directory", name);

      if (noexec)
	printf ("REMOVE DIRECTORY %s\n", name);
      else if (rmdir (name) < 0)
	warning ("Cannot remove directory", name);
      else if (verbose)
	printf ("Removed %s.\n", name);
    }
  else if (is_regularfile (&stbuf))
    {
      if (noexec)
	printf ("REMOVE FILE %s.\n", name);
      else if (unlink (name) < 0)
	warning ("Cannot remove", name);
      else if (verbose)
	printf ("Removed %s.\n", name);
    }
  else
    {
      error ("Cannot remove (not a file or directory)", name);
    }
}

static void
remove_files (filec, filev)
     int filec;
     char **filev;
{
  int i;

  for (i = 0; i < filec; i++)
    remove_one (filev[i]);
}

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

void
cmd_add ()
{
  LzHeader ahdr;
  FILE *oafp, *nafp;
  int i;
  long old_header;
  boolean old_archive_exist;
  long new_archive_size;

  /* exit if no operation */
  if (!update_if_newer && cmd_filec == 0)
    {
      error ("No files given in argument, do nothing.", "");
      return;
    }

  /* open old archive if exist */
  if ((oafp = open_old_archive ()) == NULL)
    old_archive_exist = FALSE;
  else
    old_archive_exist = TRUE;

  if (update_if_newer && cmd_filec == 0 && !oafp)
    fatal_error (archive_name); /* exit if cannot execute automatic update */
  errno = 0;

  if (new_archive && old_archive_exist)
    {
      fclose (oafp);
      oafp = NULL;
    }

  if (oafp && archive_is_msdos_sfx1 (archive_name))
    {
      skip_msdos_sfx1_code (oafp);
      build_standard_archive_name (new_archive_name_buffer, archive_name);
      new_archive_name = new_archive_name_buffer;
    }
  else
    {
      new_archive_name = archive_name;
    }

  /* build temporary file */
  if (!noexec)
    nafp = build_temporary_file ();

  /* find needed files when automatic update */
  if (update_if_newer && cmd_filec == 0)
    find_update_files (oafp);

  /* build new archive file */
  /* cleaning arguments */
  cleaning_files (&cmd_filec, &cmd_filev);
  if (cmd_filec == 0)
    {
      if (oafp)
	fclose (oafp);
      if  (!noexec)
	fclose (nafp);
      return;
    }

  for (i = 0; i < cmd_filec; i ++)
    oafp = append_it (cmd_filev[i], oafp, nafp);
  if (oafp)
    {
      old_header = ftell (oafp);
      while (get_header (oafp, &ahdr))
	{
	  if (noexec)
	    fseek (oafp, ahdr.packed_size, SEEK_CUR);
	  else
	    {
	      fseek (oafp, old_header, SEEK_SET);
	      copy_old_one (oafp, nafp, &ahdr);
	    }
	  old_header = ftell (oafp);
	}
      fclose (oafp);
    }
  if (!noexec)
    {
      write_archive_tail (nafp);
      new_archive_size = ftell (nafp);
      fclose (nafp);
    }

  /* build backup archive file */
  if (old_archive_exist)
    build_backup_file ();

  report_archive_name_if_different ();

  /* copy temporary file to new archive file */
  if (!noexec && (!strcmp(new_archive_name, "-") ||
		  rename (temporary_name, new_archive_name) < 0))
    temporary_to_new_archive_file (new_archive_size);

  /* set new archive file mode/group */
  set_archive_file_mode ();

  /* remove archived files */
  if (delete_after_append)
    remove_files (cmd_filec, cmd_filev);

  return;
}


void
cmd_delete ()
{
  FILE *oafp, *nafp;
  long new_archive_size;

  /* open old archive if exist */
  if ((oafp = open_old_archive ()) == NULL)
    fatal_error (archive_name);
  errno = 0;

  /* exit if no operation */
  if (cmd_filec == 0)
    {
      fclose (oafp);
      warning ("No files given in argument, do nothing.", "");
      return;
    }

  if (archive_is_msdos_sfx1 (archive_name))
    {
      skip_msdos_sfx1_code (oafp);
      build_standard_archive_name (new_archive_name_buffer, archive_name);
      new_archive_name = new_archive_name_buffer;
    }
  else
    {
      new_archive_name = archive_name;
    }

  /* build temporary file */
  if (!noexec)
    nafp = build_temporary_file ();

  /* build new archive file */
  delete (oafp, nafp);
  fclose (oafp);

  if (!noexec)
    {
      write_archive_tail (nafp);
      new_archive_size = ftell (nafp);
      fclose (nafp);
    }

  /* build backup archive file */
  build_backup_file ();

  report_archive_name_if_different ();

  /* copy temporary file to new archive file */
  if (!noexec && rename (temporary_name, new_archive_name) < 0)
    temporary_to_new_archive_file (new_archive_size);

  /* set new archive file mode/group */
  set_archive_file_mode ();

  return;
}
