/* $Id: dsm.c,v 1.27 2002/03/10 21:28:25 richdawe Exp $ */

/*
 *  dsm.c - DSM parsing functions for zippo
 *  Copyright (C) 1999-2001 by Richard Dawe
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "common.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <dirent.h>
#include <limits.h>
#include <unistd.h>
#include <search.h>
#include <assert.h>

/* libsocket includes */
#include <libzippo/archive.h>
#include <libzippo/dsm.h>
#include <libzippo/package.h>
#include <libzippo/packlist.h>
#include <libzippo/util.h>

#include "strlwr.h"
#include "unzip.h"

/* Sanity checks */
#if PACKAGE_VERSION_N_HAS != 14
#error "Need to update code to cope with PACKAGE_VERSION's has_*"
#endif

#if PACKAGE_VERSION_N_DATA != 21
#error "Need to update code to cope with PACKAGE_VERSION's data fields"
#endif

/* Macros for easy to handle parsing operations. */

/* Create a copy of the value in a new piece of memory. */
#define DSM_PARSING_FUNC_DUP_DEF(p) \
  int dsm_parse_##p (const char *directive, \
		     const char *str, \
		     PACKAGE_INFO *package) { \
    package->p = strdup(str); \
    return(DSM_OK); \
  }

/* Copy the value into an existing array, without overrunning. */
#define DSM_PARSING_FUNC_COPY_DEF(p) \
  int dsm_parse_##p (const char *directive, \
		     const char *str, \
		     PACKAGE_INFO *package) { \
    strncpy(package->p, str, sizeof(package->p)); \
    package->p[sizeof(package->p) - 1] = '\0'; \
    return(DSM_OK); \
  }

/* Create a copy of the value in a new piece of memory, but store the value
 * in an array of value. dsm_parse_multiple() handles finding an empty array
 * index and creating & placing the copy there. */
#define DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(p, max) \
  int dsm_parse_##p (const char *directive, \
		     const char *str, \
		     PACKAGE_INFO *package) { \
    return(dsm_parse_multiple(directive, str, package->p, max)); \
  }

/* Define some simple parsing functions - there should be a declaration in the
 * include file dsm.h (using DSM_PARSING_FUNC_DECL) & an entry in
 * the parsing table below for each of these. */
DSM_PARSING_FUNC_DUP_DEF(dsm_author);
DSM_PARSING_FUNC_DUP_DEF(dsm_author_email);
DSM_PARSING_FUNC_DUP_DEF(dsm_author_im);
DSM_PARSING_FUNC_DUP_DEF(dsm_author_web_site);
DSM_PARSING_FUNC_DUP_DEF(dsm_author_ftp_site);

DSM_PARSING_FUNC_COPY_DEF(dsm_name);

DSM_PARSING_FUNC_DUP_DEF(binaries_dsm);
DSM_PARSING_FUNC_DUP_DEF(sources_dsm);
DSM_PARSING_FUNC_DUP_DEF(documentation_dsm);

DSM_PARSING_FUNC_COPY_DEF(name);
DSM_PARSING_FUNC_COPY_DEF(manifest);

DSM_PARSING_FUNC_DUP_DEF(license);
DSM_PARSING_FUNC_DUP_DEF(organisation);

DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(author, PACKAGE_MAX_AUTHOR);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(author_email, PACKAGE_MAX_AUTHOR);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(author_im, PACKAGE_MAX_AUTHOR);

DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(web_site, PACKAGE_MAX_SITE);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(ftp_site, PACKAGE_MAX_SITE);

DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(maintainer, PACKAGE_MAX_MAINTAINER);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(maintainer_email, PACKAGE_MAX_MAINTAINER);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(maintainer_im, PACKAGE_MAX_MAINTAINER);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(maintainer_web_site, PACKAGE_MAX_MAINTAINER);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(maintainer_ftp_site, PACKAGE_MAX_MAINTAINER);

DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(porter, PACKAGE_MAX_PORTER);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(porter_email, PACKAGE_MAX_PORTER);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(porter_im, PACKAGE_MAX_PORTER);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(porting_web_site, PACKAGE_MAX_PORTER);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(porting_ftp_site, PACKAGE_MAX_PORTER);

DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list,
				  PACKAGE_MAX_MAILINGLIST);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_description,
				  PACKAGE_MAX_MAILINGLIST);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_request,
				  PACKAGE_MAX_MAILINGLIST);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_administrator,
				  PACKAGE_MAX_MAILINGLIST);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_administrator_email,
				  PACKAGE_MAX_MAILINGLIST);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_administrator_im,
				  PACKAGE_MAX_MAILINGLIST);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_web_site,
				  PACKAGE_MAX_MAILINGLIST);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(mailing_list_ftp_site,
				  PACKAGE_MAX_MAILINGLIST);

DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup,
				  PACKAGE_MAX_NEWSGROUP);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_description,
				  PACKAGE_MAX_NEWSGROUP);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_email_gateway,
				  PACKAGE_MAX_NEWSGROUP);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_administrator,
				  PACKAGE_MAX_NEWSGROUP);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_administrator_email,
				  PACKAGE_MAX_NEWSGROUP);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_administrator_im,
				  PACKAGE_MAX_NEWSGROUP);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_web_site,
				  PACKAGE_MAX_NEWSGROUP);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(newsgroup_ftp_site,
				  PACKAGE_MAX_NEWSGROUP);

DSM_PARSING_FUNC_COPY_DEF(simtelnet_path);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(zip, PACKAGE_MAX_ARCHIVE);
DSM_PARSING_FUNC_DUP_DEF(zip_options);
DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(tar_gzip, PACKAGE_MAX_ARCHIVE);
DSM_PARSING_FUNC_DUP_DEF(tar_gzip_options);

DSM_PARSING_FUNC_DUP_DEF(changelog);

DSM_PARSING_FUNC_DUP_DEF(pre_install_readme);
DSM_PARSING_FUNC_DUP_DEF(post_install_readme);
DSM_PARSING_FUNC_DUP_DEF(pre_uninstall_readme);
DSM_PARSING_FUNC_DUP_DEF(post_uninstall_readme);

/* Built-in scripting. */
DSM_PARSING_FUNC_DUP_DEF(builtin_pre_install_script);
DSM_PARSING_FUNC_DUP_DEF(builtin_post_install_script);
DSM_PARSING_FUNC_DUP_DEF(builtin_pre_uninstall_script);
DSM_PARSING_FUNC_DUP_DEF(builtin_post_uninstall_script);

DSM_PARSING_FUNC_DUP_DEF(pre_install_script);
DSM_PARSING_FUNC_DUP_DEF(post_install_script);
DSM_PARSING_FUNC_DUP_DEF(pre_uninstall_script);
DSM_PARSING_FUNC_DUP_DEF(post_uninstall_script);

DSM_PARSING_FUNC_MULTIPLE_DUP_DEF(keep_file, PACKAGE_MAX_KEEP_FILE);

DSM_PARSING_FUNC_DUP_DEF(prefix);

/* Dependency functions are defined individually */

DSM_PARSING_FUNC_DUP_DEF(install_warning);

/* Function table for parsing */
typedef struct {
  char *directive;
  int (*parse)(const char *directive, const char *str, PACKAGE_INFO *package);
} DSM_PARSER;


#define DSM_PARSING_FUNC_ENTRY(p, q) { p, dsm_parse_##q }

