/* $Id: install.c,v 1.33 2002/07/21 18:23:45 richdawe Exp $ */

/*
 *  install.c - Install routines for pakke
 *  Copyright (C) 1999-2002 by Richard Dawe
 *      
 *  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 of the License, 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 "common.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>

/* Pull in libgen.h, to get basename on some platforms. */
#ifdef HAVE_LIBGEN_H
#include <libgen.h>
#endif /* HAVE_LIBGEN_H */

/* libpakke includes */
#include <libpakke/package.h>
#include <libpakke/packlist.h>
#include <libpakke/packdep.h>
#include <libpakke/dsm.h>
#include <libpakke/mft.h>
#include <libpakke/util.h>
#include <libpakke/archive.h>
#include <libpakke/backup.h>

#include "pakke.h"
#include "install.h"
#include "md5hash.h"
#include "wget.h"
#include "mirror.h"
#include "misc.h"

#ifdef HAVE_CONIO_H
#include <conio.h>
#else /* !HAVE_CONIO_H */
/* This hack needs testing! How do you flush input stream before read? */
#define getch getchar
#define putch putchar
#endif /* HAVE_CONIO_H */

/* ----------------------------
 * - perform_install_internal -
 * ---------------------------- */

/* This is a function that actually handles the installation, once
 * perform_install() and perform_install_avail() have done the base work.
 * On success 1 is returned, else 0. */

