/* $Id: zippo.c,v 1.32 2002/03/10 21:16:56 richdawe Exp $ */

/*
 *  zippo.c - DJGPP package manager, with an RPM-like syntax
 *  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.
 */

/*
 * TODO:
 *
 * . --hash to display hashmarks
 * . --percent
 * . FTP & HTTP download, proxies, --ftp-proxy => HTTP firewall here,
 *   --ftp-port
 * . Query format? (--queryformat)
 * . Logging?
 * . --scripts query option for listing scripts?
 */

#include "common.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <dirent.h>
#include <limits.h>
#include <regex.h>
#include <sys/types.h>
#include <sys/vfs.h>

/* libzippo includes */
#include <libzippo/dsm.h>
#include <libzippo/mft.h>
#include <libzippo/util.h>
#include <libzippo/archive.h>
#include <libzippo/version.h>

/* popt includes */
#include <popt.h>

#include "zippo.h"
#include "rcfile.h"
#include "initdb.h"
#include "syncdb.h"
#include "query.h"
#include "install.h"
#include "uinstall.h"
#include "upgrade.h"
#include "chkinteg.h"
#include "wget.h"
#include "version.h"

/* Compile time information */
#include "comptime.h"

/* Copyright info */
#define COPYRIGHT_NAME        "zippo"
#define COPYRIGHT_DESCRIPTION "DJGPP Package Manager"
#define COPYRIGHT_AUTHOR      "Richard Dawe"
#define COPYRIGHT_EMAIL       "rich@phekda.freeserve.co.uk"
#define COPYRIGHT_YEAR        "1999-2002"

#include <zlib.h>
#ifndef ZLIB_COPYRIGHT
#define ZLIB_COPYRIGHT	\
"Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler"
#endif

extern char unz_copyright[];

/* Default DSMs for different platforms */
extern char *platform_dsm_table[];
extern char *platform_dsm_name_table[];

/* Paths & filenames */
char zipporc[PATH_MAX] = "";

char *cmd_install[]         = { "--install", NULL };
char *cmd_check_integrity[] = {"--check-integrity", NULL };

poptContext optCon;

