/* archives.c:
 *
 * vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "hackerlab/os/errno.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fmt/cvt.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/mem/talloc.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/rx-posix/regex.h"
#include "hackerlab/sort/qsort.h"
#include "hackerlab/vu/safe.h"
#include "libfsutils/tmp-files.h"
#include "libfsutils/file-contents.h"
#include "libfsutils/read-line.h"
#include "libfsutils/dir-listing.h"
#include "libinifile/inifile.h"
#include "po/gettext.h"
#include "libawk/relational.h"
#include "libawk/trim.h"
#include "libarch/my.h"
#include "libarch/namespace.h"
#include "libarch/archive-version.h"
#include "libarch/cached-archive.h"
#include "libarch/debug.h"
#include "libarch/archives.h"

static t_uchar const * defaults_blurb(void);


t_uchar *
arch_archive_location_file (t_uchar const * archive_name)
{
  t_uchar * location_dir = 0;
  t_uchar * answer = 0;

  if (! arch_valid_archive_name(archive_name)) 
    {
      int ign;

      printfmt (&ign, 2, "Invalid archive name `%s'.\n", archive_name);
      exit (2);
    }

  location_dir = arch_my_archive_locations_dir ();
  answer = file_name_in_vicinity (0, location_dir, archive_name);

  lim_free (0, location_dir);

  return answer;
}

/**
 * \brief return the file path that a particular archives registry file will be found at
 */
t_uchar *
arch_archive_registry_file (t_uchar const *official_name)
{
  t_uchar *my_archives_dir;
  my_archives_dir = arch_my_archives_dir();
  invariant (str_length (official_name));
  return str_replace (my_archives_dir, file_name_in_vicinity (0, my_archives_dir, official_name));
}

/**
 * \brief determine if official_name has any registry-style configuration details 
 * \param official_name the archive to query
 * \return non zero if the arch does have registry style config details
 */
int 
arch_archives_has_registry_entry (t_uchar const *official_name)
{
    t_uchar *entry_file = arch_archive_registry_file (official_name);
    int result = safe_access (entry_file, F_OK) == 0;
    lim_free (0, entry_file);
    return result;
}

t_uchar *
archive_defaults_path (void)
{
    return arch_archive_registry_file ("defaults");
}

t_uchar const *
defaults_blurb()
{
    return _("# This file lists defaults for archive configurations.\n"
             "#It is useful to list defaults for GPG signing here.\n"
             "#anything that goes in an archive registry can be placed in this config file.\n"
             "#To override a single valued setting (like gpg_command) in a per-archive configuration,\n"
             "#just set it again in a per-archive configuration file.\n"
             "#To overide a multiple-valued setting (like URL), use an empty entry\n"
             "#which will reset that to the built-in defaults. I.e. URL= would empty the list of locations\n"
             "#for an archive.\n"
             "#To create a per-archive configuration file, if you are upgrading from baz < 1.3, or tla\n"
             "#Run baz upgrade. Other wise, you can just edit ~/.arch-params/archives/archive-name\n"
             "#(This file is created automatically by baz upgrade --all).\n");
}

/**
 * \brief helper function for getting a match from a regex pattern
 *
 * FIXME: 20050309 put this in libarch somewhere.
 * \param pattern a cached regex pattern
 * \param pattern_string the regex string to compile if needed
 * \param input the string to match against
 * \param match the nth match to return
 */
static t_uchar *
regex_get_match (regex_t **pattern, t_uchar const *pattern_string, t_uchar const *input, unsigned int match)
{
  regmatch_t matches[match];
  int regexres;

  if (!*pattern)
    {
      int re_error;
      *pattern = lim_malloc (0, sizeof (**pattern));
      re_error = regcomp (*pattern, pattern_string, REG_EXTENDED);
      invariant (!re_error);
    }
  if ((regexres = regexec (*pattern, input, match, matches, 0)))
    {
      char buf[50];
      if (regexres == REG_NOMATCH)
          return NULL;
      regerror (regexres, *pattern, buf, 50);
      safe_printfmt (2, "failed during regex match for section name: %d %s\n", regexres, buf);
      return NULL;
    }
  return str_save_n (0, input + matches[match - 1].rm_so, matches[match - 1].rm_eo - matches[match - 1].rm_so);
}