int
perform_install_internal (PACKAGE_INFO *newpackage,
			  const char *dsm,
			  PACKAGE_INFO *packages,
			  const PAKKE_INSTALL *req)
{
  PACKAGE_INFO *package;
  char db_path[PATH_MAX];
  int dup_action = DUP_QUERY;  
  int keypress = 0;
  int ret = 1; /* 1 signifies checks successful; 0 signifies error. */
  int i;

  /* For dependency checking need a list of failed deps to report to user. */
#define MAX_FAILED_DEP MAX_PACKAGES
  PACKAGE_DEP *failed_dep[MAX_FAILED_DEP + 1]; /* Leave space for NULL */
  int          failed_dep_max = MAX_FAILED_DEP;  

  /* Build the DSM database path */
  strcpy(db_path, req->root);
  addforwardslash(db_path);
  strcat(db_path, PAKKE_DB_PREFIX);
  addforwardslash(db_path);

  /* TODO: Allow command-line override. */
  dup_action = newpackage->duplicate_action;

  if (isarchive(req->name))
    strcpy(newpackage->path, req->name);

  /* Currently we only support installing binary, source, documentation
   * and virtual packages. */
  switch(newpackage->version.type) {
  case TYPE_BINARIES:
  case TYPE_SOURCES:
  case TYPE_DOCUMENTATION:
  case TYPE_VIRTUAL:
    break;
		
  default:
    dief("Package type '%s' not supported yet for installs!",
	 package_type_string(&newpackage->version));
    break;
  }

  /* - Check all the dependencies - */

  /* Build xrefs between the new package and the package list, to check
   * that its dependencies will be satisfied. */
  package_xref(packages, newpackage);  

  /* Initialise failed_dep array */
  for (i = 0; i <= failed_dep_max; i++) { failed_dep[i] = NULL; }

  /* Requires */
  if (!package_check_deps(newpackage, PACKAGE_DEP_REQUIRES,
			  failed_dep, failed_dep_max)) {
    printf("- Required packages not found:\n");
    for (i = 0; failed_dep[i] != NULL; i++) {
      printf("%s\n", package_dep_string(failed_dep[i]));
    }
    ret = 0;
  }

  /* Depends on */
  if (!package_check_deps(newpackage, PACKAGE_DEP_DEPENDS_ON,
			  failed_dep, failed_dep_max)) {
    printf("- Depends on packages not found - "
	   "functionality may be impaired (continuing):\n");
    for (i = 0; failed_dep[i] != NULL; i++) {
      printf("%s\n", package_dep_string(failed_dep[i]));
    }
  }

  /* Conflicts with */
  if (!package_check_deps(newpackage, PACKAGE_DEP_CONFLICTS_WITH,
			  failed_dep, failed_dep_max)) {
    printf("- Conflicts with packages found:\n");
    for (i = 0; failed_dep[i] != NULL; i++) {
      printf("%s\n", package_dep_string(failed_dep[i]));
    }
    ret = 0;
  }

  /* Replaces */
  if (!package_check_deps(newpackage, PACKAGE_DEP_REPLACES,
			  failed_dep, failed_dep_max)) {
    printf("- Replaces packages found (use --upgrade instead):\n");
    for (i = 0; failed_dep[i] != NULL; i++) {
      printf("%s\n", package_dep_string(failed_dep[i]));
    }
    ret = 0;
  }

  /* Install before */
  if (!package_check_deps(newpackage, PACKAGE_DEP_INSTALL_BEFORE,
			  failed_dep, failed_dep_max)) {
    printf("- Install before packages found:\n");
    for (i = 0; failed_dep[i] != NULL; i++) {
      printf("%s\n", package_dep_string(failed_dep[i]));
    }
    ret = 0;
  }

  /* Install after */
  if (!package_check_deps(newpackage,
			  PACKAGE_DEP_INSTALL_AFTER,
			  failed_dep, failed_dep_max)) {
    printf("- Install after packages found (no error, continuing):\n");
    for (i = 0; failed_dep[i] != NULL; i++) {
      printf("%s\n", package_dep_string(failed_dep[i]));
    }
  }

  /* Does the package already exist in the database? */
  for (package = packages;
       package != NULL;
       package = (PACKAGE_INFO *) package->q_forw) {
    if (package_namecmp(package->name, newpackage->name) != 0)
      continue;    

    if (package_vercmp(&package->version, &newpackage->version) != 0)
      continue;

    printf("Package %s %s is already installed!\n",
	   newpackage->name, package_version_string(&newpackage->version));
    ret = 0;
  }

  /* Check all archives are available */
  if (   !install_get_and_check_archives(newpackage,
					 req->verbosity,
					 req->interactive,
					 req->name,
					 req->download_prefix,
					 (const char **) req->zip_path)
      && (req->mod != IM_TEST)) {
    /* Abort later */
    ret = 0;
  }

  /* Show the install warning - confirmation required (see below). */
  if (newpackage->install_warning != NULL)
    printf("*** WARNING ***\n%s\n", newpackage->install_warning);

  /* Install can't occur because of bad archives or failed dependencies. */
  if (ret != 1) {
    warn("Problems found with package archives or dependencies - aborting\n");
    return(0);
  }  

  /* Are we in test mode? If so, bail out now. */
  if (req->mod == IM_TEST) {    
    info("Install in test-mode complete - exiting\n");
    return(ret);
  }  

  /* Get confirmation, if there was an install warning. */
  if (newpackage->install_warning != NULL) {
    printf("[c]ontinue or [a]bort? ");

    if (req->interactive) {
      /* Interactive */
      keypress = 0;
      while (keypress == 0) {
	keypress = getch();

	switch(keypress) {
	case 'c': case 'C': case 'a': case 'A':
	  break;

	default:
	  putch('\a');
	  keypress = 0;
	  break;
	}
      }
    } else {
      /* Non-interactive => abort by default */
      keypress = 'a';
    }

    printf("%c\n", keypress);

    if ((keypress == 'a') || (keypress == 'A')) {
      printf("Installation of %s %s aborted by user\n",
	     newpackage->name, package_version_string(&newpackage->version));

      /* Bail */
      return(0);
    }
  }

  /* - Install - */

  if (!install_extract_archive(newpackage,
			       req->verbosity,
			       req->interactive,
			       dup_action,
			       req->name,
			       req->prefix,
			       req->backup_prefix,
			       (const char **) req->zip_path)) {
    printf("Installation of %s %s failed\n",
	     newpackage->name, package_version_string(&newpackage->version));

    /* Bail */
    return(0);
  }

  /* Write DSM into db directory. */
  if (!install_dsm(newpackage, db_path, dsm)) {
    /* TODO: Better error msg */
    /* TODO: Recover better! */
    dief("Unable to write DSM for %s %s!",
	 newpackage->name, package_version_string(&newpackage->version));
  } else {
    printf(". Created DSM for %s %s\n",
	   newpackage->name, package_version_string(&newpackage->version));
  }
	
  /* Generate MD5 hashes for the installed files. */
  if (md5hash(newpackage, db_path, req->prefix,
	      (const char **) req->mft_path) == -1) {
    warnf("Error generating MD5 hashes for %s %s",
	  newpackage->name, package_version_string(&newpackage->version));
    perror("install");
  } else {
    printf(". Created MD5 hashes for files from %s %s\n",
	   newpackage->name, package_version_string(&newpackage->version));
  }

  /* Create entries in the info directory for any info files. */
  if (!install_info_entries(newpackage, db_path, req->prefix)) {
    warnf("Error creating info directory entries for %s %s",
	  newpackage->name, package_version_string(&newpackage->version));
  }
	
  /* Done! */
  return(ret);
}