DSM_PARSER dsm_parser[] = {
  DSM_PARSING_FUNC_ENTRY("dsm-author",          dsm_author),
  DSM_PARSING_FUNC_ENTRY("dsm-author-email",    dsm_author_email),
  DSM_PARSING_FUNC_ENTRY("dsm-author-im",       dsm_author_im),
  DSM_PARSING_FUNC_ENTRY("dsm-author-web-site", dsm_author_web_site),
  DSM_PARSING_FUNC_ENTRY("dsm-author-ftp-site", dsm_author_ftp_site),

  DSM_PARSING_FUNC_ENTRY("dsm-name", dsm_name),

  { "dsm-file-version", dsm_parse_dsm_file_version, },       
  { "dsm-version", dsm_parse_dsm_version },  

  DSM_PARSING_FUNC_ENTRY("binaries-dsm",      binaries_dsm),
  DSM_PARSING_FUNC_ENTRY("sources-dsm",       sources_dsm),
  DSM_PARSING_FUNC_ENTRY("documentation-dsm", documentation_dsm),

  DSM_PARSING_FUNC_ENTRY("name",              name),
  DSM_PARSING_FUNC_ENTRY("manifest",          manifest),
	
  { "version", dsm_parse_package_version },
  { "type", dsm_parse_type },
  { "dsm-type", dsm_parse_type }, /* 'dsm-type' is now a synonym for 'type' */

  { "short-description", dsm_parse_short_description },	
  { "long-description", dsm_parse_long_description },

  DSM_PARSING_FUNC_ENTRY("license",      license),
  DSM_PARSING_FUNC_ENTRY("organisation", organisation),
  DSM_PARSING_FUNC_ENTRY("author",       author),
  DSM_PARSING_FUNC_ENTRY("author-email", author_email),
  DSM_PARSING_FUNC_ENTRY("author-im",    author_im),
  DSM_PARSING_FUNC_ENTRY("web-site",     web_site),
  DSM_PARSING_FUNC_ENTRY("ftp-site",     ftp_site),

  DSM_PARSING_FUNC_ENTRY("maintainer",          maintainer),
  DSM_PARSING_FUNC_ENTRY("maintainer-email",    maintainer_email),
  DSM_PARSING_FUNC_ENTRY("maintainer-im",       maintainer_im),
  DSM_PARSING_FUNC_ENTRY("maintainer-web-site", maintainer_web_site),
  DSM_PARSING_FUNC_ENTRY("maintainer-ftp-site", maintainer_ftp_site),

  DSM_PARSING_FUNC_ENTRY("porter",           porter),
  DSM_PARSING_FUNC_ENTRY("porter-email",     porter_email),
  DSM_PARSING_FUNC_ENTRY("porter-im",        porter_im),
  DSM_PARSING_FUNC_ENTRY("porting-web-site", porting_web_site),
  DSM_PARSING_FUNC_ENTRY("porting-ftp-site", porting_ftp_site),

  DSM_PARSING_FUNC_ENTRY("mailing-list", mailing_list),
  DSM_PARSING_FUNC_ENTRY("mailing-list-description", mailing_list_description),
  DSM_PARSING_FUNC_ENTRY("mailing-list-request", mailing_list_request),
  DSM_PARSING_FUNC_ENTRY("mailing-list-administrator",
			 mailing_list_administrator),
  DSM_PARSING_FUNC_ENTRY("mailing-list-administrator-email",
			 mailing_list_administrator_email),
  DSM_PARSING_FUNC_ENTRY("mailing-list-administrator-im",
			 mailing_list_administrator_im),
  DSM_PARSING_FUNC_ENTRY("mailing-list-web-site", mailing_list_web_site),
  DSM_PARSING_FUNC_ENTRY("mailing-list-ftp-site", mailing_list_ftp_site),

  DSM_PARSING_FUNC_ENTRY("newsgroup", newsgroup),
  DSM_PARSING_FUNC_ENTRY("newsgroup-description", newsgroup),
  DSM_PARSING_FUNC_ENTRY("newsgroup-email-gateway", newsgroup_email_gateway),
  DSM_PARSING_FUNC_ENTRY("newsgroup-administrator", newsgroup_administrator),
  DSM_PARSING_FUNC_ENTRY("newsgroup-administrator-email",
			 newsgroup_administrator_email),
  DSM_PARSING_FUNC_ENTRY("newsgroup-administrator-im",
			 newsgroup_administrator_im),
  DSM_PARSING_FUNC_ENTRY("newsgroup-web-site", newsgroup_web_site),
  DSM_PARSING_FUNC_ENTRY("newsgroup-ftp-site", newsgroup_ftp_site),

  DSM_PARSING_FUNC_ENTRY("simtelnet-path",   simtelnet_path),
  DSM_PARSING_FUNC_ENTRY("zip",              zip),
  DSM_PARSING_FUNC_ENTRY("zip-options",      zip_options),
  DSM_PARSING_FUNC_ENTRY("tar-gzip",         tar_gzip),
  DSM_PARSING_FUNC_ENTRY("tar-gzip-options", tar_gzip_options),

  DSM_PARSING_FUNC_ENTRY("changelog", changelog),

  DSM_PARSING_FUNC_ENTRY("pre-install-readme",    pre_install_readme),
  DSM_PARSING_FUNC_ENTRY("post-install-readme",   post_install_readme),
  DSM_PARSING_FUNC_ENTRY("pre-uninstall-readme",  pre_uninstall_readme),
  DSM_PARSING_FUNC_ENTRY("post-uninstall-readme", post_uninstall_readme),

  /* Built-in scripting */
  DSM_PARSING_FUNC_ENTRY("builtin-pre-install-script",
			 builtin_pre_install_script),
  DSM_PARSING_FUNC_ENTRY("builtin-post-install-script",
			 builtin_post_install_script),
  DSM_PARSING_FUNC_ENTRY("builtin-pre-uninstall-script",
			 builtin_pre_uninstall_script),
  DSM_PARSING_FUNC_ENTRY("builtin-post-uninstall-script",
			 builtin_post_uninstall_script),

  DSM_PARSING_FUNC_ENTRY("pre-install-script",    pre_install_script),
  DSM_PARSING_FUNC_ENTRY("post-install-script",   post_install_script),
  DSM_PARSING_FUNC_ENTRY("pre-uninstall-script",  pre_uninstall_script),
  DSM_PARSING_FUNC_ENTRY("post-uninstall-script", post_uninstall_script),

  DSM_PARSING_FUNC_ENTRY("keep-file", keep_file),

  DSM_PARSING_FUNC_ENTRY("prefix", prefix),

  DSM_PARSING_FUNC_ENTRY("requires",       deps),
  DSM_PARSING_FUNC_ENTRY("depends-on",     deps),
  DSM_PARSING_FUNC_ENTRY("conflicts-with", deps),
  DSM_PARSING_FUNC_ENTRY("replaces",       deps),
  DSM_PARSING_FUNC_ENTRY("provides",       deps),
	
  { "duplicate-action", dsm_parse_duplicate_action },

  DSM_PARSING_FUNC_ENTRY("install-before",  deps),
  DSM_PARSING_FUNC_ENTRY("install-after",   deps),
  DSM_PARSING_FUNC_ENTRY("install-warning", install_warning),

  { NULL, NULL }
};

/* ----------------------
 * - dsm_parse_operator -
 * ---------------------- */

int
dsm_parse_operator (const char *str)
{
  if (   (strcmp(str, "==") == 0)
      || (strcmp(str, "=")  == 0))
    return(DEP_OP_EQUAL);

  if (strcmp(str, "<=") == 0) return(DEP_OP_LESSEQUAL);
  if (strcmp(str, ">=") == 0) return(DEP_OP_GREATEREQUAL);
  if (strcmp(str, "!=") == 0) return(DEP_OP_NOTEQUAL);
  if (strcmp(str, "<")  == 0) return(DEP_OP_LESS);
  if (strcmp(str, ">")  == 0) return(DEP_OP_GREATER);

  return(DEP_OP_NONE);
}

/* ------------------
 * - dsm_parse_deps -
 * ------------------ */