/**
 * \brief upgrade a signing from from old_path into inifile 
 */
void
upgrade_signing_rule (inifile_t *inifile, t_uchar const *old_default_path, int is_default)
{
#define SIGNINGPATTERN "^[[:space:]]*([^[:space:]]*)[[:space:]]*(((.*)--clearsign[[:space:]]*)?(.*))?$"
    t_uchar *old_default = file_contents (old_default_path);
    regex_t * pattern = NULL;
    t_uchar *gpg_command = regex_get_match (&pattern, SIGNINGPATTERN, old_default, 2);
    t_uchar *gpg_options1 = regex_get_match (&pattern, SIGNINGPATTERN, old_default, 5);
    t_uchar *gpg_options2 = regex_get_match (&pattern, SIGNINGPATTERN, old_default, 6);
    t_uchar *gpg_options = NULL;
    gpg_options1 = trim_surrounding_ws (gpg_options1);
    gpg_options2 = trim_surrounding_ws (gpg_options2);
    if (str_length (gpg_options1))
        gpg_options = str_save (0, gpg_options1);
    if (str_length (gpg_options2))
        gpg_options = trim_surrounding_ws(str_replace (gpg_options, str_alloc_cat_many (0, gpg_options1, " ", gpg_options2, str_end)));
    if (str_chr_index (gpg_options1, '\n') || str_chr_index (gpg_options2, '\n'))
      {
        safe_printfmt (2, _("Cannot upgrade ~/.arch-params/signing/=default as it is an unhandled shell script.\n"
                            "Please extract its contents to a real executable shell script and set gpg_command=my_shell_script\n"
                            "in ~/.arch-params/archives/defaults\n"));
        /* bah, can be bothered factoring these two lines out duplicated below */
        inifile_add_key (inifile, "", "", "", _("# Uncomment to use gnome-gpg"));
        inifile_add_key (inifile, "", "", "", "# gpg_command=gnome-gpg"); /* not translated - options aren't */
      }
    else if (is_default)
      {
        inifile_add_key (inifile, "", "", "", "# Imported default signing rules");

        if (str_length (gpg_command))
            inifile_add_key (inifile, "", "gpg_command", gpg_command, "");
        if (str_length (gpg_options))
            inifile_add_key (inifile, "", "gpg_options", gpg_options, "");
      }

    regfree (pattern);
    lim_free (0, old_default);
    lim_free (0, gpg_command);
    lim_free (0, gpg_options);
    lim_free (0, gpg_options1);
    lim_free (0, gpg_options2);
}

/**
 * \brief upgrade a checking rule from from old_path into inifile 
 */
void
upgrade_checking_rule (inifile_t *inifile, t_uchar const *old_check_path)
{
#define CHECKPATTERN "(^[^[:space:]]*gpg-check(\\.awk)?[[:space:]]+gpg_command[[:space:]]*=[[:space:]]*\"(/usr/bin/)?gpg[[:space:]]+--verify-files[[:space:]]+-([[:space:]]+2[[:space:]]*>[[:space:]]*/dev/null)?\"[[:space:]]*$)"
    t_uchar *old_check = file_contents (old_check_path);
    regex_t * pattern = NULL;
    t_uchar * match= regex_get_match (&pattern, CHECKPATTERN, old_check, 1);
    if (!match)
      {
        safe_printfmt (2, "WARNING: Unconvertable check script %s - please set checking policy by hand\n", old_check_path);
        inifile_add_key (inifile, "", "", "", "# Unconvertable check script was detected.");
        inifile_add_key (inifile, "", "", "", "# you should place your gpg options in ~/.gnupg/options");
        inifile_add_key (inifile, "", "", "", "# and use the allowed_ids and allowed_fingerprints archive ");
        inifile_add_key (inifile, "", "", "", "# settings to configure policy.");
      }
    regfree (pattern);
    lim_free (0, match);
    lim_free (0, old_check);
}