/* -------------------
 * - perform_install -
 * ------------------- */

int
perform_install (const PAKKE_INSTALL *req)
{
  PACKAGE_INFO   *packages = NULL;
  PACKAGE_INFO    newpackage;
  DSM_FILE_ERROR *dfe      = NULL; /* List of err's in main DSM parse. */
  DSM_ERROR      *de       = NULL; /* List of err's in new DSM parse. */
  char           *dsm      = NULL; /* DSM read into memory */
  int             ret;

  /* Get a list of installed packages */
  packages = dsm_load_all((const char **) req->dsm_path, NULL, &dfe);
  packages = ver_load_all((const char **) req->mft_path, packages);
	
  packages = packlist_dedupe(packages);
  packlist_xref(packages);
	
  if (packages == NULL)
    warn("No installed packages found");

  /* Display DSM errors. */
  if (dsm_file_errors_fatal(dfe))
    warn("Parsing failed for some DSM(s) in installed database");
	
  show_dsm_file_errors(dfe, req->verbosity);
  dsm_free_file_error_list(dfe);
  dfe = NULL;  

  /* Parse the package's DSM */
  if (dsm_get_and_parse(req->name, &newpackage, &dsm, &de) != DSM_OK) {
    /* If there were any parsing errors, display them. */
    show_dsm_errors(req->name, de, req->verbosity);

    /* Tidy up */
    free(dsm);
    packlist_free(packages);

    dsm_free_error_list(de);
    de = NULL;

    /* Can't continue without a valid DSM. */
    die("Unable to parse DSM!");
  }

  /* Now install */
  ret = perform_install_internal(&newpackage, dsm, packages, req);

  /* Tidy up */
  free(dsm);
  packlist_free(packages);

  return(ret == 1 ? EXIT_SUCCESS : EXIT_FAILURE);
}

/* -------------------------
 * - perform_install_avail -
 * ------------------------- */

int
perform_install_avail (const PAKKE_INSTALL *req)
{
  PACKAGE_INFO    *packages       = NULL;
  PACKAGE_INFO    *packages_avail = NULL;
  PACKAGE_INFO    *newpackage     = NULL;
  PACKAGE_INFO   **matched        = NULL;
  DSM_FILE_ERROR  *dfe            = NULL; /* List of err's in main DSM parse */
  char             temp_file[PATH_MAX];
  char            *dsm_file;
  char            *dsm            = NULL; /* DSM read into memory */  
  int i;
  int ret;

  /* Get a list of installed packages */
  packages = dsm_load_all((const char **) req->dsm_path, NULL, &dfe);
  packages = ver_load_all((const char **) req->mft_path, packages);
	
  packages = packlist_dedupe(packages);
  packlist_xref(packages);
	
  if (packages == NULL)
    warn("No installed packages found");

  /* Display DSM errors. */
  if (dsm_file_errors_fatal(dfe))
    warn("Parsing failed for some DSM(s) in installed database");

  show_dsm_file_errors(dfe, req->verbosity);
  dsm_free_file_error_list(dfe);
  dfe = NULL;

  /* Get a list of available packages */
  packages_avail = dsm_load_all((const char **) req->dsm_path_avail,
				NULL, &dfe);
	
  packages_avail = packlist_dedupe(packages_avail);
	
  if (packages_avail == NULL)
    warn("No available packages found");

  /* Display DSM errors. */
  if (dsm_file_errors_fatal(dfe))
    warn("Parsing failed for some DSM(s) in available database");

  show_dsm_file_errors(dfe, req->verbosity);
  dsm_free_file_error_list(dfe);
  dfe = NULL;

  /* Find the package in the package list. */  
  matched = packlist_find(packages_avail, req->name,
			  PACKLIST_FIND_SIMPLE|PACKLIST_FIND_USER);

  if ((matched == NULL) || (*matched == NULL)) {
    /* No matches */
    warnf("No available package was found matching '%s'!", req->name);

    /* Tidy up */
    packlist_free(packages);
    packlist_free(packages_avail);
    return(EXIT_FAILURE);
  }

  /* List matched packages */
  for (i = 0; matched[i] != NULL; i++) {
    printf(". Matched package: %s %s\n",
	   matched[i]->name, package_version_string(&matched[i]->version));
  }

  /* If there's more than one match, abort. */
  if (matched[1] != NULL) {
    warnf("More than one package matched '%s' - please be more specific!",
	  req->name);

    /* Tidy up */
    free(matched);
    packlist_free(packages);
    packlist_free(packages_avail);
    return(EXIT_FAILURE);
  }

  newpackage = matched[0];

  /* Get the new package's DSM */
  strcpy(temp_file, newpackage->dsm_name);
  strcat(temp_file, ".dsm");

  dsm_file = find_in_paths(temp_file, (const char **) req->dsm_path_avail, 0);

  if (dsm_file == NULL) {
    /* Tidy up */
    free(matched);
    packlist_free(packages);
    packlist_free(packages_avail);

    dief("Unable to find DSM file '%s'", temp_file);
  }

  dsm = read_text_file_to_memory(dsm_file);

  if (dsm == NULL) {
    /* Tidy up */
    free(matched);
    packlist_free(packages);
    packlist_free(packages_avail);

    dief("Unable to read DSM file '%s'", dsm_file);
  }

  /* Now install */
  ret = perform_install_internal(newpackage, dsm, packages, req);

  /* Tidy up */
  free(dsm);
  free(matched);  
  packlist_free(packages);
  packlist_free(packages_avail);

  return(ret == 1 ? EXIT_SUCCESS : EXIT_FAILURE);
}