int
dsm_parse_deps (const char *directive, const char *str, PACKAGE_INFO *package)
{
  PACKAGE_DEP *dep = NULL;
  char *buf = strdup(str);
  char *p = NULL, *q = NULL, *r = NULL;
  int i, found, len, ret;

  PACKAGE_DEP **deps = package->deps;

  for (found = -1, i = 0; i < PACKAGE_MAX_DEP; i++) {
    if (deps[i] == NULL) { found = i; break; }
  }
  if (found == -1) { free(buf); return(0); }

  /* Allocate memory */
  dep = deps[found] = (PACKAGE_DEP *) calloc(1, sizeof(PACKAGE_DEP));
  if (dep == NULL) return (DSM_INTERNAL_ERROR);

  /* Determine dependency type */
  /* There is no error checking there; if we are here, the directive
   * must be valid.  */
  if (!strcmp(directive, "requires"))
    dep->dep_type = PACKAGE_DEP_REQUIRES;
  else if (!strcmp(directive, "depends-on"))
    dep->dep_type = PACKAGE_DEP_DEPENDS_ON;
  else if (!strcmp(directive, "conflicts-with"))
    dep->dep_type = PACKAGE_DEP_CONFLICTS_WITH;
  else if (!strcmp(directive, "replaces"))
    dep->dep_type = PACKAGE_DEP_REPLACES;
  else if (!strcmp(directive, "provides"))
    dep->dep_type = PACKAGE_DEP_PROVIDES;
  else if (!strcmp(directive, "install-before"))
    dep->dep_type = PACKAGE_DEP_INSTALL_BEFORE;
  else /* if (!strcmp(directive, "install-after")) */
    dep->dep_type = PACKAGE_DEP_INSTALL_AFTER;

  /* If there is a qualifier, store it & chop it off the end. */
  r =  strchr(str, ':');
  if (r != NULL) {
    *r = '\0', r++;
    for (; isspace(*r) && (*r != '\0'); r++) {;}
    dep->qualifier = strdup(r);
  }

  /* Make p point to the operator, if any */
  for (p = buf; !isspace(*p) && (*p != '\0'); p++) {;}
  
  if (*p == '\0') {
    p = NULL;
  } else {
    *p = '\0';
    p++;
  }

  /* Make q point to the version, if any */
  q = NULL;

  if (p != NULL) {
    for (q = p; !isspace(*q) && (*q != '\0'); q++) {;}

    if (*q != '\0') {
      *q = '\0';
      q++;
    }
  }

  len = sizeof(dep->name);
  strncpy(dep->name, buf, len);
  dep->name[len - 1] = '\0';

  /* Handle default operator & version */
  if ( (p != NULL) && (dsm_parse_operator(p) == DEP_OP_NONE) ) {
    /* This indicates that p contains a version number, which
       should be in q. */
    q = p;
    p = NULL;
  }

  if (p != NULL) {
    dep->op = dsm_parse_operator(p);
  } else {
    if (q != NULL)
      dep->op = DEP_OP_EQUAL;
    else
      dep->op = DEP_OP_NONE;
  }

  if (q != NULL) {
    ret = dsm_parse_version(directive, q, &dep->version);
  } else {
    /* Can't have an operator without a version */
    if (p != NULL) {
      ret = DSM_BAD_DIRECTIVE;
    } else {
      dep->op = DEP_OP_NONE; /* => all versions */
      ret = DSM_OK;
    }
  }

  /* For now, set the pointer to dependendant package to NULL. */
  /* This is about to change by packlist_xref(). */
  dep->dep = NULL;

  free(buf);

  /* Success? */
  if (ret != DSM_OK) {
    free(dep);
    deps[found] = NULL;
  }

  return( (ret == DSM_OK) ? DSM_OK : DSM_BAD_DIRECTIVE );
}

/* ---------------------
 * - atoi_wild_wrapper -
 * --------------------- */

/* This is like atoi(), but will return the special wildcard version numbers,
 * if given a wildcard. It also does not allow negative version numbers. */

const static int ATOI_WRAPPER_ERROR  = -1;
const static int ATOI_WRAPPER_SINGLE = -2;
const static int ATOI_WRAPPER_MULTI  = -3;

static int
atoi_wild_wrapper (const char *str)
{
  int n;

  /* '?' wildcard */
  if (strncmp(str,
	      DSM_COMP_SINGLE_WILDCARD,
	      strlen(DSM_COMP_SINGLE_WILDCARD)) == 0)
    return(ATOI_WRAPPER_SINGLE);

  /* '*' wildcard */
  if (strncmp(str,
	      DSM_COMP_MULTI_WILDCARD,
	      strlen(DSM_COMP_MULTI_WILDCARD)) == 0)
    return(ATOI_WRAPPER_MULTI);

  /* Just a plain old number */
  n = atoi(str);

  if (n < 0)
    return(ATOI_WRAPPER_ERROR);
  else
    return(n);
}

/* ----------------
 * - atoi_wrapper -
 * ---------------- */

/* This is like atoi(), but does not allow negative version numbers. */

static int
atoi_wrapper (const char *str)
{
  int n;

  n = atoi(str);

  if (n < 0)
    return(ATOI_WRAPPER_ERROR);
  else
    return(n);
}

/* -----------------------------
 * - wildcard_remaining_fields -
 * ----------------------------- */

static int
wildcard_remaining_fields (PACKAGE_VERSION *ver)
{
  if (!ver->has_major) {
    ver->has_major = 1;
    ver->major     = PACKAGE_VERSION_WILDCARD;
  }

  if (!ver->has_minor) {
    ver->has_minor = 1;
    ver->minor     = PACKAGE_VERSION_WILDCARD;
  }

  if (!ver->has_subminor) {
    ver->has_subminor = 1;
    ver->subminor     = PACKAGE_VERSION_WILDCARD;
  }

  if (!ver->has_subsubminor) {
    ver->has_subsubminor = 1;
    ver->subsubminor     = PACKAGE_VERSION_WILDCARD;
  }

  if (!ver->has_alpha) {
    ver->has_alpha = 1;
    ver->alpha     = PACKAGE_VERSION_WILDCARD;
  }

  if (!ver->has_beta) {
    ver->has_beta = 1;
    ver->beta     = PACKAGE_VERSION_WILDCARD;
  }

  if (!ver->has_pre) {
    ver->has_pre = 1;
    ver->pre     = PACKAGE_VERSION_WILDCARD;
  }

  if (!ver->has_revision) {
    ver->has_revision = 1;
    ver->revision     = PACKAGE_VERSION_WILDCARD;
  }

  if (!ver->has_patchlevel) {
    ver->has_patchlevel = 1;
    ver->patchlevel     = PACKAGE_VERSION_WILDCARD;
  }

  if (!ver->has_snapshot) {
    ver->has_snapshot   = 1;
    ver->snapshot_year  = PACKAGE_VERSION_WILDCARD;
    ver->snapshot_month = PACKAGE_VERSION_WILDCARD;
    ver->snapshot_day   = PACKAGE_VERSION_WILDCARD;
  }

  if (!ver->has_release) {
    ver->has_release = 1;
    ver->release     = PACKAGE_VERSION_WILDCARD;
  }

  /* OK */
  return(1);
}

/* ------------------------------
 * - dsm_parse_version_internal -
 * ------------------------------ */

/* TODO: Are wildcards handled in all cases? */