static t_uchar *
arch_archives_new_archive_ini_blurb (void)
{
    return _("# This ini file controls the configuration for one archive - \n"
             "# The archive is named by the same name as the name of this file\n");
}

/**
 * \brief save the ini file
 */
void
arch_archives_save_archive_ini_no_default (t_uchar const * official_name, inifile_t *inifile)
{
    t_uchar *archive_file = arch_archive_registry_file (official_name);
    inifile_save_merge (inifile, 0, archive_file);
    lim_free (0, archive_file);
}

/**
 * \brief load a defaults-free version of the inifile for a given archive
 *
 * if none exists, one will be created
 * \param official_name the official name of the archive.
 * \param inifile the inifile to load into, it will be inited during the load.
 */
void
arch_archives_get_archive_ini_no_default (t_uchar const * official_name, inifile_t *inifile)
{
    t_uchar *defaults = archive_defaults_path ();
    t_uchar *archive_file = arch_archive_registry_file (official_name);
    inifile_init (inifile);
    if (safe_access (archive_file, F_OK))
      {
        inifile_process_text (inifile, arch_archives_new_archive_ini_blurb());
        inifile_save_merge (inifile, 0, archive_file);
      }
    else
      {
        inifile_load (inifile, archive_file);
      }
    lim_free (0, defaults);
    lim_free (0, archive_file);
    return;
}

/**
 * \brief load in the inifile for a given archive
 *
 * \param official_name the official name of the archive.
 * \param inifile the inifile to load into, it will be inited during the load.
 */
void
arch_archives_get_archive_ini (t_uchar const *official_name, inifile_t *inifile)
{
    t_uchar *defaults = archive_defaults_path ();
    t_uchar *archive_file = arch_archive_registry_file (official_name);
    inifile_init (inifile);
    inifile_load (inifile, defaults);
    inifile_load (inifile, archive_file);
    lim_free (0, archive_file);
    lim_free (0, defaults);
    return;
}

static void
ensure_archives_defaults_exists (void)
{
  t_uchar *my_archives_dir;
  t_uchar *defaults_path;
  int errn;
  arch_ensure_my_arch_params();

  my_archives_dir = arch_my_archives_dir();
    
  if (vu_mkdir (&errn, my_archives_dir , 0700) && errn != EEXIST)
    {
      safe_printfmt (2, "arch: unable to create directory %s\n", my_archives_dir);
      exit (2);
    }

  defaults_path = archive_defaults_path();
  if (safe_access (defaults_path, F_OK))
    {
      t_uchar *old_default_path;
      inifile_t inifile;
      inifile_init (&inifile);
      inifile_process_text (&inifile, defaults_blurb());
      old_default_path = file_name_in_vicinity (0, arch_my_arch_params(), "signing/=default");
      
      if (!safe_access (old_default_path, F_OK))
          upgrade_signing_rule (&inifile, old_default_path, 1);
      else
        {
          /* bah, can be bothered factoring these two lines out duplicated above */
          inifile_add_key (&inifile, "", "", "", _("# Uncomment to use gnome-gpg"));
          inifile_add_key (&inifile, "", "", "", "# gpg_command=gnome-gpg");
        }
      lim_free (0, old_default_path);

      old_default_path = file_name_in_vicinity (0, arch_my_arch_params(), "signing/=default.check");
      if (!safe_access (old_default_path, F_OK))
          upgrade_checking_rule (&inifile, old_default_path);
      lim_free (0, old_default_path);
      
      inifile_save_merge (&inifile, 0, defaults_path);
    }
}

