/* $Id: rcfile.c,v 1.13 2002/06/23 20:28:31 richdawe Exp $ */

/*
 *  rcfile.c - Configuration file functions for pakke
 *  Copyright (C) 1999-2002 by Richard Dawe
 *      
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * TODO: Update lexer to understand this format:
 *
 * Parse a line of the following form:
 *
 * hostspec := <host>[(:<port>)|( [[on ]port ]<port>)]
 *
 * proxy (ftp|http) [using|with] <hostspec>[( or|,) <hostspec> [...]]
 *
 * This looks like a mouthful, but ends up being near to an English sentence,
 * e.g. "proxy ftp with myproxy on port 8080". The list of addresses is
 * returned as "<host>:<port>" strings, dynamically allocated (i.e. free()
 * it!).
 */

#include "common.h"

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <limits.h>

/* libpakke includes */
#include <libpakke/util.h>

#include "rcfile.h"

static char rcfilename[PATH_MAX];
static int rcfile_inited = 0;

/* These are used for substitutions, */
static char pakke_root[PATH_MAX];
static char pakke_prefix[PATH_MAX];
static char pakke_download_prefix[PATH_MAX];

/* These store paths to resources, parsed from the configuration file(s). */
static char **p_installed_dsm = NULL;
static char **p_installed_mft = NULL;
static char **p_available_dsm = NULL;
static char **p_zip           = NULL;
static char **p_tar_gzip      = NULL;
static char **p_tar_bzip2     = NULL;
static char **p_http_proxies  = NULL;
static char **p_ftp_proxies   = NULL;
static char **p_http_mirrors  = NULL;
static char **p_ftp_mirrors   = NULL;
static char **p_locations     = NULL;

/* Valid substitutions, e.g. @PAKKE-ROOT@. */
enum {
  SUBST_NONE = 0,
  SUBST_PAKKE_ROOT,
  SUBST_PAKKE_DOWNLOAD
};

typedef struct {
  int         type;
  const char *name;
} subst_t;

static subst_t substs[] = {
  { SUBST_PAKKE_ROOT,     "PAKKE-ROOT" },
  { SUBST_PAKKE_DOWNLOAD, "PAKKE-DOWNLOAD" },
  { SUBST_NONE,           NULL }
};

/* --------------
 * - add_string -
 * -------------- */

/* Convenience function for adding items to NULL-terminated string arrays. */

static int inline
add_string (char ***strings, char *s)
{
  char **x = *strings;
  int i = 0;

  /* Need to allocate space for entry plus NULL terminator. */
  if (x == NULL) {
    x = malloc(sizeof(char *) * 2);
  } else {
    for (i = 0; x[i] != NULL; i++) {;} /* NULL not included in count */
    x = realloc(x, sizeof(char *) * (i + 2));    
  }

  if (x == NULL)
    return(0);

  *strings = x;
  x[i] = s;
  x[i + 1] = NULL;

  return(1);
}

/* ----------------
 * - free_strings -
 * ---------------- */

/* Convenience function for tidying up NULL-terminated string arrays. */

static int inline
free_strings (char ***strings)
{
  char **x = *strings;
  int i;

  if (x != NULL) {
    for (i = 0; (x)[i] != NULL; i++) {
      free((x)[i]);
    }
    free(x);
    *strings = NULL;
  }
  return(1);
}

/* ------------
 * - res2list -
 * ------------ */

/* Convenience function for mapping rc_resource_t value to a list pointer. */

static char ***
res2list (const rc_resource_t res)
{
  switch(res) {
  case RC_INSTALLED_MFT: return(&p_installed_mft);
  case RC_INSTALLED_DSM: return(&p_installed_dsm);
  case RC_AVAILABLE_DSM: return(&p_available_dsm);
  case RC_ZIP:           return(&p_zip);
  case RC_TAR_GZIP:      return(&p_tar_gzip);
  case RC_TAR_BZIP2:     return(&p_tar_bzip2);
  case RC_HTTP_PROXY:    return(&p_http_proxies);
  case RC_FTP_PROXY:     return(&p_ftp_proxies);
  case RC_HTTP_MIRROR:   return(&p_http_mirrors);
  case RC_FTP_MIRROR:    return(&p_ftp_mirrors);
  case RC_LOCATION:      return(&p_locations);

  case RC_NONE:
  default:
    return(NULL);
  }
}

/* ---------------
 * - rcfile_init -
 * --------------- */

static void
rcfile_uninit_wrapper (void)
{
  rcfile_uninit();
}

int
rcfile_init (const char *filename,
	     const char *root,
	     const char *prefix,
	     const char *download_prefix)
{
  /* Allow this to be called multiple times */
  if (rcfile_inited)
    rcfile_uninit();

  if (access(filename, R_OK) != 0)
    return(0);
  /* Assume root, prefix OK */

  strcpy(rcfilename, filename);
  strcpy(pakke_root, root);
  strcpy(pakke_prefix, prefix);
  strcpy(pakke_download_prefix, download_prefix);

  /* Set up the exit handler. */
  if (!rcfile_inited)
    atexit(rcfile_uninit_wrapper);

  /* Done */
  rcfile_inited++;

  return(rcfile_inited);
}

/* -----------------
 * - rcfile_uninit -
 * ----------------- */