/* ----------------------------------
 * - install_get_and_check_archives -
 * ---------------------------------- */

int
install_get_and_check_archives (PACKAGE_INFO *package,
				const int verbosity,
				const int interactive,
				const char *req_name,
				const char *download_prefix,
				const char **archive_paths)
{
  char **archives       = NULL;
  char   copy_req_name[PATH_MAX];
  char  *my_archives[2] = { NULL, NULL };
  int    n_archives     = 0; /* Number of archives */
  char  *a_name         = NULL;  
  int    keypress       = 0;
  int    ret            = 1; /* Succeed by default */
  int    i;

  /*
   * Should we download package archives?
   *
   * -1 = an error occurred last time we tried to download or the user
   *      doesn't want to download;
   *  0 = haven't asked the user yet;
   *  1 = download.
   */
  int download = 0;

  /* If we've pointed at a package archive and it only has one archive,
   * then assume we can install from this archive. */
  int single_archive = 0;

  /* TODO: tar-gzip, etc. */
		
  /* No zips => no install! */
  if ( (package->zip == NULL) || (package->zip[0] == NULL) ) {
    /* NB: Virtual packages are installed by copying the DSM. */
    if (   (package->version.type == TYPE_BINARIES)
	&& (package->version.type == TYPE_SOURCES)
        && (package->version.type == TYPE_DOCUMENTATION) ) {
      warn("No archives specified in package DSM!");
      ret = 0;
    }
  }

  /* Count the number of archives */
  for (n_archives = 0; package->zip[n_archives] != NULL; n_archives++) {;}

  /* If there's one archive for this DSM and the req_name is an archive,
   * use that instead of the listed archive. */
  if ((n_archives == 1) && isarchive(req_name)) {
    /* basename() may trample on string, so take & use a copy of req_name. */
    strcpy(copy_req_name, req_name);

    /* NB: Path from req_name searched */
    single_archive = 1;
    my_archives[0] = basename(copy_req_name);
    archives       = my_archives;
  } else {
    archives = package->zip;
  }

  /* Now check each archive */
  for (i = 0; i < n_archives; i++) {
    /* Barf here if it isn't an archive. */
    if (!isarchive(archives[i])) {      
      warnf("'%s' is not an archive", archives[i]); 
      ret = 0;
      continue;
    }

    /* Find the archive */
    a_name = find_archive(archives[i], req_name, archive_paths);

    /* Try to download via HTTP/FTP, if the archive isn't found. */
    if (   (a_name == NULL) && !single_archive
	&& (download == 0) && (download_prefix != NULL)) {
      /* Ask if we should download package via HTTP/FTP. */
      printf("Unable to locate archive '%s' for package %s %s\n",
	     archives[i],
	     package->name,
	     package_version_string(&package->version));

      printf("Download archives for package %s %s - [y]es or [n]o? ",
	     package->name, package_version_string(&package->version));

      if (interactive) {
	/* Interactive */
	keypress = 0;
	while (keypress == 0) {
	  keypress = getch();

	  switch(keypress) {
	  case 'y': case 'Y':
	    download = 1;
	    break;

	  case 'n': case 'N':
	    download = -1; /* Don't ask again */
	    break;

	  default:
	    putch('\a');
	    keypress = 0;
	    break;
	  }
	}
      } else {
	/* Non-interactive => don't download anything */
	keypress = 'n';
      }

      printf("%c\n", keypress);

      if (download == 1) {
	if (!install_download_archives(package, verbosity,
				       archive_paths,
				       download_prefix)) {
	  warnf("Unable to download archives for package %s %s",
		package->name,
		package_version_string(&package->version));
	} else {
	  /* Now try to find the archive again. */
	  a_name = find_archive(archives[i], req_name, archive_paths);
	}
      }
    }

    if (a_name == NULL) {
      warnf("Unable to locate archive '%s'!", archives[i]);
      ret = 0;
      continue;
    }

    /* Check integrity of archive */
    if (verbosity != V_NORMAL)
      printf(". Checking integrity of archive '%s'...", a_name);

    if (!archive_check_integrity(a_name)) {
      printf("\n");
      warnf("Archive '%s' has been corrupted!", a_name);
      ret = 0;
      continue;
    }

    /* OK */
    printf("OK\n");
  }

  return(ret);
}