static int
dsm_parse_version_internal (const char *str, const int allow_wildcard,
			    PACKAGE_VERSION *ver)
{
  char *buf = strdup(str);
  char *p   = buf;
  char *q   = NULL;
  char snapshot_year[5], snapshot_month[3], snapshot_day[3];
  int i, ret, n, n_l0, count;
  int (*my_atoi)(const char *);

  /* Zero the package version fields apart from the type (since that's
   * parsed separately), in case there are multiple 'version'
   * directives in a file. It's not legal to have multiple 'version'
   * directives, but we should cope properly. Assume the last 'version'
   * directive is the one wanted. */

  /* ASSUMPTION: 'ver' has been zeroed and contains valid either none
   * or some valid version information. */
  if (!ver->has_type)
    ver->has_version = 0;

  if (ver->has_platform) {
    /* Free strings, to avoid memory leak. */
    if (ver->platform_cpu) {
      free(ver->platform_cpu);
      ver->platform_cpu = NULL;
    }

    if (ver->platform_manufacturer) {
      free(ver->platform_manufacturer);
      ver->platform_manufacturer = NULL;
    }

    if (ver->platform_kernel) {
      free(ver->platform_kernel);
      ver->platform_kernel = NULL;
    }

    if (ver->platform_os) {
      free(ver->platform_os);
      ver->platform_os = NULL;
    }
  }

  ver->has_major
    = ver->has_minor
    = ver->has_subminor
    = ver->has_subsubminor
    = ver->has_alpha
    = ver->has_beta
    = ver->has_pre
    = ver->has_revision
    = ver->has_patchlevel
    = ver->has_snapshot
    = ver->has_release
    = ver->has_platform
    = 0;

  ver->major
    = ver->minor
    = ver->minor_l0
    = ver->subminor
    = ver->subminor_l0
    = ver->subsubminor
    = ver->subsubminor_l0
    = ver->alpha
    = ver->beta
    = ver->pre
    = ver->revision
    = ver->patchlevel
    = ver->snapshot_year
    = ver->snapshot_month
    = ver->snapshot_day
    = ver->release
    = 0;

  /* Use the appropriate function for converting version numbers. If we're
   * using wildcards, then use the modified atoi() that understands them. */
  if (allow_wildcard)
    my_atoi = atoi_wild_wrapper;
  else
    my_atoi = atoi_wrapper;

  if (buf == NULL) return(DSM_INTERNAL_ERROR);
  strlwr(buf);

  /* Parse the period-separated version numbers */
  for (i = 0, p = strtok(buf, "."); ; i++, p = strtok(NULL, ".")) {    
    if (p == NULL)
      break;

    /* Make sure we have a digit (or, optionally, a wildcard) */
    if (!isdigit(*p)) {
      int ok;

      ok  = allow_wildcard;
      ok &= (   (strncmp(p,
			 DSM_COMP_SINGLE_WILDCARD,
			 strlen(DSM_COMP_SINGLE_WILDCARD)) == 0)
	     || (strncmp(p,
			 DSM_COMP_MULTI_WILDCARD,
			 strlen(DSM_COMP_MULTI_WILDCARD)) == 0));

      if (!ok) {
	free(buf);
	return(DSM_BAD_VERSION_FORMAT);
      }
    }

    /* Convert version component & count its leading zeroes, whether or not
     * we use that count. */
    ret  = my_atoi(p);
    n_l0 = count_l0(p);

    if (ret >= 0) {
      n = ret;
    } else if (ret == ATOI_WRAPPER_SINGLE) {
      n = PACKAGE_VERSION_WILDCARD;
    } else if (ret == ATOI_WRAPPER_MULTI) {
      /* Wildcard remaining fields */
      /* TODO: This could be more sophisticated - it could stop wildcarding
       * at the first not-wildcarded field after the wildcard, e.g.
       * 2.* alpha 1. But how likely is this kind of format to be used? */
      wildcard_remaining_fields(ver);
      free(buf), buf = NULL;

      /* OK, I know what you're thinking...ugh, gotos. */
      goto dsm_parse_version_checks;
    } else {
      /* Probably n == ATOI_WRAPPER_ERROR */
      free(buf);
      return(DSM_BAD_VERSION_FORMAT);
    }

    switch(i) {
    case 0:
      ver->has_major = 1;
      ver->major     = n;
      break;

    case 1:
      ver->has_minor = 1;
      ver->minor     = n;
      ver->minor_l0  = n_l0;
      break;

    case 2:
      ver->has_subminor = 1;
      ver->subminor     = n;
      ver->subminor_l0  = n_l0;
      break;

    case 3:
      ver->has_subsubminor = 1;
      ver->subsubminor     = n;
      ver->subsubminor_l0  = n_l0;
      break;

    default:
      free(buf);
      return(DSM_BAD_VERSION_FORMAT);
    }
  }

  /* buf has been torn up by strtok(), so use str from here on. */
  free(buf), buf = NULL;

  /* Alpha version */
  p = strstr(str, DSM_COMP_ALPHA);
  if (p != NULL) {
    for (p += strlen(DSM_COMP_ALPHA); (*p != '\0') && isspace(*p); p++) {;}
    ver->has_alpha = 1;
    ver->alpha     = my_atoi(p);
  }

  /* Beta version */
  p = strstr(str, DSM_COMP_BETA);
  if (p != NULL) {
    for (p += strlen(DSM_COMP_BETA); (*p != '\0') && isspace(*p); p++) {;}
    ver->has_beta = 1;
    ver->beta     = my_atoi(p);
  }

  /* Pre */
  p = strstr(str, DSM_COMP_PRE);
  if (p != NULL) {
    for (p += strlen(DSM_COMP_PRE); (*p != '\0') && isspace(*p); p++) {;}
    ver->has_pre = 1;
    ver->pre     = my_atoi(p);
  }
	
  /* Revision */
  p = strstr(str, DSM_COMP_REVISION);
  if (p != NULL) {
    for (p += strlen(DSM_COMP_REVISION); (*p != '\0') && isspace(*p); p++) {;}
    ver->has_revision = 1;
    ver->revision     = my_atoi(p);
  }

  /* Patchlevel */
  p = strstr(str, DSM_COMP_PATCHLEVEL);
  if (p != NULL) {
    for (p += strlen(DSM_COMP_PATCHLEVEL);
	 (*p != '\0') && isspace(*p); p++) {;}
    ver->has_patchlevel = 1;
    ver->patchlevel     = my_atoi(p);
  }

  /* Snapshot */
  p = strstr(str, DSM_COMP_SNAPSHOT);
  if (p != NULL) {
    for (p += strlen(DSM_COMP_SNAPSHOT); (*p != '\0') && isspace(*p); p++) {;}
    if (strlen(p) >= 8) {
      /* TODO: Support wildcards => need hyphens in date? */
      /* TODO: Error condition? */
      ver->has_snapshot = 1;

      strncpy(snapshot_year,  p,     4);
      strncpy(snapshot_month, p + 4, 2);
      strncpy(snapshot_day,   p + 6, 2);

      snapshot_year[4]  = '\0';
      snapshot_month[2] = '\0';
      snapshot_day[2]   = '\0';

      ver->snapshot_year  = atoi(snapshot_year);
      ver->snapshot_month = atoi(snapshot_month);
      ver->snapshot_day   = atoi(snapshot_day);
    }
  }

  /* Release */
  p = strstr(str, DSM_COMP_RELEASE);
  if (p != NULL) {
    for (p += strlen(DSM_COMP_RELEASE);
	 (*p != '\0') && isspace(*p); p++) {;}
    ver->has_release = 1;
    ver->release     = my_atoi(p);
  }

  /* Platform */
  /* TODO: Make this cope with regexps */
  p = strstr(str, DSM_COMP_PLATFORM);
  if (p != NULL) {
    for (p += strlen(DSM_COMP_PLATFORM); (*p != '\0') && isspace(*p); p++) {;}

    /* Count the number of dash delimiters */
    for (q = p, count = 0; !isspace(*q) && (*q != '\0'); q++) {
      if (*q == '-') count++;
    }

    if ((count == 2) || (count == 3)) {
      /* Chop up the platform by turning the hyphens to
       * nuls, strdup'ing portions and then placing the
       * hyphens back. */
      q = strchr(p, '-'), *q = '\0';
      ver->platform_cpu = strdup(p);
      *q = '-', p = q, p++;
      q = strchr(p, '-'), *q = '\0';
      ver->platform_manufacturer = strdup(p);
      *q = '-', p = q, p++;
      if (count == 3) {
	q = strchr(p, '-'), *q = '\0';
	ver->platform_kernel = strdup(p);
	*q = '-', p = q, p++;
      }
      /* TODO: This should detect the end by looking for
       * whitespace. */
      ver->platform_os = strdup(p);

      ver->has_platform = 1;
    }
  }

 dsm_parse_version_checks:
  /* Any version info present? */
  if (   ver->has_major    || ver->has_minor
      || ver->has_subminor || ver->has_subsubminor
      || ver->has_alpha    || ver->has_beta       || ver->has_pre
      || ver->has_revision || ver->has_patchlevel || ver->has_snapshot
      || ver->has_release  || ver->has_platform)
    ver->has_version = 1;

  /* Sanity checks */

  /* It can't be an alpha, beta and pre-release simultaneously. */
  count  = (ver->has_alpha != 0) && (ver->alpha != PACKAGE_VERSION_WILDCARD);
  count += (ver->has_beta  != 0) && (ver->beta  != PACKAGE_VERSION_WILDCARD);
  count += (ver->has_pre   != 0) && (ver->pre   != PACKAGE_VERSION_WILDCARD);

  if (count > 1) {
    /* TODO: Slight more descriptive error */
    return(DSM_BAD_VERSION_FORMAT);
  }

  /* It can't be a pre-release and release simultaneously. */
  count = (ver->has_pre != 0) && (ver->pre != PACKAGE_VERSION_WILDCARD);
  count
    += (ver->has_release != 0) && (ver->release != PACKAGE_VERSION_WILDCARD);

  if (count > 1) {
    /* TODO: Slight more descriptive error */
    return(DSM_BAD_VERSION_FORMAT);
  }

  /* OK */
  return(DSM_OK);
}

/* ---------------------
 * - dsm_parse_version -
 * --------------------- */

/* This is just an inferface to dsm_parse_version_internal(), but does not
 * allow wildcards. */

int
dsm_parse_version (const char *directive, const char *str,
		   PACKAGE_VERSION *ver)
{
  return(dsm_parse_version_internal(str, 0, ver));
}

/* ------------------------------
 * - dsm_parse_wildcard_version -
 * ------------------------------ */