t_uchar *
arch_archive_location (t_uchar const * archive_name, int soft)
{
  t_uchar * file = 0;
  t_uchar * first_line = 0;
  t_uchar * start;
  t_uchar * end;
  t_uchar * answer = 0;

  ensure_archives_defaults_exists();
  file = arch_archive_location_file (archive_name);

  if (safe_access (file, F_OK))
    {
      lim_free (0, file);
      
      if (soft)
          return 0;

      safe_printfmt (2, "archive not registered: %s\n", archive_name);
      safe_printfmt (2, "  (see register-archive)\n");
      exit (2);
    }

  first_line = read_line_from_file (file);

  for (start = first_line; char_is_blank (*start); ++start)
    ;
  for (end = start; *end && !char_is_space (*start); ++end)
    ;

  answer = str_save_n (0, start, end - start);

  lim_free (0, file);
  lim_free (0, first_line);

  return answer;
}

/**
 * \brief find all the locations for an archive
 * \param official_name the archive name to query
 * \return rel_table of the locations, with parameters as fields
 */
ar_archive_location
arch_archive_locations (t_uchar const *official_name)
{
    return arch_archive_locations_ext (official_name, 0);
}

/**
 * \brief Compare the properties of two locations
 */
static int lcmp(int left, int right)
{
  if (left == right)
    return 0;
  else if (left == 2)
    return -1;
  else if (right == 2)
    return 1;
  else
    return 0;
}


/**
 * \brief This function compares likely up-to-dateness of locations.
 *
 * A location is more likely up-to-date if it is
 * 1. a readonly master (not really, but http is nicer to use)
 * 2. a writable master
 * 3. a readable mirror
 * 4. a writable mirror
 * \param _left The first location
 * \param _right The second location
 * \param context unused
 * \return -1 if the left location has a higher priority, +1 if the right does
 */
static int
arch_archive_fresh_cmp(void * _left, void *_right, void *context)
{
    arch_archive_location_t * left = *(arch_archive_location_t **)_left;
    arch_archive_location_t * right = *(arch_archive_location_t **)_right;
    int result = lcmp (left->master, right->master);
    if (result == 0)
      result = lcmp (left->readonly, right->readonly);
    return result;
}

static int
arch_archive_location_priority_cmp (void * _left, void * _right, void *context)
{
    arch_archive_location_t * left = *(arch_archive_location_t **)_left;
    arch_archive_location_t * right = *(arch_archive_location_t **)_right;
    return right->priority - left->priority;
}

  
/**
 * \brief Compare two locations by priority and other properties
 *
 * \param _left The first location
 * \param _right The second location
 * \param context unused
 * \return <0 if the left location has a higher priority, >0 if the right does
 */
static int 
arch_archive_location_property_cmp(void * left, void *right, void *context)
{
  int result = arch_archive_location_priority_cmp (left, right, context);
  if (result == 0)
    result = arch_archive_fresh_cmp (left, right, context);
  return result;
}



/**
 * \brief get a rel_table of locations and properties from an inifile value list
 * \param values the inifile values and comments
 * \return a ar_archive_location with the locations and parameters.
 */
ar_archive_location
locations_from_ini_values (rel_table values)
{
    ar_archive_location result = NULL;
    int index;
    /* discard any legacy locations */
    rel_for_each (values, index)
      {
        int result_index = ar_size_archive_location(result);
        arch_archive_location_t * location = arch_archive_location_new_inivalue (values[index][0]);
        if (!location->disabled)
            ar_insert_archive_location (&result, result_index, location);
        else 
            talloc_free (location);
      }
    quicksort (&result[0], ar_size_archive_location (result), sizeof (void *), arch_archive_location_property_cmp, NULL);
    return result;
}

/**
 * \brief find all the locations for an archive
 * \param official_name the archive name to query
 * \param new_only do not query legacy registries.
 * \return ar_archive_location of the locations
 */
ar_archive_location
arch_archive_locations_ext (t_uchar const *official_name, int new_only)
{
  ar_archive_location locations = NULL;
  t_uchar * found_archive = arch_archive_location (official_name, 1);
  if (found_archive && !new_only)
    {
      ar_insert_archive_location (&locations, 0, arch_archive_location_new (found_archive));
      /* we have to present its not readonly for connect_commitable to be able to work */
      locations[0]->readonly = 0;
    }
  lim_free (0, found_archive);
  if (arch_archives_has_registry_entry (official_name))
    {
      inifile_t inifile;
      rel_table values;
      arch_archives_get_archive_ini (official_name, &inifile);
      values = inifile_get_key_values (&inifile, "", "url");
      if (rel_n_records (values))
        {
          /* discard any legacy locations */
          ar_free_archive_location (&locations);
          locations = locations_from_ini_values(values);
        }
      rel_free_table (values);
      inifile_finalise (&inifile);
    }
  return locations;
}