/* ---------------
 * - do_download -
 * --------------- */

/* This is a helper function for install_download_archives. */

static int
do_download (PACKAGE_INFO *package,
	     const int verbosity,
	     const char *download_prefix,
	     const char *archive_path)
{
  mode_t mode = 0;
  char url[PATH_MAX], targetdir[PATH_MAX], olddir[PATH_MAX];
  int ret, i;

  /* TODO: tar-gzip, etc. */

  for (i = 0; package->zip[i] != NULL; i++) {
    /* Build the URL */
    strcpy(url, archive_path);
    addforwardslash(url);
    strcat(url, package->simtelnet_path);
    addforwardslash(url);
    strcat(url, package->zip[i]);
    forwardslashify(url);

    /* Create the target directory. */
    strcpy(targetdir, download_prefix);
    addforwardslash(targetdir);
    strcat(targetdir, package->simtelnet_path);
    addforwardslash(targetdir);
    forwardslashify(targetdir);

    mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
    ret = recursive_mkdir(targetdir, mode);
    if ((ret != 0) && (errno != EEXIST)) {
      perror("pakke");
      return(0);
    }

    /* Save the directory, change into the target directory, download
     * the file and change back to the old directory. */
    if (getcwd(olddir, sizeof(olddir)) == NULL) {
      perror("pakke");
      return(0);
    }

    if (chdir(targetdir) != 0) {
      perror("pakke");
      return(0);
    }

    ret = wget_get(url, package->zip[i], verbosity);

    if (chdir(olddir) != 0) {
      perror("pakke");
      ret = 0;
    }

    if (!ret)
      return(0);
  }

  return(1);
}

/* -----------------------------
 * - install_download_archives -
 * ----------------------------- */

int
install_download_archives (PACKAGE_INFO *package,
			   const int verbosity,
			   const char **archive_paths,
			   const char *download_prefix)
{
  const char *mirror = mirror_mirror();
  int         ok     = 0; /* Fail by default */
  int         i;

  /* Look through the archive paths for any URLs. If any are found,
   * try to download from them. Otherwise use one of the mirrors. */

  /* - Archive paths - */

  for (i = 0; archive_paths[i] != NULL; i++) {
    if (!isurl(archive_paths[i]))
      continue;

    /* If we fail to download from this URL, try the next. */
    if (do_download(package, verbosity, download_prefix, archive_paths[i])) {
      ok = 1;
      break;
    } else {
      warnf("Unable to download archives for package %s %s from '%s'",
	    package->name,
	    package_version_string(&package->version),
	    archive_paths[i]);
    }
  }

  if (ok)
    return(ok);

  /* - Mirrors - */

  if (do_download(package, verbosity, download_prefix, mirror)) {
    ok = 1;
  } else {
    warnf("Unable to download archives for package %s %s from '%s'",
	  package->name,
	  package_version_string(&package->version),
	  mirror);
  }

  return(ok);
}