/* This is just an inferface to dsm_parse_version_internal(), but does
 * allow wildcards. This is intended for use in user-interfacing code,
 * e.g. code that accepts the package name and version. */

int
dsm_parse_wildcard_version (const char *str, PACKAGE_VERSION *ver)
{
  return(dsm_parse_version_internal(str, 1, ver));
}

/* Simple wrapper functions */

int
dsm_parse_dsm_file_version (const char *directive, const char *str,
			    PACKAGE_INFO *package)
{
  return(dsm_parse_version(directive, str, &package->dsm_file_version));
}

int
dsm_parse_dsm_version (const char *directive, const char *str,
		       PACKAGE_INFO *package)
{
  /* our_ver is a PACKAGE_VERSION version of DSM_VERSION_*, so we can
   * compare it with dsm-version parsed from a DSM. */
  static PACKAGE_VERSION our_ver = {
    /* has_* fields */
    1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    /* data fields */
    DSM_VERSION_MAJOR, DSM_VERSION_MINOR, 0, DSM_VERSION_SUBMINOR, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, 0
  };

  int ret = 0;

  /* Parse the DSM's version */
  ret = dsm_parse_version(directive, str, &package->dsm_version);
  if (ret != DSM_OK)
    return(ret);

  /* Do we support this version? */
  if (package_vercmp(&our_ver, &package->dsm_version) < 0) {
    /* Nope */
    return(DSM_NOT_UNDERSTOOD);
  }

  return(DSM_OK);
}

int
dsm_parse_package_version (const char *directive, const char *str,
			   PACKAGE_INFO *package)
{
  return(dsm_parse_version(directive, str, &package->version));
}

/* ---------------------------
 * - dsm_parse_type_internal -
 * --------------------------- */

int
dsm_parse_type_internal (const char *str,
			 const int allow_wildcard,
			 PACKAGE_INFO *package)
{
  int ret = DSM_OK;

  package->version.has_type = 1;

  if (strcasecmp(str, DSM_COMP_BINARIES) == 0) {
    package->version.type = TYPE_BINARIES;
  } else if (strcasecmp(str, DSM_COMP_SOURCES) == 0) {
    package->version.type = TYPE_SOURCES;
  } else if (strcasecmp(str, DSM_COMP_DOCUMENTATION) == 0) {
    package->version.type = TYPE_DOCUMENTATION;
  } else if (strcasecmp(str, DSM_COMP_GROUP) == 0) {
    package->version.type = TYPE_GROUP;
  } else if (strcasecmp(str, DSM_COMP_VIRTUAL) == 0) {
    package->version.type = TYPE_VIRTUAL;
  } else if (   allow_wildcard
	     && (   (strcasecmp(str, DSM_COMP_SINGLE_WILDCARD) == 0)
		 || (strcasecmp(str, DSM_COMP_MULTI_WILDCARD) == 0))) {
    package->version.type = TYPE_WILDCARD;
  } else {
    package->version.has_type = 0;
    package->version.type     = TYPE_NONE;
    ret = DSM_BAD_TYPE;
  }

  /* Type => we have some version information */
  if (package->version.has_type)
    package->version.has_version = 1;

  return(ret);
}

/* ------------------
 * - dsm_parse_type -
 * ------------------ */

/* This is just an inferface to dsm_parse_type_internal(), but does not
 * allow wildcards. */

int
dsm_parse_type (const char *directive, const char *str, PACKAGE_INFO *package)
{
  return(dsm_parse_type_internal(str, 0, package));
}

/* ---------------------------
 * - dsm_parse_wildcard_type -
 * --------------------------- */

/* This is just an inferface to dsm_parse_version_internal(), but does
 * allow wildcards. This is intended for use in user-interfacing code,
 * e.g. code that accepts the package name and version. */

int
dsm_parse_wildcard_type (const char *directive,
			 const char *str,
			 PACKAGE_INFO *package)
{
  return(dsm_parse_type_internal(str, 1, package));
}

/* ----------------------
 * - dsm_parse_multiple -
 * ---------------------- */

/* This handles directives that occur multiple times in a generic manner.
 * 'max' specifies the maximum index that should be used into 'aa'. */

int
dsm_parse_multiple (const char *directive, const char *str, char **aa,
		    const int max)
{
  int i, found;

  for (found = -1, i = 0; i < max; i++) {
    if (aa[i] == NULL) {
      found = i;
      break;
    }
  }
  
  if (found == -1) {
    /* There are too many items already. */
    return(DSM_TOO_MANY);
  }

  aa[found] = strdup(str);
  return(DSM_OK);
}

/* ------------------------------
 * - dsm_parse_duplicate_action -
 * ------------------------------ */

int
dsm_parse_duplicate_action (const char *directive, const char *str,
			    PACKAGE_INFO *package)
{
  char *buf = strdup(str);
  
  strlwr(buf);

  if (strcmp(str, "replace") == 0)
    package->duplicate_action = DUP_REPLACE;
  else if (strcmp(str, "keep") == 0)
    package->duplicate_action = DUP_KEEP;
  else if (strcmp(str, "backup") == 0)
    package->duplicate_action = DUP_BACKUP;
  else if (strcmp(str, "skip") == 0)
    package->duplicate_action = DUP_SKIP;
  else
    /* Default to querying */
    package->duplicate_action = DUP_QUERY;

  free(buf);
  return(DSM_OK);
}

/* -------------------------------
 * - dsm_parse_short_description -
 * ------------------------------- */

int
dsm_parse_short_description (const char *directive, const char *str,
			     PACKAGE_INFO *package)
{
  size_t s = sizeof(package->short_description);
	
  strncpy(package->short_description, str, s);
  package->short_description[s - 1] = '\0';
  return(dsm_parse_escapes(package->short_description));
}

/* -------------------------------
 * - dsm_parse_short_description -
 * ------------------------------- */

int
dsm_parse_long_description (const char *directive, const char *str,
			    PACKAGE_INFO *package)
{
  package->long_description = strdup(str);
  return(dsm_parse_escapes(package->long_description));
}

/* ---------------------
 * - dsm_parse_escapes -
 * --------------------- */

/* This parses escape characters in str, according to the DSM spec. It assumes
 * that the escape characters expand to less characters than the actual
 * escape. It works directly on str. */

int
dsm_parse_escapes (const char *str)
{
  char       esc   = 0;		/* Current escape character */
  char       repl  = 0;		/* Replacement character    */
  const char *head = str;	/* Start of the string      */
  char       *p    = NULL;	/* Current "tail" portion   */

  for (p = strchr(head, '\\'); p != NULL; p = strchr(p, '\\')) {
    /* Which escape is it? */
    esc = *(p + 1);
		
    switch(esc) {
    case 'n':
      repl = '\n';
      break;

    case 't':
      repl = '\t';
      break;

    case '\\':
      repl = '\\';
      break;

      /* Unknown escape => barf */
    default:
      return(DSM_BAD_ESCAPE);
      break;
    }

    /* Now do the replacement. */
    *p       = repl;
    *(p + 1) = '\0'; /* Terminate for safety */
    
    if (strlen(p + 2) > 0)
      memmove(p + 1, p + 2, strlen(p + 2));
  }

  return(DSM_OK);
}

/* ----------------------------
 * - required_directive_error -
 * ---------------------------- */

static DSM_ERROR *
required_directive_error (const char *d)
{
  DSM_ERROR *err = NULL;

  err = (DSM_ERROR *) calloc(1, sizeof(*err));
  if (err == NULL) return(NULL);
	
  err->q_forw = err->q_back = NULL;
	
  err->ret         = DSM_REQUIRED_DIRECTIVE;
  err->line_number = 0;
  err->directive   = strdup(d);
  err->data        = NULL;
	
  return(err);
}

/* -------------------
 * - bad_value_error -
 * ------------------- */

static DSM_ERROR *
bad_value_error (const char *d, const char *msg)
{
  DSM_ERROR *err = NULL;

  err = (DSM_ERROR *) calloc(1, sizeof(*err));
  if (err == NULL) return(NULL);
	
  err->q_forw = err->q_back = NULL;
	
  err->ret         = DSM_BAD_VALUE;
  err->line_number = 0;
  err->directive   = strdup(d);
  err->data        = strdup(msg);
	
  return(err);
}

/* --------------
 * - file_error -
 * -------------- */

/* Generate an error for a file-related problem. */