struct poptOption options_table[] = {
  { "quiet",            'Q',  POPT_ARG_NONE,   NULL, 'Q',
    "quiet mode", "Turns quite mode on" },
  { "verbose",          'v',  POPT_ARG_NONE,   NULL, 'v',
    "verbose mode", "Displays information verbosey" },
    /*POPT_AUTOHELP*/
    
/* TODO: Uncomment the above and comment out the next, if we want to enable
 * popt's default help, rather than using the help function way below. */
  { "help",             'h',  POPT_ARG_NONE,   NULL, 'h', NULL, NULL },
  { "version",          'V',  POPT_ARG_NONE,   NULL, 'V', NULL, NULL },
  { "license",          'L',  POPT_ARG_NONE,   NULL, 'L', NULL, NULL },
  { "query",            'q',  POPT_ARG_NONE,   NULL, 'q', NULL, NULL },
  { "installed",        'a',  POPT_ARG_NONE,   NULL, 'a', NULL, NULL },
  { "available",        'A',  POPT_ARG_NONE,   NULL, 'A', NULL, NULL },
  { "package",          'p',  POPT_ARG_NONE,   NULL, 'p', NULL, NULL },
  { "long-info",        'i',  POPT_ARG_NONE,   NULL, 'i', NULL, NULL },
  { "longinfo",         'i',  POPT_ARG_NONE,   NULL, 'i', NULL, NULL },
  { "requires",         'R',  POPT_ARG_NONE,   NULL, 'R', NULL, NULL },
  { "uninstall",        'e',  POPT_ARG_NONE,   NULL, 'e', NULL, NULL },
  { "upgrade",          'U',  POPT_ARG_NONE,   NULL, 'U', NULL, NULL },
  { "test",             'n',  POPT_ARG_NONE,   NULL, 'n', NULL, NULL },

/*
 * Long form only options start here: The numbers 130 and upwards that appear
 * in the fourth element of some entries tell the big switch in `main' how
 * to process those options. 
 */
#define ZO_ROOT 130
  { "root",             '\0', POPT_ARG_STRING, NULL, ZO_ROOT,
    NULL, NULL },
#define ZO_PREFIX 131
/* TODO: When prefix is properly supported, re-enable this. */
#if 0
  { "prefix" ,          '\0', POPT_ARG_STRING, NULL, ZO_PREFIX,
    NULL, NULL },
#endif /* 0 */
#define ZO_INITDB 132
  { "initdb",           '\0', POPT_ARG_NONE,   NULL, ZO_INITDB,
    NULL, NULL },
#define ZO_SYNCDB 133
  { "syncdb",           '\0', POPT_ARG_NONE,   NULL, ZO_SYNCDB,
    NULL, NULL },
#define ZO_WITH_PLATFORM 134
  { "with-platform",    '\0', POPT_ARG_STRING, NULL, ZO_WITH_PLATFORM,
    NULL, NULL },
#define ZO_WITH_ZIPPO 135
  { "with-zippo",       '\0', POPT_ARG_NONE,   NULL, ZO_WITH_ZIPPO,
    NULL, NULL },
#define ZO_DEPENDS_ON 136
  { "depends-on",       '\0', POPT_ARG_NONE,   NULL, ZO_DEPENDS_ON,
    NULL, NULL },
#define ZO_CONFLICTS_WITH 137
  { "conflicts-with",   '\0', POPT_ARG_NONE,   NULL, ZO_CONFLICTS_WITH,
    "List packages that conflict", NULL},
#define ZO_REPLACES 138
  { "replaces",         '\0', POPT_ARG_NONE,   NULL, ZO_REPLACES,
    NULL, NULL },
#define ZO_PROVIDES 139
  { "provides",         '\0', POPT_ARG_NONE,   NULL, ZO_PROVIDES,
    NULL, NULL }, 
#define ZO_INSTALL_BEFORE 140
  { "install-before",   '\0', POPT_ARG_NONE,   NULL, ZO_INSTALL_BEFORE,
    NULL, NULL },
#define ZO_INSTALL_AFTER 141
  { "install-after",    '\0', POPT_ARG_NONE,   NULL, ZO_INSTALL_AFTER,
    NULL, NULL },
#define ZO_ALL_DEPS 142
  { "all-deps",	        '\0', POPT_ARG_NONE,   NULL, ZO_ALL_DEPS,
    NULL, NULL },
  { "all-dependencies",	'\0', POPT_ARG_NONE,   NULL, ZO_ALL_DEPS,
    NULL, NULL },
#define ZO_CHECK_INTEGRITY 143
  { "check-integrity",   '\0', POPT_ARG_NONE,  NULL, ZO_CHECK_INTEGRITY,
    NULL, NULL },
#define ZO_RCFILE 144
  { "rcfile",           '\0', POPT_ARG_STRING, NULL, ZO_RCFILE,
    " - Configuration file name", NULL },
#define ZO_SHORTINFO 145
  { "shortinfo",        '\0', POPT_ARG_NONE,   NULL, ZO_SHORTINFO,
    NULL, NULL },
  { "short-info",       '\0', POPT_ARG_NONE,   NULL, ZO_SHORTINFO,
    NULL, NULL },
#define ZO_LONGINFO 146
  { "longinfo",	        '\0', POPT_ARG_NONE,   NULL, ZO_LONGINFO,
    NULL, NULL },
  { "long-info",        '\0', POPT_ARG_NONE,   NULL, ZO_LONGINFO,
    NULL, NULL },
/* I had to change --install to return something other than 'i' */
#define ZO_INSTALL 148
  { "install",          '\0', POPT_ARG_NONE,   NULL, ZO_INSTALL,
    NULL, NULL },
#define ZO_CHECKSIG 149
  { "checksig",	        '\0', POPT_ARG_NONE,   NULL, ZO_CHECKSIG,
    NULL, NULL },
#define ZO_CHANGELOG 150
  { "changelog",        '\0', POPT_ARG_NONE,   NULL, ZO_CHANGELOG,
    NULL, NULL },
#define ZO_PRE_INSTALL_README 151
  { "pre-install-readme", '\0', POPT_ARG_NONE, NULL, ZO_PRE_INSTALL_README,
    NULL, NULL },
#define ZO_POST_INSTALL_README 152
  { "post-install-readme", '\0', POPT_ARG_NONE, NULL, ZO_POST_INSTALL_README,
    NULL, NULL },
#define ZO_PRE_UNINSTALL_README 153
  { "pre-uninstall-readme", '\0', POPT_ARG_NONE, NULL, ZO_PRE_UNINSTALL_README,
    NULL, NULL },
#define ZO_POST_UNINSTALL_README 154
  { "post-uninstall-readme", '\0', POPT_ARG_NONE, NULL,
    ZO_POST_UNINSTALL_README, NULL, NULL },
#define ZO_INSTALL_WARNING 155
  { "install-warning", '\0', POPT_ARG_NONE, NULL, ZO_INSTALL_WARNING,
    NULL, NULL },

  { NULL,               'f',  POPT_ARG_STRING, NULL, 'f', NULL, NULL },
  { NULL,               'l',  POPT_ARG_NONE,   NULL, 'l', NULL, NULL },
  { NULL,               '\0', 0,               NULL, 0}
};

/* Pwetty messages */
static void show_copyright (int license_hint);
static void show_version   (void);
static void show_help      (void);
static void show_license   (void);

/* Utility functions */
static int check_paths (char **list, char *desc);
static int __inline__ is_query (int op);

/* --------
 * - main -
 * -------- */