/* ---------------------------
 * - install_extract_archive -
 * --------------------------- */

static int
free_toc (char **toc)
{
  int i;

  for (i = 0; toc[i] != NULL; i++) { free(toc[i]); }
  free(toc);
  return(1);
}

int
install_extract_archive (PACKAGE_INFO *package,
			 const int verbosity,
			 const int interactive,
			 const int dup_action,
			 const char *req_name,
			 const char *prefix,
			 const char *backup_prefix,
			 const char **zip_path)
{
  char          filename[PATH_MAX];  /* Temporary-use file name   */
  char          filename2[PATH_MAX]; /* Temporary-use file name 2 */  
  char        **archives       = NULL;
  char          copy_req_name[PATH_MAX];
  char         *my_archives[2] = { NULL, NULL };
  int           n_archives     = 0; /* Number of archives */
  char         *a_name         = NULL;
  char        **a_toc          = NULL;
  const char   *backup_path    = NULL;
  int           action         = DUP_QUERY;
  int           extract        = 0;
  int           keypress       = 0;
  mode_t        mode           = 0;
  struct stat   s;
  int           ret         = 0;
  char         *p           = NULL;
  int           i           = 0;
  int           j           = 0;

  /* Generate name for the backup path */
  backup_path = backup_get_path(package, backup_prefix);

  /* Make sure the prefix exists */
  printf(". Creating prefix directory '%s'...", prefix);

  mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
  ret = recursive_mkdir(prefix, mode);

  if ((ret != 0) && (errno != EEXIST)) {
    printf("failed\n");
    perror("pakke");
    return(0);
  }

  printf("done\n");

  /* TODO: tar-gzip, etc. */

  /* Count the number of archives */
  for (n_archives = 0; package->zip[n_archives] != NULL; n_archives++) {;}

  /* If there's one archive for this DSM and the req_name is an archive,
   * use that instead of the listed archive. */
  if ((n_archives == 1) && isarchive(req_name)) {
    /* basename() may trample on string, so take & use a copy of req_name. */
    strcpy(copy_req_name, req_name);

    /* NB: Path from req_name searched */
    my_archives[0] = basename(copy_req_name);
    archives       = my_archives;
  } else {
    archives = package->zip;
  }

  for (i = 0; archives[i] != NULL; i++) {
    /* Barf here if it isn't an archive. */
    if (!isarchive(archives[i])) {
      warnf("'%s' is not an archive", archives[i]);
      return(0);
    }
		
    /* Find the archive */
    a_name = find_archive(archives[i], req_name, zip_path);

    if (a_name == NULL) {
      printf("Unable to locate archive '%s'!\n", archives[i]);
      /* TODO: Make all actions logged => can undo */      
      
      return(0);
    }

    printf(". Installing from archive '%s'\n", a_name);

    /* Get the table of contents */
    a_toc = archive_get_toc(a_name);
    if (a_toc == NULL) {
      warnf("Archive '%s' has no entries", a_name);
      continue;
    }

    /* Extract all files */
    for (j = 0; a_toc[j] != NULL; j++) {
      strcpy(filename, prefix);
      strcat(filename, a_toc[j]);

      /* Directory? */
      if (ends_with_slash(filename)) {
	/* Create the directory */
	ret = recursive_mkdir(filename, mode);

	if (ret != 0) {
	  /* Unable to create directory */
	  warnf("Unable to create directory '%s'", filename);	  

	  /* Bail out */
	  free_toc(a_toc), a_toc = NULL;
	  return(0);
	}
				
	if (verbosity != V_NORMAL)
	  printf(". Created directory '%s'\n", filename);
      }

      /* Check the directory stem and create if it doesn't exist. Some zip
       * files seem to contain the directories after the files. Some don't
       * even seem to have directory names. Extracting the file will fail
       * if the directory doesn't exist. */
      strcpy(filename2, filename);
      p = strrchr(filename2, '/');
      if (p != NULL) {
	*p = '\0';
	ret = stat(filename2, &s);

	if ((ret == -1) && (errno = ENOENT)) {
	  ret = recursive_mkdir(filename2, mode);
	  if (ret != 0) {
	    /* Unable to create directory */
	    warnf("Unable to create directory '%s'", filename2);

	    /* Bail out */
	    free_toc(a_toc), a_toc = NULL;
	    return(0);
	  }

	  if (verbosity != V_NORMAL)
	    printf(". Created directory '%s'\n", filename2);
	} else if ((ret == -1) || !S_ISDIR(s.st_mode)) {
	} /* Otherwise we need do nothing. */
      }

      /* If this archive entry is a directory, move onto the next entry. */
      if (strrchr(filename, '/') == filename + strlen(filename) - 1)
	continue;

      /* If the file already exists, behaviour is controlled by
       * the duplicate-action setting. */
      extract = 1;

      if (access(filename, F_OK | R_OK) == 0) {	
	if (dup_action == DUP_QUERY) {
	  /* Prompt user */
	  printf("'%s' already exists\n", filename);
	  printf("[r]eplace, [b]ackup, [s]kip or [a]bort? ");

	  if (interactive) {
	    /* Interactive */
	    keypress = 0;
	    while (keypress == 0) {
	      keypress = getch();

	      switch(keypress) {
	      case 'r': case 'R': case 'b': case 'B':
	      case 's': case 'S': case 'a': case 'A':
		break;

	      default:
		putch('\a');
		keypress = 0;
		break;
	      }
	    }
	  } else {
	    /* Non-interactive => always backup */
	    keypress = 'b';
	  }

	  printf("%c\n", keypress);

	  switch(keypress) {
	  case 'r': case 'R':
	    action = DUP_REPLACE;
	    break;

	  default:
	  case 'b': case 'B':
	    action = DUP_BACKUP;
	    break;

	  case 's': case 'S':
	    action = DUP_SKIP;
	    break;

	  case 'a': case 'A':
	    action = DUP_ABORT;
	    break;
	  }
	} else {
	  action = dup_action;
	}

	/* Display message for action on duplicate. */
	switch(action) {
	case DUP_REPLACE:
	  printf("'%s' already exists - overwriting\n", filename);
	  break;

	default:
	case DUP_BACKUP:
	  ret = backup_file(a_toc[j], prefix, backup_path);
	  if (ret == 0) {
	    printf(". Backed up: '%s/%s' -> '%s/%s'\n",
		   prefix, a_toc[j], backup_path, a_toc[j]);
	  } else {
	    warnf("Unable to back up '%s/%s' - ABORTING", prefix, a_toc[j]);

	    /* Bail out */
	    free_toc(a_toc), a_toc = NULL;
	    return(0);
	  }
	  break;

	case DUP_KEEP:
	case DUP_SKIP:
	  extract = 0;
	  printf("'%s' already exists - skipping\n", filename);
	  break;

	case DUP_ABORT:
	  printf("Installation of %s %s aborted by user\n",
		 package->name, package_version_string(&package->version));

	  /* Bail out */
	  free_toc(a_toc), a_toc = NULL;
	  return(0);
	  break;
	}
      }

      /* Extract file */
      if (extract) {
	if (!archive_extract_file(a_name, a_toc[j], filename)) {
	  /* Unable to extract file! */
	  warnf("Unable to create file '%s' - ABORTING", filename);

	  /* Bail out */
	  free_toc(a_toc), a_toc = NULL;
	  return(0);
	}
			
	if (verbosity != V_NORMAL)
	  printf(". Created file '%s'\n", filename);
      }
    }

    /* Free the TOC */
    free_toc(a_toc), a_toc = NULL;
  }

  return(1);
}