static DSM_ERROR *
file_error (const int ret, const char *msg)
{
  DSM_ERROR *err = NULL;

  err = (DSM_ERROR *) calloc(1, sizeof(*err));
  if (err == NULL) return(NULL);
	
  err->q_forw = err->q_back = NULL;
	
  err->ret         = ret;
  err->line_number = 0;
  err->directive   = NULL;

  if (msg != NULL)
    err->data = strdup(msg);
	
  return(err);
}

/* -------------
 * - dsm_check -
 * ------------- */

/*
 * This function checks that the DSM contains the required fields. If it does,
 * 1 is returned. Otherwise, 0 is returned.
 */

int
dsm_check (PACKAGE_INFO *package, DSM_ERROR **de)
{
  DSM_ERROR *err  = NULL; /* Error info, for analysis of problem.   */
  DSM_ERROR *ferr = NULL; /* First error info struct, head of list. */
  DSM_ERROR *lerr = NULL; /* Last error info struct, end of list.   */
  int        ok   = 1;    /* !ok, if required field missing.        */

  /* Need an error list pointer */
  if (de == NULL)
    return(0);

  /* Set up ferr, lerr */  
  if (*de != NULL) {
    ferr = *de;
    for (lerr = *de; lerr->q_forw != NULL; lerr = lerr->q_forw) {;}
  }

  /* dsm-author */
  if (package->dsm_author == NULL) {
    err = required_directive_error("dsm-author");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }
		
    ok = 0;
  }

  /* dsm-file-version */
  if (!package->dsm_file_version.has_version) {
    err = required_directive_error("dsm-file-version");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }
		
    ok = 0;
  }

  /* dsm-version */
  if (!package->dsm_version.has_version) {
    err = required_directive_error("dsm-version");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }
		
    ok = 0;
  }

  /* dsm-name */
  if (strlen(package->dsm_name) == 0) {
    err = required_directive_error("dsm-name");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }
		
    ok = 0;
  }

  /* type */
  if (!package->version.has_type || (package->version.type == TYPE_NONE)) {
    err = required_directive_error("type");
    
    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }
 
    ok = 0;
  }

  /* name */
  if (strlen(package->name) == 0) {
    err = required_directive_error("name");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }
		
    ok = 0;
  }

  /* version */
  if (!package->version.has_version) {
    err = required_directive_error("version");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }
		
    ok = 0;
  }

  /* manifest */
  if (   (strlen(package->manifest) == 0)
      && package->version.has_type
      && (   (package->version.type == TYPE_BINARIES)
          || (package->version.type == TYPE_DOCUMENTATION)
          || (package->version.type == TYPE_SOURCES))) {
    err = required_directive_error("manifest");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }
		
    ok = 0;
  }

  /* binaries-dsm, sources-dsm, documentation-dsm should not contain
   * a '.dsm' extension. */
  if (   (package->binaries_dsm != NULL)
      && (strchr(package->binaries_dsm, '.') != NULL)) {
    err = bad_value_error("binaries-dsm", "file extension present");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }

    ok = 0;
  }
	
  if (   (package->sources_dsm != NULL)
      && (strchr(package->sources_dsm, '.') != NULL)) {
    err = bad_value_error("sources-dsm", "file extension present");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }

    ok = 0;
  }

  if (   (package->documentation_dsm != NULL)
      && (strchr(package->documentation_dsm, '.') != NULL)) {
    err = bad_value_error("documentation-dsm", "file extension present");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }

    ok = 0;
  }

  /* short-description */
  if (strlen(package->short_description) == 0) {
    err = required_directive_error("short-description");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }
		
    ok = 0;
  }

  /* zip */
  /* TODO: Change this when tar-gzip is supported. */
  if (   (package->zip[0] == NULL)
      && (package->version.type != TYPE_VIRTUAL)
      && (package->version.type != TYPE_GROUP) ) {
    err = required_directive_error("zip");

    if (err != NULL) {			
      if (lerr != NULL) {
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	ferr = err;
      }
      lerr = err;
    }
		
    ok = 0;
  }

  /* If there was no list before, point it at the new one. */
    if (*de == NULL)
      *de = ferr;

  if (ok)
    return(1);

  return(0);
}

/* -------------
 * - dsm_parse -
 * ------------- */

/*
 * This parses the DSM text stored in the buffer 'buf' and places the parsed
 * data into the package info structure 'package'. A copy of 'buf' for
 * parsing.
 *
 * On success DSM_OK is returned. On failure any parsed data in 'package'
 * is freed, 'de' contains a list of errors and DSM_FAILED is returned.
 */

int
dsm_parse (const char *buf, PACKAGE_INFO *package, DSM_ERROR **de)
{
  char         *dsm  = NULL;	/* Don't stamp all over buf! */
  char         *p    = NULL;
  char         *q    = NULL;
  char 	       *next = NULL;
  char         *end  = NULL;
  DSM_ERROR    *err  = NULL;	/* Error info, for analysis of problem.   */
  DSM_ERROR    *ferr = NULL;	/* First error info struct, head of list. */
  DSM_ERROR    *lerr = NULL;	/* Last error info struct, end of list.   */
  PACKAGE_DEP **deps = NULL;
  int i, parsed, ret, line_number, next_line_number, line_continued;
  int found = 0;		/* Directive parser found flag = 1=y, 0=n */

  memset(package, 0, sizeof(PACKAGE_INFO));
  package_set_defaults(package);

  dsm = strdup(buf);
  if (dsm == NULL) return(DSM_INTERNAL_ERROR);

  /* Set the parse success flag. Succeed by default. */
  parsed = 1;

  /* TODO: This code is a big hack. Rewrite it so there is an array
   * pointing to the start of each line. Then line continutation can be
   * handled nicely. The code will be clearer too. */
	
  /* The following code is a little nasty. The line separator is CR, LF
   * or CRLF. */
  for (end = dsm + strlen(dsm), next = p = dsm,
	 next_line_number = line_number = 1;
       p < end;
       p = next, line_number = next_line_number) {
    /* Support line continuation character - '\' - and perform some
     * character conversions, e.g. '\t' -> ' '. */
    for (line_continued = 0, q = p; q < end; q++) {
      if (*q == '\\') {
	switch(*(q + 1)) {
	case '\n':
	case '\r':
	  *q = ' ';
	  q++;
	  line_continued = 1;
	  break;

	default:
	  /* Let the escaping code handle it. */
	  break;
	}
      }

      /* Convert all tabs to spaces. */
      if (*q == '\t') *q = ' ';

      while ((*q == '\n') || (*q == '\r')) {
	if (line_continued) {
	  /* This avoids adding unwanted whitespace to the description by
	   * jiggling the memory about to nuke the CRs & LFs. */
	  /* TODO: Well, it would, if it worked. :( */
	  memmove((void *) q, (void *) (q + 1), strlen(q + 1) + 1);
	  end--;
	} else {
	  break;
	}
      }
   
      /* Non-whitespace character indicates that
       * the continuation character wasn't. */
      if (!isspace(*q)) line_continued = 0;
    }
		
    /* Need to keep track now, before the following code stamps
     * on the buffer. */
    while ((*next != '\r') && (*next != '\n') && (*next != '\0') ) {
      next++;
    }

    /* TODO: This causes problems with the line numbering - two or more lines
     * can be skipped, but will not be counted. */
    while ((*next == '\r') || (*next == '\n')) {
      /* Eat CRs in CRLFs */
      if ((*next == '\r') && (*(next + 1) == '\n')) {
	*next = '\0';
	next++;
      }

      *next = '\0';
      next++;
      next_line_number++;
    }

    /* Pre-process the line - remove whitespaces @ start & end */
    while (isspace(*p)) { p++; }
    rtrim(p);

    /* Comment or a blank line? */
    if ((*p == '#') || (*p == '\0')) continue;

    /* Split the line in two so that p contains the directive name
       and q contains the value. */
    q = strchr(p, ':');
    if (q == NULL) {
      ret = DSM_BAD_DIRECTIVE;
    } else if (strcmp(q, ":") == 0) {
      /* Skip empty directives */
      /* TODO: Generate warning */
      ret = DSM_OK;
    } else {
      *q = '\0', q++;
      while (isspace(*q)) { q++; }
      strlwr(p);

      /* Parse the directive appropriately */
      for (ret = DSM_OK, found = i = 0; dsm_parser[i].directive != NULL; i++) {
	if (strcmp(p, dsm_parser[i].directive) == 0) {
	  /* Use the appropriate parser. */
	  found = 1;
	  ret   = dsm_parser[i].parse(p, q, package);
	  break;
	}
      }

      /* Parsing failed? */		
      if (!found) ret = DSM_UNKNOWN_DIRECTIVE;
    }		

    /* Parsing failed, so store info in DSM error
     * structure. */
    if (ret != DSM_OK) {
      err = (DSM_ERROR *) calloc(1, sizeof(*err));
      if (err == NULL) break;

      err->q_forw = err->q_back = NULL;
				
      if (lerr != NULL) {
	/* If we have a queue, tag this on the end. */
	insque((struct qelem *) err, (struct qelem *) lerr);
      } else {
	/* This entry is the queue head. */
	ferr = err;
      }

      err->ret         = ret;
      err->line_number = line_number;
      err->directive   = strdup(p);

      if (q != NULL)
	err->data = strdup(q);
				
      lerr   = err;
      parsed = 0;
    }
  }

  /* Tidy up */
  free(dsm), dsm = NULL;

  /* - Now check if we have all required fields. - */
  if (!dsm_check(package, &ferr))
    parsed = 0;

  /* TODO: Check that we have the same or less *-email as the corresponding
   * * fields (e.g. maintainer-email vs. maintainer. If not, generate
   * a warning. */

  /* If the dependency has no type in its version, default to matching
   * a binary package. */
  /* TODO: This is a HACK! Fix it by specifying that dependencies
   * can have types in the DSM specification. */
  for (deps = package->deps; *deps != NULL; deps++) {
    if ((*deps)->version.has_version && !(*deps)->version.has_type) {
      (*deps)->version.has_type = 1;
      (*deps)->version.type     = TYPE_BINARIES;
    }
  }

  /* Processing done - successful? */
  if (parsed) {
    /* The package has a valid DSM. */
    package->has_dsm = 1;		

    /* Set other internal flags. */
    package->visited = 0;

    /* No errors */
    assert(ferr == NULL);
    *de = NULL;
  } else {
    *de = ferr;
  }

  /* Parsing failed => free data in package structure. */
  if (!parsed)
    package_free(package);

  /* OK */
  return(parsed ? DSM_OK : DSM_FAILED);
}