int
main (int argc, char *argv[])
{	
  int    op              = OP_NONE;	/* Operation              */
  int    qm              = QM_NONE;	/* Query modifier         */
  int    im              = IM_NONE;	/* Install modifier       */
  int    um              = UM_NONE;	/* Uninstall modifier     */
  int    upm             = UPM_NONE;    /* Upgrade modifier       */
  int    verbosity       = V_NORMAL;
  char * root            = NULL;		/* DJGPP root directory   */
  char * prefix          = NULL;		/* Install prefix         */
  char * backup_prefix   = NULL;                /* Backup prefix          */
  char **dsm_path        = NULL;
  char **dsm_path_avail  = NULL;		/* Available packages     */
  char **mft_path        = NULL;
  char **zip_path        = NULL;		/* .zip search path       */
  char **tar_gzip_path   = NULL;		/* .tar.gz search path    */
  char **tar_bzip2_path  = NULL;		/* .tar.bz2 search path   */
  char **http_proxies    = NULL;
  char **ftp_proxies     = NULL;
  char * name            = NULL;
  char * qname           = NULL;
  char **platforms       = NULL;		/* Platforms to be init'd */
  int    platform_count  = 0;
  char * zippo           = NULL;		/* zippo binary name      */
  char * p               = NULL;
  char * q               = NULL;
  int    ret             = EXIT_FAILURE;
  int    i, j;
       
  /* For popt */
  int opt;
  const char *optarg;

  /* Request structures */
  ZIPPO_INITDB          req_initdb;
  ZIPPO_SYNCDB          req_syncdb;
  ZIPPO_QUERY           req_query;
  ZIPPO_INSTALL         req_install;
  ZIPPO_UNINSTALL       req_uninstall;
  ZIPPO_UPGRADE         req_upgrade;
  ZIPPO_CHECK_INTEGRITY req_check_integrity;
  
  /* MSS bizness */
#ifdef MSS	
#ifdef DEBUG
  /* Are we running on a read-only file system? If so, generate a
   * warning and skip MSS logging, for debugging purposes. */
  {
    struct statfs sfbuf;
    long bytes_free = 0;
    char cwd[PATH_MAX];
    extern int MSS_DO_LOG_TO_STDOUT;

    if (getwd(cwd) != NULL) {
      if (statfs(cwd, &sfbuf) == 0)
	bytes_free = sfbuf.f_bsize * sfbuf.f_bfree;
			
      if ((bytes_free == 0) || access(cwd, W_OK)) {
	warnf("No free space on disk => not using MSS");

	/* This is an ugly hack to persuade MSS to work on read-only or
	 * full filesystems (e.g. CDs). This seems more reasonable than just
	 * not working at all. */
	MSS_DO_LOG_TO_STDOUT = 1;
	MSS_STARTUP;
      } else {
	MSS_STARTUP;
      }			
    }
  }
#else	/* !DEBUG */
  /* Non-debug => warn if we've built with MSS (which should be
   * unlikely, but still possible). */
#warning "Why are you building zippo with MSS but no debug?"
  MSS_STARTUP;
#endif	/* DEBUG */	
#endif	/* MSS */

  /* Set up a popt context for parsing the command-line options. popt support
   * was added by Kalum (thanks!). */
  optCon = poptGetContext("zippo", argc, argv, options_table, 0);

  while( (opt = poptGetNextOpt(optCon)) > 0 ){
    optarg = poptGetOptArg(optCon);

    switch(opt) {
    case 'Q':
      verbosity = V_QUIET;
      break;
				
    case 'v':
      verbosity = V_VERBOSE;
      break;
			
    case 'h':
      op = OP_HELP;
      break;
			
    case 'V':
      op = OP_VERSION;
      break;

    case 'L':
      op = OP_LICENSE;
      break;
			
    case 'q':      			
      op = OP_QUERY;
      /* TODO: Support querying multiple packages */
      /* Can't specify package name twice */
      if (name != NULL)
	die("Package already specified!");
      break;
			
    case 'a': /* All installed packages */
      if (op == OP_QUERY) {
	op = OP_QUERY_ALL;
      } else {
	die("All packages option must follow query option!");
      }  
      break;
			
    case 'A': /* All available packages */
      if (op == OP_QUERY) {  
	op = OP_QUERY_AVAILABLE;
      } else if (op == OP_INSTALL) {
	op = OP_INSTALL_AVAILABLE;
      } else {
	die("All packages option must follow query option!");
      }
      break;
		  
    case 'p':
      if (op == OP_QUERY) {
	/* Query file rather than installed package. */
	op = OP_QUERY_FILE;
      } else {
	die("Package option only valid in query mode!");
      } 
      break;
			
    case 'l' :
      if (!is_query(op))
	die("File list option only valid in query mode!");
			
      qm = QM_LISTFILES;
      break;

    case 'R':
      if (!is_query(op))
	die("Requires list option only valid in query mode!");
			
      qm = QM_REQUIRES;	       
      break;
			
    case 'f':
      if (!is_query(op))
	die("File owner option only valid in query mode!");

      qname = strdup(optarg);
      forwardslashify(qname);
      qm = QM_HASFILE;	     
      break;
      
    case 'i':			
      /*
       * This is tricky, because we have to handle both install and longinfo
       * options. If we have no operation yet, then it's an install. If we
       * have an operation, we should be querying - generate an error if
       * we're not.
       */
      if (op == OP_NONE) {
	/* Kalum: add the --install to the args then the install will be
	 * processed. */
	poptStuffArgs(optCon, cmd_install);
	break;
      } else {			
	/*  longinfo */
	if (!is_query(op))
	  die("Info option only valid in query mode!");
	else			
	  qm = QM_LONGINFO;
      }
			
      break;

      /* Uninstall */
    case 'e':
      op = OP_UNINSTALL;
      break;

    case 'U':
      op = OP_UPGRADE;
      break;
      
    case 'n':
      switch(op) {
      case OP_INSTALL:           im  = IM_TEST; break;
      case OP_INSTALL_AVAILABLE: im  = IM_TEST; break;
      case OP_UNINSTALL:         um  = UM_TEST; break;
      case OP_UPGRADE:           upm = UPM_TEST; break;
				
      default:
	die("Test option only valid in install/uninstall mode!");
	break;
      }
      break;
			
    case ZO_ROOT: /* --root */
      root = strdupnx(optarg, 1);
      forwardslashify(root);
      addforwardslash(root);
      break;
			
    case ZO_PREFIX:  /* --prefix */	     
      prefix = strdupnx(optarg, 1);
      forwardslashify(prefix);
      addforwardslash(prefix);
      break;
			
    case ZO_INITDB: /* --initdb */
      op = OP_INITDB;
      break;
			
    case ZO_SYNCDB: /* --syncdb */
      op = OP_SYNCDB;
      break;
			
    case ZO_WITH_PLATFORM: /* --with-platform=<platforms> */
      if (optarg) {
	/* Default platform DSMs should be added to the database. */
	p = strdup(optarg);
                      
	for (platform_count = 0, q = strtok(p, ",");
	     q != NULL;
	     q = strtok(NULL, ",")) {
	  if (*q != '\0')
	    platform_count++;
	}

	free(p);
		    
	platforms = malloc(sizeof(char *) * (platform_count + 1));
		    
	if (platforms != NULL) {
	  p = strdup(optarg);
	  platforms[platform_count] = NULL;
			 
	  for (j = 0, q = strtok(p, ",");
	       q != NULL;
	       j++, q = strtok(NULL, ",")) {
	    if (*q != '\0')
	      platforms[j] = strdup(q);
	  }

	  free(p);
	}
      }
      break;
			
    case ZO_WITH_ZIPPO: /* --with-zippo */
      zippo = argv[0];
      break;
			
    case ZO_DEPENDS_ON: /* --depends-on */
      if (!is_query(op))
	die("Depends-on list option only valid in query mode!");
			
      qm = QM_DEPENDS_ON;
      break;
			
    case ZO_CONFLICTS_WITH: /* --conflicts-with */
      if (!is_query(op))
	die("Conflicts-with list option only valid in query mode!");
			
      qm = QM_CONFLICTS_WITH;
      break;
			
    case ZO_REPLACES: /* --replaces */
      if (!is_query(op))
	die("Replaces list option only valid in query mode!");
			
      qm = QM_REPLACES;
      break;
			
    case ZO_PROVIDES: /* --provides */
      if (!is_query(op))
	die("Provides list option only valid in query mode!");
			
      qm = QM_PROVIDES;
      break;
			
    case ZO_INSTALL_BEFORE: /* --install-before */
      if (!is_query(op))
	die("Install-before list option only valid in query mode!");
			
      qm = QM_INSTALL_BEFORE;
      break;
			
    case ZO_INSTALL_AFTER: /* --install-after */
      if (!is_query(op))
	die("Install-after list option only valid in query mode!");
			
      qm = QM_INSTALL_AFTER;
      break;
			
    case ZO_ALL_DEPS: /* --all-deps, --all-dependencies */
      if (!is_query(op))
	die("All dependencies list option only valid in query mode!");
			
      qm = QM_ALLDEPS;
      break;
			
    case ZO_CHECK_INTEGRITY: /* --check-integrity */
      op = OP_CHECK_INTEGRITY;
      break;
			
    case ZO_RCFILE: /* --rcfile */
      strcpy(zipporc, optarg);	   
      break;
		  
    case ZO_SHORTINFO: /* --short-info, --shortinfo */
      if (!is_query(op))
	die("Short info option only valid in query mode!");
			
      qm = QM_SHORTINFO;
      break;

    case ZO_LONGINFO: /* --long-info, --longinfo */
      if (!is_query(op))
	die("Long info option only valid in query mode!");
			
      qm = QM_LONGINFO;
      break;

    case ZO_INSTALL: /* --install */
      op = OP_INSTALL;
      break;
			
    case ZO_CHECKSIG: /* --checksig */
      warn("--checksig option not supported - using --check-integrity");
      poptStuffArgs(optCon, cmd_check_integrity);
      break;

    case ZO_CHANGELOG:
      if (!is_query(op))
	die("Changelog option only valid in query mode!");

      qm = QM_CHANGELOG;
      break;

    case ZO_PRE_INSTALL_README:
      if (!is_query(op))
	die("Pre-install readme option only valid in query mode!");

      qm = QM_PRE_INSTALL_README;
      break;

    case ZO_POST_INSTALL_README:
      if (!is_query(op))
	die("Post-install readme option only valid in query mode!");

      qm = QM_POST_INSTALL_README;
      break;

    case ZO_PRE_UNINSTALL_README:
      if (!is_query(op))
	die("Pre-uninstall readme option only valid in query mode!");

      qm = QM_PRE_UNINSTALL_README;
      break;

    case ZO_POST_UNINSTALL_README:
      if (!is_query(op))
	die("Post-install readme option only valid in query mode!");

      qm = QM_POST_UNINSTALL_README;
      break;

    case ZO_INSTALL_WARNING:
      if (!is_query(op))
	die("Install warning option only valid in query mode!");

      qm = QM_INSTALL_WARNING;
      break;
			
    default:
      die("Please type `zippo --help' for more information.\n");
      break;
    }
  }  

  if (opt < -1 ) {
    dief("zippo: bad argument %s: %s\n",
	 poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 
	 poptStrerror(opt));
  }

  /* Handle help options */
  switch(op) {
  case OP_HELP:
    show_copyright(1);
    show_help();
		
    return(EXIT_SUCCESS);
    break;

  case OP_LICENSE:
    show_copyright(0);
    show_license();

    return(EXIT_SUCCESS);
    break;
		
  default:
    break;
  }
	
  /* Default paths if none specified */
  if (root == NULL) {
    root = strdupnx(getenv("DJDIR"), 1);
    forwardslashify(root);
    addforwardslash(root);
  }

  if (root == NULL) {
    /* Warn if no root specified */
    root = strdupnx(DEFAULT_DJGPP_PATH, 1);
    forwardslashify(root);
    addforwardslash(root);

    warn("Could not find DJGPP directory - using default path '"
	 DEFAULT_DJGPP_PATH "' instead!");
  }  

  if (prefix == NULL) {
    prefix = strdupnx(root, 1);
    addforwardslash(prefix);

    /* Don't warn here about assumed prefix. */
  }

  if (backup_prefix == NULL) {
    /* TODO: Make backup_prefix user-configurable */
    {
      const char BACKUP_SUBDIR[] = "backup/";
      size_t     extra_len       = 0;

      extra_len     = strlen(BACKUP_SUBDIR);
      backup_prefix = strdupnx(prefix, extra_len);
      strcat(backup_prefix, BACKUP_SUBDIR);

       /* Don't warn here about assumed backup_prefix. */
    }
  }  

  if (verbosity != V_NORMAL) {
    printf(". DJGPP path '%s'\n"
	   ". Installation prefix '%s'\n"
	   ". Backup prefix '%s'\n",
	   root, prefix, backup_prefix);
  }

  /* If the database is to be initialised, do it now. */
  if (op == OP_INITDB) {
    req_initdb.op        = op;
    req_initdb.mod       = 0;
    req_initdb.verbosity = verbosity;
    req_initdb.root      = root;
    req_initdb.prefix    = prefix;
    req_initdb.platforms = platforms;
    req_initdb.zippo     = zippo;
    return(perform_initdb(&req_initdb));
  }

  /* Read the configuration file. */
  if (strlen(zipporc) == 0) {
    sprintf(zipporc, "%s%s%s", root, ZIPPO_SHARE_PREFIX, RCFILE_DEFAULT);
  }

  if (!rcfile_init(zipporc, root, prefix)) {
    if (op == OP_VERSION) {
      /* Only warn, when displaying versions. */
      warn("Unable to read configuration file!");
    } else {
      die("Unable to read configuration file!");
    }
  }

  if (verbosity != V_NORMAL)
    printf(". Reading configuration from '%s'\n", zipporc);

  /* TODO: Finish parser */
  /* Test new parser */
  if (!rcfile_parse()) {
    if (op == OP_VERSION) {
      /* Only warn, when displaying versions. */
      warnf("Failed to parse configuration file '%s'", zipporc);
    } else {
      dief("Failed to parse configuration file '%s'", zipporc);
    }
  }

  /* Get the DSM & manifest paths, FTP & HTTP proxies */
  dsm_path       = rcfile_get_resources(RC_INSTALLED_DSM);
  mft_path       = rcfile_get_resources(RC_INSTALLED_MFT);
  dsm_path_avail = rcfile_get_resources(RC_AVAILABLE_DSM);
  zip_path       = rcfile_get_resources(RC_ZIP);
  tar_gzip_path  = rcfile_get_resources(RC_TAR_GZIP);
  tar_bzip2_path = rcfile_get_resources(RC_TAR_BZIP2);
  http_proxies   = rcfile_get_resources(RC_HTTP_PROXY);
  ftp_proxies    = rcfile_get_resources(RC_FTP_PROXY);

  if (verbosity != V_NORMAL) {
    for (i = 0; (dsm_path != NULL) && (dsm_path[i] != NULL); i++) {
      printf(". Installed DSM path [%d]: '%s'\n",
	     i + 1, dsm_path[i]);
    }

    for (i = 0; (mft_path != NULL) && (mft_path[i] != NULL); i++) {
      printf(". Installed manifest path [%d]: '%s'\n",
	     i + 1, mft_path[i]);
    }

    for (i = 0; (dsm_path_avail != NULL) && (dsm_path_avail[i] != NULL); i++) {
      printf(". Available DSM path [%d]: '%s'\n",
	     i + 1, dsm_path_avail[i]);
    }

    for (i = 0; (zip_path != NULL) && (zip_path[i] != NULL); i++) {
      printf(". Available zip path [%d]: '%s'\n",
	     i + 1, zip_path[i]);
    }

    for (i = 0; (tar_gzip_path != NULL) && (tar_gzip_path[i] != NULL); i++) {
      printf(". Available gzip'd tar path [%d]: '%s'\n",
	     i + 1, tar_gzip_path[i]);
    }

    for (i = 0; (tar_bzip2_path != NULL) && (tar_bzip2_path[i] != NULL); i++) {
      printf(". Available bzip2'd tar path [%d]: '%s'\n",
	     i + 1, tar_bzip2_path[i]);
    }

    for (i = 0; (http_proxies != NULL) && (http_proxies[i] != NULL); i++) {
      printf(". HTTP proxy [%d]: '%s'\n",
	     i + 1, http_proxies[i]);
    }

    for (i = 0; (ftp_proxies != NULL) && (ftp_proxies[i] != NULL); i++) {
      printf(". FTP proxy [%d]: '%s'\n",
	     i + 1, ftp_proxies[i]);
    }
  }

  /* Check that the specified paths exist. */
  check_paths(dsm_path, "Installed DSM");
  check_paths(mft_path, "Installed Manifest");
  check_paths(dsm_path_avail, "Available DSM");
  check_paths(zip_path, "Available zip");
  check_paths(tar_gzip_path, "Available gzip'd tar");
  check_paths(tar_bzip2_path, "Available bzip2'd tar");

  /* Set up wget handling, now the configuration file is parsed. */
  if (!wget_init(root, http_proxies, ftp_proxies))
    die("wget_init() failed");

  /* Display version information */
  if (op == OP_VERSION) {
    show_copyright(1);
    show_version();

    return(EXIT_SUCCESS);
  }
	
  /* Get the package name */
  /* TODO: Support actions on multiple packages */
  if (poptPeekArg(optCon)) {
    name = strdup(poptGetArg(optCon));
    forwardslashify(name);
  }
	
  /* Check we have a package name */
  if (   (   (op == OP_QUERY)
	  || (op == OP_QUERY_FILE)
	  || (op == OP_INSTALL)
	  || (op == OP_INSTALL_AVAILABLE)
	  || (op == OP_UNINSTALL)
	  || (op == OP_UPGRADE)
  	  || (op == OP_CHECK_INTEGRITY) )
	 && (name == NULL) ) {
    switch(op) {
    default:	               fprintf(stderr, "Unknown");         break;
    case OP_QUERY:             fprintf(stderr, "Query");           break;
    case OP_QUERY_FILE:        fprintf(stderr, "Query");           break;
    case OP_INSTALL:           fprintf(stderr, "Install");         break;
    case OP_INSTALL_AVAILABLE: fprintf(stderr, "Install");         break;
    case OP_UNINSTALL:         fprintf(stderr, "Uninstall");       break;
    case OP_UPGRADE:           fprintf(stderr, "Upgrade");         break;
    case OP_CHECK_INTEGRITY:   fprintf(stderr, "Integrity check"); break;
    }
		
    fprintf(stderr,
	    " operation requires package name!\n"
	    "Please type `zippo --help' for more information.\n");
    return(EXIT_FAILURE);
  }

  /* TODO: Use this:
  {
  warnf("Version information for package '%s' faked from .ver file",
  package->name);
  }
  */

  /* Do the deed */
  switch(op) {	
  case OP_SYNCDB:
    req_syncdb.op             = op;
    req_syncdb.mod            = 0;
    req_syncdb.verbosity      = verbosity;
    req_syncdb.root           = root;
    req_syncdb.prefix         = prefix;
    req_syncdb.backup_prefix  = backup_prefix;
    req_syncdb.dsm_path       = dsm_path;
    req_syncdb.mft_path       = mft_path;
    req_syncdb.dsm_path_avail = dsm_path_avail;
    req_syncdb.zip_path       = zip_path;
    req_syncdb.tar_gzip_path  = tar_gzip_path;
    req_syncdb.tar_bzip2_path = tar_bzip2_path;
	  
    ret = perform_syncdb(&req_syncdb);
    break;

  case OP_QUERY:
    req_query.op             = op;
    req_query.mod            = qm;
    req_query.verbosity      = verbosity;
    req_query.root           = root;
    req_query.prefix         = prefix;
    req_query.name           = name;
    req_query.qname          = qname;
    req_query.dsm_path       = dsm_path;
    req_query.mft_path       = mft_path;
    req_query.dsm_path_avail = dsm_path_avail;
 
    ret = perform_query(&req_query);
    break;

  case OP_QUERY_FILE:
    req_query.op             = op;
    req_query.mod            = qm;
    req_query.verbosity      = verbosity;
    req_query.root           = root;
    req_query.prefix         = prefix;
    req_query.name           = name;
    req_query.qname          = qname;
    req_query.dsm_path       = dsm_path;
    req_query.mft_path       = mft_path;
    req_query.dsm_path_avail = dsm_path_avail;
    
    ret = perform_query_file(&req_query);
    break;

  case OP_QUERY_ALL:
    req_query.op             = op;
    req_query.mod            = qm;
    req_query.verbosity      = verbosity;
    req_query.root           = root;
    req_query.prefix         = prefix;
    req_query.name           = name;
    req_query.qname          = qname;
    req_query.dsm_path       = dsm_path;
    req_query.mft_path       = mft_path;
    req_query.dsm_path_avail = dsm_path_avail;

    ret = perform_query_all(&req_query);
    break;

  case OP_QUERY_AVAILABLE:
    /* No .mft file paths here */
    req_query.op             = op;
    req_query.mod            = qm;
    req_query.verbosity      = verbosity;
    req_query.root           = root;
    req_query.prefix         = prefix;
    req_query.name           = name;
    req_query.qname          = qname;
    req_query.dsm_path       = dsm_path;
    req_query.mft_path       = mft_path;
    req_query.dsm_path_avail = dsm_path_avail;

    ret = perform_query_all_avail(&req_query);
    break;

  case OP_INSTALL:
    req_install.op             = op;
    req_install.mod            = im;
    req_install.verbosity      = verbosity;
    req_install.root           = root;
    req_install.prefix         = prefix;
    req_install.backup_prefix  = backup_prefix;
    req_install.name           = name;
    req_install.dsm_path       = dsm_path;
    req_install.mft_path       = mft_path;
    req_install.dsm_path_avail = dsm_path_avail;
    req_install.zip_path       = zip_path;
    req_install.tar_gzip_path  = tar_gzip_path;
    req_install.tar_bzip2_path = tar_bzip2_path;

    ret = perform_install(&req_install);
    break;

  case OP_INSTALL_AVAILABLE:
    req_install.op             = op;
    req_install.mod            = im;
    req_install.verbosity      = verbosity;
    req_install.root           = root;
    req_install.prefix         = prefix;
    req_install.backup_prefix  = backup_prefix;
    req_install.name           = name;
    req_install.dsm_path       = dsm_path;
    req_install.mft_path       = mft_path;
    req_install.dsm_path_avail = dsm_path_avail;
    req_install.zip_path       = zip_path;
    req_install.tar_gzip_path  = tar_gzip_path;
    req_install.tar_bzip2_path = tar_bzip2_path;

    ret = perform_install_avail(&req_install);
    break;

  case OP_UNINSTALL:
    req_uninstall.op            = op;
    req_uninstall.mod           = um;
    req_uninstall.verbosity     = verbosity;
    req_uninstall.root          = root;
    req_uninstall.prefix        = prefix;
    req_uninstall.backup_prefix = backup_prefix;
    req_uninstall.name          = name;
    req_uninstall.dsm_path      = dsm_path;
    req_uninstall.mft_path      = mft_path;
 
    ret = perform_uninstall(&req_uninstall);
    break;

  case OP_UPGRADE:
    req_upgrade.op             = op;
    req_upgrade.mod            = upm;
    req_upgrade.verbosity      = verbosity;
    req_upgrade.root           = root;
    req_upgrade.prefix         = prefix;
    req_upgrade.backup_prefix  = backup_prefix;
    req_upgrade.name           = name;
    req_upgrade.dsm_path       = dsm_path;
    req_upgrade.mft_path       = mft_path;
    req_upgrade.zip_path       = zip_path;
    req_upgrade.tar_gzip_path  = tar_gzip_path;
    req_upgrade.tar_bzip2_path = tar_bzip2_path;
 
    ret = perform_upgrade(&req_upgrade);
    break;

  case OP_CHECK_INTEGRITY:
    req_check_integrity.op        = op;
    req_check_integrity.verbosity = verbosity;
    req_check_integrity.root      = root;
    req_check_integrity.prefix    = prefix;
    req_check_integrity.name      = name;
    req_check_integrity.dsm_path  = dsm_path;
    req_check_integrity.mft_path  = mft_path;

    ret = perform_check_integrity(&req_check_integrity);
    break;
		
  default:
    ret = EXIT_FAILURE;
    break;
  }
	
  /* Tidy up */
#define FREE_STRINGS(x) \
  if (x != NULL) { \
    for (i = 0; (x)[i] != NULL; i++) { \
      free((x)[i]); \
    } \
    free(x); \
  }

  free(root);
  free(prefix);

  if (name != NULL) free(name);
  if (name != NULL) free(qname);
  FREE_STRINGS(platforms);

#undef FREE_STRINGS

  wget_uninit();
  rcfile_uninit();  

  return(ret);
}