/* ---------------
 * - install_dsm -
 * --------------- */

int
install_dsm (PACKAGE_INFO *package, const char *db_path, const char *dsm)
{
  char  filename[PATH_MAX]; /* Temporary-use file name */
  FILE *fp  = NULL;
  int   ret = 1; /* Succeed by default */

  strcpy(filename, db_path);
  strcat(filename, package->dsm_name);
  if (!isdsm(filename))
    strcat(filename, ".dsm");

  fp = fopen(filename, "wt");
  if (fp == NULL)
    return(0);

  if (fwrite(dsm, strlen(dsm), 1, fp) <= 0) {
    /* Failed */
    ret = 0;
  }

  fclose(fp);
  return(ret);
}

/* ---------------------------------
 * - install_info_entries_internal -
 * --------------------------------- */

/*
 * Create or delete an entry in the info directory file for any info files
 * belonging to the package. This relies on MD5 hashes existing for
 * the package. 'install_flag' should be non-zero if you want to create
 * an entry; conversely it should be zero to delete an entry.
 *
 * On success, 1 is returned. On failure, 0 is returned.
 */

int
install_info_entries_internal (PACKAGE_INFO *package, const char *db_path,
			       const char *prefix, int install_flag)
{
  char             cmd_install_info[PATH_MAX * 4]; /* 3 paths + some space */
  char             info_prefix[PATH_MAX]; /* Path to search for info files */
  const char       info_subdir[] = "info/";
  md5hash_entry  **list          = NULL;
  char            *filename      = NULL;
  char            *p             = NULL;
  FILE            *fp            = NULL;
  int              matched;
  int              i;

  /* Build info_prefix */
  strcpy(info_prefix, prefix);
  addforwardslash(info_prefix);
  strcat(info_prefix, info_subdir);
  forwardslashify(info_prefix);

  /* Get all the MD5 hash values calculated previously. */
  list = md5hash_get(package, db_path);
  if (list == NULL) {
    /* Failed */
    return(0);
  }

  /* Search for files in info_prefix */
  for (i = 0; list[i] != NULL; i++) {    
    filename = list[i]->filename;
    if (!(strstr(filename, info_prefix) == filename))
      continue;

    /* Find file extension */
    p = strrchr(filename, '.');
    if (p == NULL)
      continue;
    p++;
      
    /* Match info filename extensions */
    matched  = (strcmp(p, "inf") == 0);
    matched |= (strcmp(p, "info") == 0);

    if (!matched)
      continue;

    /* Now run install-info for this info file */
    sprintf(cmd_install_info,
	    "install-info %s--info-file=%s --info-dir=%s",
	    !install_flag ? "--delete " : "", filename, info_prefix);    

    /*
     * Adding or removing info entries like this is bad, because
     * the info files may not follow the DJGPP info directory
     * section names. We may end with a completely trashed and
     * unhelpful info directory. Instead, it is better to rely on
     * the DSM, because that will add it to the appropriate section
     * in the info directory.
     */
#ifdef INSTALL_INFO_LIKE_THIS_IS_BAD_MMMMMMMKAY_BUT_DO_IT_ANYWAY
    {
      int ret;

      /* Run the appropriate install-info command. */
      infof("Executing: %s", cmd_install_info);

      ret = system(cmd_install_info);
      if (ret)
	warnf("install-info returned error code %d", ret);
    }
#else
    /* Open the batch file, if necessary. */
    if (fp == NULL) {
      const size_t tmpfilelen = L_tmpnam + 4; /* Leave room for '.bat'. */
      char         tmpfile[tmpfilelen];

      assert(tmpfilelen <= (PATH_MAX + 1));

      tmpnam(tmpfile);
      strcat(tmpfile, ".bat");
      forwardslashify(tmpfile);

      fp = fopen(tmpfile, "wt");
      if (fp == NULL) {
	warnf("Could not open '%s'", tmpfile);
      } else {
	infof("Creating batch file to %s info directory entries...",
	      install_flag ? "add" : "remove");
	infof("Please see batch file '%s' for install-info commands.",
	      tmpfile);
      }
    }

    /* Display the command-line and write it to a batch file. */
    puts(cmd_install_info);

    if (fp != NULL)
      fprintf(fp, "%s\n", cmd_install_info);
#endif
  }

  /* Tidy up */
  if (fp != NULL)
    fclose(fp);

  md5hash_entries_free(list);
  free(list), list = NULL;

  return(1);
}

/* Wrappers for install_info_entries_internal() */

/* Create */
int
install_info_entries (PACKAGE_INFO *package, const char *db_path,
		      const char *prefix)
{
  return(install_info_entries_internal(package, db_path, prefix, 1));
}

/* Delete */
int
uninstall_info_entries (PACKAGE_INFO *package, const char *db_path,
			const char *prefix)
{
  return(install_info_entries_internal(package, db_path, prefix, 0));
}