/* ----------------
 * - dsm_load_dir -
 * ---------------- */

/* This parses all the DSMs that can found in the 'dir'. If 'packages' is 
 * non-null, then any parsed packages will be added to the package list.
 * Otherwise, a new list will created. The package list is returned on 
 * success, else NULL. */

PACKAGE_INFO *
dsm_load_dir (const char *dir, PACKAGE_INFO *packages, DSM_FILE_ERROR **dfe)
{
  PACKAGE_INFO *list       = packages;
  PACKAGE_INFO *newpackage = NULL;
  char mypath[PATH_MAX], dsmfile[PATH_MAX];

  DSM_FILE_ERROR *dsm_flist = *dfe;  /* Err's in parsing of all files. */
  DSM_FILE_ERROR *dsm_ferr  = NULL;  /* Struct with file parse errors. */
  DSM_ERROR       *dsm_err   = NULL; /* Err's in parsing for file.     */
  char            *dsm       = NULL;
	
  DIR *d = NULL;
  struct dirent *de = NULL;
  int ret;

  /* Check params */
  if (dir == NULL) return(list);

  /* Format the path name for later */
  strcpy(mypath, dir);
  addforwardslash(mypath);

  d = opendir(mypath);
  if (!d) return (list);

  while ( (de = readdir(d)) != NULL ) {
    /* Suitable file name? */
    if (strcmp(de->d_name, ".") == 0) continue;
    if (strcmp(de->d_name, "..") == 0) continue;
    if (!isdsm(de->d_name)) continue;
    if (strstr(de->d_name, ".dsm") == de->d_name) continue;

    /* Set up the new package info struct. */
    newpackage = (PACKAGE_INFO *)calloc(sizeof(*newpackage), 1);
			
    if (newpackage == NULL) {
      warn("Unable to allocate memory!");
      continue;
    }
			
    /* Parse the package file */
    strcpy(dsmfile, mypath);
    strcat(dsmfile, de->d_name);

    dsm     = read_text_file_to_memory(dsmfile);
    dsm_err = NULL;
			
    if (dsm != NULL) {
      ret = dsm_parse(dsm, newpackage, &dsm_err);
    } else {
      /* Blank DSM => warning */
      dsm_err = calloc(sizeof(*dsm_err), 1);
      if (dsm_err == NULL) {
	warn("Unable to allocate memory!");

	/* Tidy up */
	free(newpackage), newpackage = NULL;
					
	continue;
      }

      dsm_err->ret         = DSM_FAILED;
      dsm_err->line_number = 0;
      dsm_err->directive   = NULL;
      dsm_err->data        = NULL;

      ret = DSM_FAILED;
    }

    free(dsm), dsm = NULL;
			
    if (ret != DSM_OK) {
      /* Store errors with associated file in
       * list. */
      dsm_ferr = malloc(sizeof(*dsm_ferr));
      if (dsm_ferr == NULL) {
	warn("Unable to allocate memory!");

	/* Tidy up */
	package_free(newpackage);
	free(newpackage), newpackage = NULL;
					
	continue;
      }

      dsm_ferr->name = strdup(de->d_name);
      dsm_ferr->de   = dsm_err;
				
      if (dsm_flist == NULL) {
	/* Create list. */
	*dfe = dsm_flist = dsm_ferr;
	(*dfe)->q_forw  = NULL;
	(*dfe)->q_back  = NULL;
      } else {
	insque((struct qelem *) dsm_ferr, (struct qelem *) dsm_flist);
      }

      /* Tidy up */
      package_free(newpackage);
      free(newpackage), newpackage = NULL;
				
      continue;
    }
			
    /* Add package to list. If the list doesn't exist,
     * create it. */
    if (list == NULL)
      list = newpackage;
    else 
      packlist_add(list, newpackage);
		
  }

  closedir(d);

  return(list);
}

/* ----------------
 * - dsm_load_all -
 * ---------------- */

/* This parses all the DSMs that can found in the array of paths 'path'. If
 * 'packages' is non-null, then any parsed packages will be added to the
 * package list. Otherwise, a new list will created. The package list is
 * returned on success, else NULL. */

PACKAGE_INFO *
dsm_load_all (const char **path, PACKAGE_INFO *packages, DSM_FILE_ERROR **dfe)
{
  int i;
  PACKAGE_INFO * list = packages;

  /* Check params */
  if (path == NULL) 
    return list;

  /* Go through all the spec'd directories looking for '.dsm's */
  for (i = 0; path[i] != NULL; i++) {
    list = dsm_load_dir(path[i], list, dfe);
  }

  return(list);
}

/* --------------
 * - dsm_perror -
 * -------------- */

/* This function formats and displays an error message appropriate to the
 * passed error structure. */