/**
 * \brief question for a location in the archives registry
 * \return non zero on presence
 */
int
arch_archives_archive_has_location (t_uchar const * official_name, t_uchar const * location)
{
    return arch_archives_archive_has_location_ext (official_name, location, 0);
}

/**
 * \brief does the archive registry contain a specific archive location
 * \param new_only don't query legacy registrations
 * \return non zero on presence
 */
int
arch_archives_archive_has_location_ext (t_uchar const * official_name, t_uchar const * location, int new_only)
{
    ar_archive_location locations = arch_archive_locations_ext (official_name, new_only);
    int index;
    int found = 0;
    ar_for_each (locations, index)
      {
        if (!arch_archive_cmp_location (location, locations[index]->url))
            found = 1;
      }
    ar_free_archive_location (&locations);
    return found;
}

void
arch_delete_archive_location (t_uchar * archive_name, int force_p)
{
  t_uchar * file = 0;
  int errn;

  file = arch_archive_location_file (archive_name);

  if (vu_unlink (&errn, file))
    {
      if (!(force_p && (errn == ENOENT)))
        {
          safe_printfmt (2, "archive not registered: %s\n", archive_name);
          safe_printfmt (2, "  (see register-archive)\n");
          exit (2);
        }
    }

  lim_free (0, file);
}

void
arch_archives_delete_archive_location (t_uchar const *official_name, t_uchar const *location, int force)
{
    /* find the url in the registry */
    /* remove it */
    inifile_t inifile;
    rel_table values;
    int index;
    int offset = 0;
    t_uchar *uncached_location = arch_uncached_location (location);
    arch_archives_get_archive_ini_no_default (official_name, &inifile);
    values = inifile_get_key_values (&inifile, "", "url");
    rel_for_each (values, index)
      {
        arch_archive_location_t *location = arch_archive_location_new_inivalue (values[index][0]);
        t_uchar *ini_uncached = arch_uncached_location (location->url);
        if (!str_cmp (ini_uncached, uncached_location))
          {
            inifile_remove_key (&inifile, "", "url", index - offset++);
          }
        lim_free (0, ini_uncached);
        talloc_free (location);
      }
    if (offset)
        arch_archives_save_archive_ini_no_default (official_name, &inifile);
    else if (!force)
        {
          safe_printfmt (2, _("archive location not registered: %s at %s\n"), official_name, location);
          safe_printfmt (2, _("  (see register-archive)\n"));
          exit (2);
        }
    rel_free_table (values);
    lim_free (0, uncached_location);
    inifile_finalise (&inifile);
}

/**
 * \brief generate a list of archives registered by 
 * =locations files
 * \param place the output in where
 */
void
arch_registered_registered_name_archives (rel_table *where)
{
  t_uchar * dir = 0;
  rel_table files = 0;
  int index;

  dir = arch_my_archive_locations_dir ();
  files = maybe_directory_files (dir);

  rel_for_each (files, index)
    {
      t_uchar * f;

      f = files[index][0];
      if (str_cmp (".", f) && str_cmp ("..", f) && arch_valid_archive_name (f))
        {
          t_uchar * location = 0;

          location = arch_archive_location (f, 0);
          rel_add_records (where, rel_make_record (f, location, 0), 0);

          lim_free (0, location);
        }
    }


  lim_free (0, dir);
  rel_free_table (files);
}