/* ------------------
 * - show_copyright -
 * ------------------ */

static void
show_copyright (int license_hint)
{
  printf("%s %d.%d.%d - %s\nCopyright (C) %s by %s <%s>\n",	       
	 COPYRIGHT_NAME,
	 ZIPPO_VERSION_MAJOR, ZIPPO_VERSION_MINOR, ZIPPO_VERSION_SUBMINOR,
	 COPYRIGHT_DESCRIPTION,
	 COPYRIGHT_YEAR, COPYRIGHT_AUTHOR, COPYRIGHT_EMAIL);

  if (license_hint) {
    printf("Distributed under terms of the GNU GPL ('%s -L' to view)\n",
	   COPYRIGHT_NAME);
  }

  printf("\n");
}

/* ----------------
 * - show_version -
 * ---------------- */

static void
show_version (void)
{
  int i;

  printf("Compilation information:\n");

  printf(". Built on '%s' by %s", zippo_build_host, zippo_build_user);
  if ((zippo_build_user_mail != NULL) && (zippo_build_user_mail[0] != '\0'))
    printf(" <%s>", zippo_build_user_mail);
  printf("\n. Built on %s %s\n", zippo_build_date, zippo_build_time);

  printf(". libzippo %d.%d.%d\n", LIBZIPPO_VERSION_MAJOR,
	 LIBZIPPO_VERSION_MINOR, LIBZIPPO_VERSION_SUBMINOR);

#ifdef __DJGPP__
  printf(". DJGPP %d.0%d\n", DJGPP, DJGPP_MINOR);
#else
  printf(". %s\n", zippo_build_uname);
#endif

  printf(". %s\n. %s\n. zlib %s %s\n.%s\n", zippo_build_gcc,
	 zippo_build_binutils, ZLIB_VERSION, ZLIB_COPYRIGHT, unz_copyright);

  printf("\nDSM built-ins:\n");

  printf(". DSM version:   %d.%d.%d\n",
	 DSM_VERSION_MAJOR, DSM_VERSION_MINOR, DSM_VERSION_SUBMINOR);

  printf(". Platform:      %s\n", zippo_build_platform_default);

  printf(". Root:          %s "
	 "(NB: overridden by DJDIR from environment)\n",
	 DEFAULT_DJGPP_PATH);

  printf(". Platform DSMs:");
  for (i = 0; platform_dsm_table[i] != NULL; i++) {
    printf(" %s", platform_dsm_name_table[i]);
  }
  printf("\n");

  printf("\nHelpers:\n. HTTP/FTP: wget: ");
  if (wget_executable() == NULL)
    printf("(Not found)\n");
  else
    printf("%s\n", wget_executable());
  printf("\n");
}