void
dsm_perror (const char *str, const DSM_ERROR *de)
{
  if (de == NULL) return;
	
  switch(de->ret) {
  default:
    fprintf(stderr,
	    "%s: Line %d: Unknown error\n",
	    str, de->line_number);
    break;

  case DSM_FAILED:
    fprintf(stderr, "%s: Failed\n", str);
    break;

  case DSM_OK:
    fprintf(stderr, "%s: No error\n", str);
    break;

  case DSM_NONEXISTENT:
    fprintf(stderr, "%s: '%s' does not exist\n", str, str);
    break;

  case DSM_EMPTY:
    fprintf(stderr, "%s: Empty\n", str);
    break;

  case DSM_ARCHIVE_HAS_NO_DSM:
    fprintf(stderr, "%s: '%s' does not contain a DSM\n", str, str);
    break;

  case DSM_BAD_DIRECTIVE:
    fprintf(stderr,
	    "%s: Line %d: Bad directive '%s'\n",
	    str, de->line_number, de->directive);
    break;

  case DSM_UNKNOWN_DIRECTIVE:	       
    fprintf(stderr,
	    "%s: Line %d: Unknown directive '%s'\n",
	    str, de->line_number, de->directive);
    break;

  case DSM_REQUIRED_DIRECTIVE:
    fprintf(stderr,
	    "%s: Required directive '%s' missing\n",
	    str, de->directive);
    break;

  case DSM_TOO_MANY:
    fprintf(stderr,
	    "%s: Line %d: Too many occurrences of '%s' - array full\n",
	    str, de->line_number, de->directive);
    break;

  case DSM_BAD_VERSION_FORMAT:
    fprintf(stderr,
	    "%s: Line %d: Bad version format '%s' for directive '%s'\n",
	    str, de->line_number, de->data, de->directive);
    break;

  case DSM_BAD_TYPE:
    fprintf(stderr,
	    "%s: Line %d: Bad type '%s' for directive '%s'\n",
	    str, de->line_number, de->data, de->directive);
    break;

  case DSM_BAD_ESCAPE:
    fprintf(stderr,
	    "%s: Line %d: Bad escape in '%s' for directive '%s'\n",
	    str, de->line_number, de->data, de->directive);
    break;

  case DSM_BAD_VALUE:
    fprintf(stderr,
	    "%s: Bad value for directive '%s': %s\n",
	    str, de->directive, de->data);
    break;

  case DSM_BAD_ARCHIVE:
    fprintf(stderr, "%s: Bad archive\n", str);
    break;

  case DSM_PACKAGE_UNSUPPORTED:
    fprintf(stderr, "%s: Package unsupported\n", str);
    break;

  case DSM_TAR_GZ_UNSUPPORTED:
    fprintf(stderr, "%s: .tar.gz packages unsupported\n", str);
    break;

  case DSM_TAR_BZ2_UNSUPPORTED:
    fprintf(stderr, "%s: .tar.bz2 packages unsupported\n", str);
    break;

  case DSM_URL_UNSUPPORTED:
    fprintf(stderr, "%s: URLs unsupported\n", str);
    break;

  case DSM_NOT_UNDERSTOOD:
    fprintf(stderr,
	    "%s: Line %d: DSM specification version '%s' is not understood\n"
	    "%s: Perhaps you need to upgrade your packaging program?\n",
	    str, de->line_number, de->data, str);
    break;
		
  case DSM_INTERNAL_ERROR:
    fprintf(stderr,
	    "%s: Line %d: Internal error (directive='%s',data='%s')\n",
	    str, de->line_number, de->directive, de->data);
    break;
  }
}

/* -----------------------
 * - dsm_free_error_list -
 * ----------------------- */

/* This frees the error data returned by dsm_parse(). */

int
dsm_free_error_list (const DSM_ERROR *de)
{
  DSM_ERROR *h = (DSM_ERROR *) de; /* List head pointer         */
  DSM_ERROR *p = NULL, *n = NULL;  /* Current and next pointers */
	
  /* Simple case */
  if (de == NULL) return(1);

  /* Traverse the linked list to free all child data. */
  for (p = h; p != NULL; p = p->q_forw) {
    free(p->directive), p->directive = NULL;
    free(p->data),      p->data      = NULL;
  }

  /* Remove all the list elements. */
  for (p = h; p != NULL; p = n) {		
    n = p->q_forw;			/* Save next pointer */
    remque((struct qelem *) p);	/* Remove from queue */
    free(p);
  }

  return(1);
} 

/* ----------------------------
 * - dsm_free_file_error_list -
 * ---------------------------- */

/* This is similar to dsm_free_error_list(), except that each error list is
 * associated with the file that caused the error. There is only slightly
 * more work to be done. */

int
dsm_free_file_error_list (const DSM_FILE_ERROR *dfe)
{
  DSM_FILE_ERROR *h = (DSM_FILE_ERROR *) dfe; /* List head pointer         */
  DSM_FILE_ERROR *p = NULL, *n = NULL;	      /* Current and next pointers */
	
  /* Simple case */
  if (dfe == NULL) return(1);

  /* Traverse the linked list to free all child data. */
  for (p = h; p != NULL; p = p->q_forw) {
    free(p->name),              p->name = NULL;
    dsm_free_error_list(p->de), p->de   = NULL;
  }

  /* Remove all the list elements. */
  for (p = h; p != NULL; p = n) {		
    n = p->q_forw;			/* Save next pointer */
    remque((struct qelem *) p);	/* Remove from queue */
    free(p);
  }
	
  return(1);
}

/* ---------------------
 * - dsm_get_and_parse -
 * --------------------- */

/*
 * This function gets and parses DSM from all supported places - DSM file,
 * ZIP package, etc. 'where' specifies the source file (DSM, ZIP, etc.);
 * 'package' is where the parsed information is placed. If the caller wishes
 * to have a copy of the DSM text, 'dsm' should point to a char pointer -
 * this will be updated to point to the DSM text. 'de' should to point to
 * a DSM_ERROR pointer, which will be updated to point to a list of errors.
 *
 * On success DSM_OK is returned. Otherwise one of the other DSM_* error
 * codes is returned.
 */

int
dsm_get_and_parse (const char *where,  PACKAGE_INFO *package, char **dsm,
		   DSM_ERROR **de)
{
  char ** a_toc    = NULL;
  char  * dsm_text = NULL;
  int     i;
  char  * p        = NULL;
  int     ret;

  /* Is 'where' a directory or special file? */
  if ((isdir(where) > 0) || isspecialpath(where)) {
    *de = file_error(DSM_PACKAGE_UNSUPPORTED, NULL);
    return DSM_PACKAGE_UNSUPPORTED;
  }

  /* Does 'where' exist? */
  if (access(where, R_OK) != 0) {
    *de = file_error(DSM_NONEXISTENT, NULL);
    return DSM_NONEXISTENT;
  }

  /* Try to recognize what did we get - if we can't, we have
   * an unsupported package type. */
  if (!(isdsm(where) || isarchive(where) || isurl(where))) {
    *de = file_error(DSM_PACKAGE_UNSUPPORTED, NULL);
    return DSM_PACKAGE_UNSUPPORTED;
  }

  /* We don't support .tar.gz, .tar.bz2 or URL yet. */
  if (istargz(where)) {
    *de = file_error(DSM_TAR_GZ_UNSUPPORTED, NULL);
    return DSM_TAR_GZ_UNSUPPORTED;
  }
  
  if (istarbz2(where)) {
    *de = file_error(DSM_TAR_BZ2_UNSUPPORTED, NULL);
    return DSM_TAR_BZ2_UNSUPPORTED;
  }

  if (isurl(where)) {
    *de = file_error(DSM_URL_UNSUPPORTED, NULL);
    return DSM_URL_UNSUPPORTED;
  }

  /* Plain DSM file */
  if (isdsm(where)) {
    /* Read the DSM into a buffer. */
    dsm_text = read_text_file_to_memory(where);
    if (!dsm_text) {
      *de = file_error(DSM_NONEXISTENT, NULL);
      return DSM_NONEXISTENT;
    }
  } else if (isarchive(where)) {
    /* Find the DSM in the archive and read it into a buffer. 
     * Look for the DSM in the root of the archive or in the
     * 'manifest/' directory. */
    a_toc = archive_get_toc(where);
    if (!a_toc) {
      *de = file_error(DSM_BAD_ARCHIVE, NULL);
      return DSM_BAD_ARCHIVE;
    }

    for (i = 0; a_toc[i] != NULL; i++) {
      /* Skip non-DSMs, directories */
      if (!isdsm(a_toc[i]) || ends_with_slash(a_toc[i]))
	continue;

      if (strchr(a_toc[i], '/') != NULL) {
	/* Manifest directory? */
	if (strncasecmp(a_toc[i], "manifest/", 9) != 0)
	  continue;
	
	p = a_toc[i] + 9;
	if (strchr(p, '/') != NULL) continue;
      }

      dsm_text = archive_extract_text_file_to_memory(where, a_toc[i]);
      break;
    }

    /* Free the TOC */
    for (i = 0; a_toc[i] != NULL; i++) { free(a_toc[i]); }
    free(a_toc), a_toc = NULL;

    /* Found DSM? */
    if (!dsm_text) {
      *de = file_error(DSM_ARCHIVE_HAS_NO_DSM, NULL);
      return DSM_ARCHIVE_HAS_NO_DSM;
    }
  }

  /* No text in DSM? */
  if (dsm_text == NULL) {
    *de = file_error(DSM_NONEXISTENT, NULL);
    return DSM_NONEXISTENT;
  }

  /* Now parse the DSM */
  ret = dsm_parse(dsm_text, package, de);

  /* If the caller is interested, return DSM text. */
  if ((ret == DSM_OK) && (dsm != NULL))
    *dsm = dsm_text;
  else
    free(dsm_text);

  return(ret);
}