rel_table
arch_registered_archives (void)
{
  t_uchar * dir = 0;
  rel_table files = 0;
  rel_table answer = 0;
  rel_table temp_new = NULL;
  rel_table temp_old = NULL;
  int index;
  
  /* FIXME-REMOVENAMES */
  arch_registered_registered_name_archives (&temp_old);

  dir = arch_my_archives_dir ();
  files = maybe_directory_files (dir);

  rel_for_each (files, index)
    {
      t_uchar * file;

      file = files[index][0];
      if (arch_valid_archive_name (file))
        {
          ar_archive_location locations = arch_archive_locations (file);
          if (ar_size_archive_location (locations))
              rel_add_records (&temp_new, rel_make_record (file, locations[0]->url, NULL), NULL);

          ar_free_archive_location (&locations);
        }

    }
  
  lim_free (0, dir);
  rel_free_table (files);
 
  rel_sort_table_by_field (0, temp_new, 0);
  rel_sort_table_by_field (0, temp_old, 0);
  /* old only rows */
  answer = rel_join (1, rel_join_output (1, 0, 1, 1, -1) , 0, 0, temp_old, temp_new);
  /* and the shared and new only ones */
  rel_append_x (&answer, temp_new);

  rel_free_table (temp_new);
  rel_free_table (temp_old);
  return answer;
}

t_uchar *
arch_mirrored_at_name (t_uchar const * const archive)
{
  t_uchar * mirror_name = 0;

  mirror_name = str_alloc_cat (0, archive, "-MIRROR");

  return mirror_name;
}

t_uchar *
arch_mirrored_from_name (t_uchar const * const archive)
{
  t_uchar * source_name = 0;

  source_name = str_alloc_cat (0, archive, "-SOURCE");

  return source_name;
}

t_uchar *
arch_mirrored_at (t_uchar const * const archive)
{
  t_uchar * mirror_name = 0;
  t_uchar * mirror_loc = 0;

  mirror_name = arch_mirrored_at_name (archive);
  mirror_loc = arch_archive_location (mirror_name, 1);

  if (!mirror_loc)
    {
      lim_free (0, mirror_name);
      mirror_name = 0;
    }

  lim_free (0, mirror_loc);
  return mirror_name;
}


t_uchar *
arch_mirrored_from (t_uchar const * const archive)
{
  t_uchar * source_name = 0;
  t_uchar * source_loc = 0;

  source_name = arch_mirrored_from_name (archive);
  source_loc = arch_archive_location (source_name, 1);

  if (!source_loc)
    {
      lim_free (0, source_name);
      source_name = 0;
    }

  lim_free (0, source_loc);
  return source_name;
}



t_uchar *
arch_fs_archive_archive_version_path (t_uchar * archive_path)
{
  return file_name_in_vicinity (0, archive_path, ".archive-version");
}

t_uchar *
arch_fs_archive_meta_info_path (t_uchar * archive_path)
{
  return file_name_in_vicinity (0, archive_path, "=meta-info");
}

t_uchar *
arch_fs_archive_meta_info_item_path (t_uchar * archive_path, t_uchar * meta_info_name)
{
  t_uchar * meta_info_dir = 0;
  t_uchar * meta_info_path;

  meta_info_dir = arch_fs_archive_meta_info_path (archive_path);
  meta_info_path = file_name_in_vicinity (0, meta_info_dir, meta_info_name);

  lim_free (0, meta_info_dir);

  return meta_info_path;
}

/**
 * \brief legacy call to find signature checking rule
 */
t_uchar *
arch_archives_signature_signing_rule_file (t_uchar const * registered_name)
{
    t_uchar * result = file_name_in_vicinity (0, arch_my_arch_params(), "signing");
    result = str_realloc_cat_many (0, result, "/", registered_name, str_end);
    return result;
}

t_uchar *
arch_archives_signature_checking_rule_file (t_uchar const * registered_name)
{
    t_uchar * result = file_name_in_vicinity (0, arch_my_arch_params(), "signing");
    result = str_realloc_cat_many (0, result, "/", registered_name, ".check", str_end);
    return result;
}



/* tag: Tom Lord Sun May 18 19:17:40 2003 (archives.c)
 */


syntax highlighted by Code2HTML, v. 0.9.1