int
rcfile_uninit (void)
{
  if (!rcfile_inited)
    return(1);

  /* Free parsed paths */
  free_strings(&p_installed_dsm);
  free_strings(&p_installed_mft);
  free_strings(&p_available_dsm);
  free_strings(&p_zip);
  free_strings(&p_tar_gzip);
  free_strings(&p_tar_bzip2);
  free_strings(&p_http_proxies);
  free_strings(&p_ftp_proxies);
  free_strings(&p_http_mirrors);
  free_strings(&p_ftp_mirrors);
  free_strings(&p_locations);

  rcfile_inited = 0;
  return(1);
}

/* ----------------
 * - rcfile_parse -
 * ---------------- */

int
rcfile_parse (void)
{  
  FILE *fp  = NULL;
  int   ret = 0;

  if (!rcfile_inited)
    return(0);

  fp = fopen(rcfilename, "rt");
  if (fp == NULL)
    return(0);

  /* Now run parser */
  rcfile_yyin = fp;
  ret = !rcfile_yyparse(); /* rcfile_yyparse() returns 0 on success */

  fclose(fp);

  return(ret);
}

/* ---------
 * - subst -
 * --------- */

/*
 * Substitute all occurences of 'name' for 'value' in buffer 'buf'.
 * On success, 1 is returned, otherwise 0. The function may fail, if
 * there is not enough space in 'buf' to perform the substitution.
 */

int
subst (char *buf, size_t buflen, const char *name, const char *value)
{
  char *p = NULL;

  if ((buf == NULL) || (buflen == 0) || (name == NULL) || (value == NULL))
    return(0);

  for (p = strstr(buf, name); p != NULL; p = strstr(buf, name)) {
    if ((*(p - 1) == '@') && (*(p + strlen(name)) == '@')) {
      /*
       * Substitute as a two stage process:
       *
       * <blah>@NAME@<blah2>
       *    -> <blah><-------gap--------><blah2>
       *    -> <blah><---substitution---><blah2>
       */

      /* Chop the middle part out */
      *(p - 1) = '\0';
      p += strlen(name);
      *p = '\0';
      p++;

      /* strcpy() can't copy overlapping areas, so use
       * memmove(). */
      memmove(p + strlen(value) - strlen(name) - 2, p, strlen(p) + 1);
      memcpy(buf + strlen(buf), value, strlen(value)); /* Don't copy nul! */
    }
  }

  return(1);
}

/* -----------------------
 * - rcfile_add_resource -
 * ----------------------- */

int
rcfile_add_resource (const rc_resource_t res, const char *path)
{
  char    buf[PATH_MAX];
  size_t  buflen = sizeof(buf);
  char   *p = NULL;
  char ***list = NULL;
  int     i;

  /* Work out which list to use */
  list = res2list(res);
  if (list == NULL)
    return(0);

  /* Make substitutions on the path, if there are any. */
  strcpy(buf, path);

  /* Substitutions from pakke-specific variables. */
  for (i = 0; substs[i].type != SUBST_NONE; i++) {
    switch(substs[i].type) {
    case SUBST_PAKKE_ROOT:
      if (!subst(buf, buflen, substs[i].name, pakke_root))
	return(0);
      break;

    case SUBST_PAKKE_DOWNLOAD:
      if (!subst(buf, buflen, substs[i].name, pakke_download_prefix))
	return(0);
      break;

    default:
      break;
    }
  }

  /* Substitutions from the environment. */
  p = strchr(buf, '@');
  while (p != NULL) {
    char      *q, *r;
    ptrdiff_t  d;
    int        ret;

    /* Find the terminating @ */
    q = strchr(p + 1, '@');
    if (q == NULL)
      break;

    /* Make @@ -> @, so we can have passwords in FTP & HTTP URLs,
     * among other things. */
    /* TODO: Why does the parser let this through? */
    if (q == (p + 1)) {
      memmove(p, q, strlen(q) + 1);
      p = strchr(p + 1, '@');
      continue;
    }

    /* Find the name of the unsubstituted variable. */
    p++;
    d = q - p;
    r = malloc((ptrdiff_t) d + 1 /* nul */);
    memcpy(r, p, (ptrdiff_t) d);
    r[d] = '\0';

    /* Now substitute */
    if (getenv(r))
      ret = subst(buf, buflen, r, getenv(r));
    else
      ret = subst(buf, buflen, r, "");

    free(r);
    if (!ret)
      return(0);

    /* Next! */
    p = strchr(p, '@');
  }

  /* Do we have anything left, after substitution? If not, quit now. */
  if (strlen(buf) == 0)
    return(1);

  /* Now add to the list */
  p = strdup(buf);
  if (p == NULL)
    return(0);
  
  if (!add_string(list, p)) {
    free(p);
    return(0);
  }

  return(1);
}

/* ------------------------
 * - rcfile_get_resources -
 * ------------------------ */

char **
rcfile_get_resources (rc_resource_t res)
{
  char ***list = NULL;

  list = res2list(res);
  if (list == NULL)
    return(NULL);  

  return(*list);
}

/* ------------------
 * - rcfile_yyerror -
 * ------------------ */

/* Parser support routine */

void
rcfile_yyerror (char *s)
{
  fprintf(stderr, "%s:%d: %s\n", rcfilename, rcfile_yylineno, s);
}
