/* $Id: install.c,v 1.24 2002/03/10 21:25:50 richdawe Exp $ */

/*
 *  install.c - Install routines for zippo
 *  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>

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

#include "zippo.h"
#include "install.h"
#include "md5hash.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 ZIPPO_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, ZIPPO_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 archives are available */
  if (   !install_check_archives(newpackage, req->verbosity, req->name,
				 (const char **) req->zip_path)
      && (req->mod != IM_TEST)) {
    /* Abort later */
    ret = 0;
  }

  /* - 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 (strcmp(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;
  }

  /* 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? ");

    keypress = 0;
    while (keypress == 0) {
      keypress = getch();

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

      default:
	putch('\a');
	keypress = 0;
	break;
      }
    }

    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, 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 ZIPPO_INSTALL *req)
{
  PACKAGE_INFO   *packages = NULL;
  PACKAGE_INFO    newpackage;
  DSM_FILE_ERROR *dfe      = NULL; /* List of err's in main DSM parse. */
  DSM_FILE_ERROR *ndfe     = NULL;
  DSM_ERROR      *de       = NULL; /* List of err's in new DSM parse. */
  DSM_ERROR      *nde      = NULL;
  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);
	
  packlist_dedupe(packages);
  packlist_xref(packages);
	
  if (packages == NULL)
    warn("No installed packages found");

  /* Display DSM errors. */
  if (dfe != NULL)
    warn("Parsing failed for some DSM(s) in installed database");
	
  for (ndfe = dfe; ndfe != NULL; ndfe = ndfe->q_forw) {
    dsm_perror(ndfe->name, ndfe->de);
  }
	
  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. */
    for (nde = de; nde != NULL; nde = (DSM_ERROR *) nde->q_forw) {
      dsm_perror(req->name, nde);
    }

    /* 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 ZIPPO_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 */
  DSM_FILE_ERROR  *ndfe           = NULL;
  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);
	
  packlist_dedupe(packages);
  packlist_xref(packages);
	
  if (packages == NULL)
    warn("No installed packages found");

  /* Display DSM errors. */
  if (dfe != NULL)
    warn("Parsing failed for some DSM(s) in installed database");
	
  for (ndfe = dfe; ndfe != NULL; ndfe = ndfe->q_forw) {
    dsm_perror(ndfe->name, ndfe->de);
  }
	
  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);
	
  packlist_dedupe(packages_avail);
	
  if (packages_avail == NULL)
    warn("No available packages found");

  /* Display DSM errors. */
  if (dfe != NULL)
    warn("Parsing failed for some DSM(s) in available database");
	
  for (ndfe = dfe; ndfe != NULL; ndfe = ndfe->q_forw) {
    dsm_perror(ndfe->name, ndfe->de);
  }
	
  dsm_free_file_error_list(dfe);

  /* 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);

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

    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)
    dief("Unable to find DSM file '%s'", temp_file);

  dsm = read_text_file_to_memory(dsm_file);

  if (dsm == NULL)
    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_check_archives -
 * -------------------------- */

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

  /* 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)) {
    p = strrchr(req_name, '/');
    if (p != NULL) {
      p++;
      my_archives[0] = (char *) p;
      archives = my_archives;
    } else {
      archives = package->zip;
    }
  } 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,
			  (const char **) 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);
}

/* ---------------------------
 * - 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 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         *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("zippo");
    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)) {
    p = strrchr(req_name, '/');
    if (p != NULL) {
      p++;
      my_archives[0] = (char *) p;
      archives = my_archives;
    } else {
      archives = package->zip;
    }
  } 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? ");

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

	  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              ret;
  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
    /* 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));
}