/* -------------
 * - show_help -
 * ------------- */

static void
show_help (void)
{
  printf("%s",

/* TODO: One/two line summary of command-line syntax */

/* Locations */
"Locations:\n"
"   --rcfile <file>        - Configuration file name\n"
"   --root <path>          - Database location (full path)\n"
"   --prefix <path>        - Install prefix (full path)\n"
"\n"

/* Output options */
"Output options:\n"
"   -v, --verbose          - Verbose output\n"
"   -Q, --quiet            - Minimal output\n"
"\n"

/* Database maintenance */
"Database maintenance:\n"
"   --initdb [<modifiers>] - Initialise database in directory spec'd by --root"
"\n"
"                            (create directory structure & configuration"
"file).\n"
"   --syncdb               - Create DSMs for pre-installed (manifest style)\n"
"                            packages from available package database.\n"
"\n"
"Database initialisation modifiers:\n"
"   --with-platform=<platform(s)>\n"
"                          - Create DSMs for the platforms specified in the\n"
"                            comma-separated list <platform(s)>.\n"
"   --with-zippo           - Copy the zippo program into the <root>/bin/\n"
"                            directory.\n"
"\n"

/* Query help */
"Queries:\n"
"   -q, --query <query>    - Perform a query\n"
"   <query> = <package identifier> [<query modifier>]\n"
"\n"
"Query package identifier:\n"
"   <package name>         - Query installed package\n"
"   -p <file>              - Query package file\n"
"   -a, --installed [<package name>]\n"
"                          - Query all/specified installed package(s)\n"
"   -A, --available [<package name>]\n"
"                          - Query all/specified available package(s)\n"
"\n"
"Query modifiers:\n"
"   -i, --longinfo, --long-info\n"
"                          - Display long description\n"
"   --shortinfo, --short-info\n"
"                          - Display short description\n"
"   -l                     - List files owned by/in package\n"
"   -f <file>              - Query whether package has <file>\n"
"   -R, --requires         - List packages required beforehand\n"
"   --depends-on           - List packages used, but not required\n"
"   --conflicts-with       - List packages that conflict\n"
"   --replaces             - List packages replaced\n"
"   --provides             - List features provided\n"
"   --install-before       - List packages to install beforehand\n"
"   --install-after        - List packages to install afterwards\n"
"   --all-deps,\n"
"     --all-dependencies   - List all dependencies\n"
"   --changelog            - List log of changes\n"
"   --pre-install-readme, --post-install-readme\n"
"                          - List pre- & post-installation help files\n"
"   --pre-uninstall-readme, --post-uninstall-readme\n"
"                          - List pre- & post-uninstallation help file\n"
"   --install-warning      - Show the install warning\n"
"\n"

/* Install options */
"Installing:\n"
"   -i, --install <package identifier> [<install modifiers>]\n"
"                          - Install package\n"
"\n"
"Install package identifier:\n"
"   <package file>         - Install using DSM file from package file\n"
"   <DSM file name>        - Install using DSM file\n"
"   -A, --available <package name>\n"
"                          - Install available package\n"
"\n"
"Install modifiers:\n"
"   -n, --test             - Check dependencies, but don't install\n"
"\n"

/* Uninstall options */
"Uninstalling:\n"
"   -e, --uninstall <spec> - Uninstall package <spec>\n"
"   <spec> = <package> [<uninstall modifiers>]\n"
"\n"
"Uninstall modifiers:\n"
"   -n, --test             - Check dependencies, but don't uninstall\n"
"\n"

/* Upgrade options */
"Upgrading:\n"
"   -U, --upgrade <spec>   - Upgrade package <spec>\n"
"   <spec> = <package> [<upgrade modifiers>]\n"
"\n"
"Upgrade modifiers:\n"
"   -n, --test             - Check dependencies, but don't upgrade\n"
"\n"

/* Integrity checking */
"Integrity checking:\n"
"   --check-integrity <spec>\n"
"                          - Check integrity of packages matched by <spec>\n"
"                            (includes listing of changed files)\n"
"   <spec> = <package>\n"
"\n"

/* Help options */
"Help:\n"
"   -h, --help             - Display this help\n"
"   -V, --version          - Display version information\n"
"   -L, --license          - Display licensing information\n"	       
"\n"
	 );
}

/* ----------------
 * - show_license -
 * ---------------- */

static void
show_license (void)
{
  printf(
"   This program is free software; you can redistribute it and/or modify\n"
"   it under the terms of the GNU General Public License as published by\n"
"   the Free Software Foundation; either version 2 of the License, or\n"
"   (at your option) any later version.\n"
"\n"
"   This program is distributed in the hope that it will be useful,\n"
"   but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
"   GNU General Public License for more details.\n"
"\n"
"   You should have received a copy of the GNU General Public License\n"
"   along with this program; if not, write to the Free Software\n"
"   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n"
"\n");
}

/* ---------------
 * - check_paths -
 * --------------- */

/* This function checks that the paths specified in 'list' exist. If one
 * doesn't exist, a warning is given, using the specified description. If
 * _all_ the paths exist, 1 is returned, else 0. */

static int
check_paths (char **list, char *desc)
{
  int ret;
  int i, failed;

  /* Simple case - no paths - duh. */
  if (list == NULL)
    return(1);
	
  for (failed = i = 0; list[i] != NULL; i++) {
    /* Check that the specified path is a directory. Skip URLs. */
    if (isurl(list[i]))
      continue;

    ret = isdir(list[i]);

    if (ret < 0) {
      warnf("Unable to check that '%s' is a directory (errno = %d): %s",
	    list[i], errno, strerror(errno));
      failed = 1;
      continue;
    }

    if (!ret) {
      warnf("%s path [%d]: '%s' not found", desc, i + 1, list[i]);
      failed = 1;
    }
  }

  return(!failed);
}

/* ------------
 * - is_query -
 * ------------ */

/* If this is a query operator, return 1; else return 0. */

static int __inline__
is_query (int op)
{
  switch(op) {
  case OP_QUERY:
  case OP_QUERY_FILE:
  case OP_QUERY_ALL:
  case OP_QUERY_AVAILABLE:
    return(1);

  default:
    return(0);
  }
}
