/* archive.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 "config-options.h"
#include "po/gettext.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/os/errno.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/os/sys/types.h"
#include "hackerlab/os/sys/wait.h"
#include "hackerlab/os/signal.h"
#include "hackerlab/os/time.h"
#include "hackerlab/os/unistd.h"
#include "hackerlab/os/stdlib.h"
#include "hackerlab/fmt/cvt.h"
#include "hackerlab/mem/alloc-limits.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/mem/talloc.h"
#include "hackerlab/hash/hash-utils.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/char/str.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/fs/file-names.h"
#include "libfsutils/tmp-files.h"
#include "libfsutils/rmrf.h"
#include "libfsutils/copy-file.h"
#include "libawk/trim.h"
#include "libarch/namespace.h"
#include "libarch/my.h"
#include "libarch/exec.h"
#include "libarch/archives.h"
#include "libarch/archive-version.h"
#include "libarch/debug.h"
#include "libarch/pfs.h"
#include "libarch/archive-pfs.h"
#include "libarch/cached-archive.h"
#include "libarch/arch-cache.h"
#include "libarch/archive.h"


/* __STDC__ prototypes for static functions */
static int invoke_tar_extract (int * pid_ret);
static int wait_for_tar (int pid);
static void ensure_writable (struct arch_archive * arch, int mirror_ok);
static struct arch_archive * connect_if_url (t_uchar const * branch, t_uchar ** branch_out);
static struct arch_archive * connect_if_not_url (t_uchar const * branch, t_uchar ** branch_out, struct arch_archive * (*connect_function)(t_uchar const *));
static struct arch_archive * arch_archive_connected_one_of (ar_archive_location const locations);
static struct arch_archive * arch_archive_connect_first_location (t_uchar const * official_name, ar_archive_location const locations);
static int writable_archive (struct arch_archive *archive);



static struct arch_archive * connected_by_url = 0;



/** 
 * \brief register a connected archive in the archive registry
 */
void
arch_archive_register (struct arch_archive * archive)
{
  int present = arch_archives_archive_has_location_ext (archive->official_name, archive->location, 1);
  if (!present)
    {
      arch_archive_location_t * location;
      inifile_t inifile;
      t_uchar * value;
      arch_archives_get_archive_ini_no_default (archive->official_name, &inifile);
      location = arch_archive_location_new (archive->location);
      if (archive->mirror_of)
          location->master = 0;
      else
          location->master = 2;
      if (str_cmp (archive->registered_name, archive->official_name))
          location->priority = 50;
      if (writable_archive (archive))
          location->readonly = 0;
      else
          location->readonly = 2;

      value = arch_archive_location_to_ini_key(location);
      inifile_add_key (&inifile, "", "url", value, "");
      arch_archives_save_archive_ini_no_default (archive->official_name, &inifile);
      inifile_finalise (&inifile);
      talloc_free (location);
      lim_free (0, value);
      archive->in_registry = -1;
    }
}

void
arch_make_archive (t_uchar * name, t_uchar * location, t_uchar * mirror_of, int dot_listing_lossage, int signed_archive, int tla_archive, int register_archive)
{
  t_uchar * current_loc = 0;
  t_uchar * version;

  invariant (arch_valid_archive_name (name));

  current_loc = arch_archive_location (name, 1);

  if (current_loc && !mirror_of)
    {
      safe_printfmt (2, "arch_make_archive: archive already registered\n  name: %s\n", name);
      exit (2);
    }

  version = arch_archive_version_for_new_archive (tla_archive);

  if (arch_cached_archive_protocol (location))
    arch_cached_make_archive (name, location, version, mirror_of, dot_listing_lossage, signed_archive);
  else
    {
      if (str_cmp_prefix ("uncached:", location) == 0)
        location = location + str_length ("uncached:");
      arch_pfs_make_archive (name, location, version, mirror_of, dot_listing_lossage, signed_archive);
    }

  if (register_archive)
    {
      struct arch_archive *arch = arch_archive_connect_location (location, 0);
      arch_archive_register (arch);
      arch_archive_close (arch);
    }

  arch_run_hook ("make-archive", "ARCH_ARCHIVE", name, "ARCH_LOCATION", location, 0);

  lim_free (0, version);
}

t_uchar *
arch_archive_not_writable (struct arch_archive *arch, int mirror_ok)
{
  if (arch->access != arch_archive_writable)
   {
     return str_alloc_cat_many  (0, "attempt to modify archive that has ",
                                    "read-only compatibility: ",
                                    arch->official_name, " url: ",
                                    arch->location,
                                    "\n",
                                    str_end);
    }

  if (!mirror_ok)
    {
      if (!arch->client_anticipates_mirror && !!arch->mirror_of)
        {
          return str_alloc_cat_many (0, "attempt to commit directly to mirror\n",
                                        "  archive: ", arch->location, "\n",
                                        "mirror of: ", arch->official_name, "\n",
                                        str_end);
        }

      if (arch->client_anticipates_mirror && !arch->mirror_of)
        {
          return str_alloc_cat_many(0, "archive is not a mirror of ",
                                       arch->client_anticipates_mirror, "\n",
                                       "   archive: ", arch->official_name, "\nurl: ",
                                       arch->location, "\n",
                                       "   expected mirror of",
                                       arch->client_anticipates_mirror, "\n",
                                       "\n",
                                       str_end);
        }

      if (arch->client_anticipates_mirror && str_cmp (arch->client_anticipates_mirror, arch->mirror_of))
        {
          return str_alloc_cat_many(0, "attempt to write to wrong mirror\n",
                                       "   url: ", arch->location, "\n",
                                       "   expected mirror:",
                                       arch->client_anticipates_mirror, "\n",
                                       "   got mirror of: ", arch->mirror_of, "\n",
                                       str_end);
        }
    }
  return NULL;
}

/**
 * \brief connect to a url, with an expected name
 */
struct arch_archive *
arch_archive_connect_location (t_uchar const * location, int soft_errors)
{
  struct arch_archive *result;
  for (result = connected_by_url;
       result && str_cmp (result->location, location);
       result = result->next)
    ;

  if (result)
    {
      invariant (!!talloc_reference (NULL, result));
    }
  else
    {
      result = arch_archive_connect_location_ext (NULL, location, NULL, soft_errors, soft_errors);
      if (!result)
        {
          if (!soft_errors)
              panic ("failed to connect to location with soft_errors zero");
          
          return NULL;
        }
    }
    return result;
}

/**
 * \brief get the signed status from an inifile, with a overridable default
 */
arch_fail_action_t
arch_archive_inifile_signed (struct arch_archive * arch, inifile_t *inifile, arch_fail_action_t default_value)
{
    arch_fail_action_t result;
    t_uchar * temp_string = inifile_get_single_string (inifile, "", "when_unsigned", "default");
    if (!str_casecmp ("ignore", temp_string))
        result = arch_fail_ignore;
    else if (!str_casecmp ("warn", temp_string))
        result = arch_fail_warn;
    else if (!str_casecmp ("error", temp_string))
        result = arch_fail_error;
    else if (!str_casecmp ("default", temp_string))
        result = default_value;
    else
      {
        result = default_value;
        safe_printfmt (2, "invalid value '%s' for when_unsigned for archive %s\n", temp_string, arch->official_name);
      }
    lim_free (0, temp_string);
    return result;
}

/**
 * \brief set the signed satatus for an inifile
 */
void 
arch_archive_inifile_set_signed (inifile_t *inifile, arch_fail_action_t value)
{
    t_uchar * temp_string;
    switch (value)
      {
        default:panic ("unknown fail_action value");
        case arch_fail_ignore:
                temp_string = "ignore";break;
        case arch_fail_warn:
                temp_string = "warn";break;
        case arch_fail_error:
                temp_string = "error";break;
      }
    inifile_set_single_string (inifile, "", "when_unsigned", temp_string, "");
}

/**
 * \brief connect to a url, with the ability to override the signature checking policy
 *
 * returned handles should be closed with arch_archive_close
 */
struct arch_archive *
arch_archive_connect_location_ext (t_uchar const * name, t_uchar const * location, t_uchar const * want_mirror_of, int soft_errors, int override_signed)
{
  struct arch_archive * answer;
  int err;
  int cached_protocol;

  /* FIXME RBC 20050322 only the concrete classes should alloc.
   * Right way: pass name and client_anticipates to all the connect concrete calls
   */
  answer = talloc (NULL, struct arch_archive);
  mem_set0 ((t_uchar *)answer, sizeof (*answer));
  talloc_set_destructor (answer, arch_archive_finalise);

  answer->client_anticipates_mirror = (want_mirror_of ? str_save (0, want_mirror_of) : 0);

  answer->registered_name = str_save (0, name);
  
  cached_protocol = arch_cached_archive_protocol (location);

  if (cached_protocol)
      arch_ensure_cache_path ();
  if (cached_protocol && arch_cache_active ())
    {
      answer->location = str_save (0, location);
      err = arch_cached_archive_connect (&answer, soft_errors);
    }
  else
    {
      t_uchar * uncached_location = arch_uncached_location(location);
      answer->location = str_save (0, uncached_location + str_length ("uncached:"));
      lim_free (0, uncached_location);
      err = arch_pfs_archive_connect (&answer, soft_errors);
    }
  
  if (err)
    {
      if (soft_errors)
        {
          if (talloc_free (answer))
              debug (dbg_archive, 0, _("Failed during free of archive\n"));
          return NULL;
        }
      panic ("failed to connect to location.");
    }

  answer->version = answer->vtable->archive_version (answer);
  if (!answer->version)
    {
      if (soft_errors)
        {
          if (talloc_free (answer))
              debug (dbg_archive, 0, _("Failed during free of archive\n"));
          return NULL;
        }
      safe_printfmt (2, "arch_archive_connect_location_ext: unidentifiable archive (%s)\n", answer->location);
      exit (2);
    }
  answer->access = arch_archive_access (answer->version);
  answer->type = arch_archive_type (answer->version);

  if (answer->access == arch_archive_incompatible)
    {
      safe_printfmt (2, "arch_archive_connect: attempt to connect to incompatible archive\n  archive: %s\n", (name ? name : location));
      if (soft_errors)
        {
          if (talloc_free (answer))
              debug (dbg_archive, 0, _("Failed during free of archive\n"));
          return NULL;
        }
      else
          exit (2);
    }

    {
      t_uchar *temp = arch_get_meta_info (answer, "mirror");
      answer->mirror_of = str_save (0, trim_surrounding_ws (temp));
      lim_free (0, temp);
      temp = arch_get_meta_info (answer, "name");
      answer->official_name = str_save (0, trim_surrounding_ws (temp));
      lim_free (0, temp);
    }
  
  if (!answer->official_name)
    {
      int ign;

      printfmt (&ign, 2, "Unable to read the official name of archive %s\n  Archive damaged?\n", name);
      if (soft_errors)
        {
          if (talloc_free (answer))
              debug (dbg_archive, 0, _("Failed during free of archive\n"));
          return NULL;
        }
      else
          exit (2);
    }
  lim_free (0, answer->registered_name);
  answer->registered_name = str_save (0, answer->official_name);
  answer->http_blows = arch_get_meta_int_info (answer, "http-blows");
  answer->signed_archive = arch_get_meta_int_info (answer, "signed-archive");

  if (arch_archive_has_registry_entry (answer))
    {
      inifile_t inifile;
      arch_archives_get_archive_ini (answer->official_name, &inifile);
      answer->when_unsigned = arch_archive_inifile_signed (answer, &inifile, arch_fail_ignore);
      inifile_finalise (&inifile);
    }

  if (want_mirror_of && str_cmp (want_mirror_of, answer->mirror_of))
    {
      safe_printfmt (2, "arch_archive_connect: attempt to connect to wrong mirror\n  archive: %s\n  wanted mirror of: %s\n  got mirror of: %s\n",
                     (name ? name : location), want_mirror_of, (answer->mirror_of ? answer->mirror_of : (t_uchar *)"(not a mirror)"));
      if (soft_errors)
        {
          if (talloc_free (answer))
              debug (dbg_archive, 0, _("Failed during free of archive\n"));
          return NULL;
        }
      else
          exit (2);
    }

  if (!override_signed && arch_archive_check_signed_status (answer, 2))
    {
      if (soft_errors)
        {
          if (talloc_free (answer))
              debug (dbg_archive, 0, _("Failed during free of archive\n"));
          return NULL;
        }
      else
          exit (2);
    }
  if (answer)
    {
      answer->next = connected_by_url;
      connected_by_url = answer;
    }
  return answer;
}

struct arch_archive *
arch_archive_connect_ext (t_uchar const * const name, t_uchar const * const want_mirror_of, int soft_errors)
{
  struct arch_archive * answer = 0;

  /* FIXME-REMOVENAME to change this, we need to be given connection
   * requests by oficial name only, with an alternative api for
   * locations, or more options - i.e. give me a mirror, give me a 
   * writable location.
   * 1) find matching official name
   * 2) if it doesn't fit the requested options, keep searching
   */
  t_uchar * location = 0;

  location = arch_archive_location (name, 0);

  answer = arch_archive_connect_location_ext (name, location, want_mirror_of, soft_errors, 0);
  if (!answer)
    {
      if (soft_errors)
        {
          lim_free (0, location);
          return NULL;
        }
      panic ("failed to connect to location with soft_errors zero");
    }
  lim_free (0, answer->registered_name);
  answer->registered_name = str_save (0, name);

  lim_free(0, location);
  return answer;
}

/**
 * \brief connect to a url archive. If the url looks like an archive name
 * or branch name, don't connect.
 * \param branch the branch to conenct to 
 * \param branch_out where to store the official namespace if we connect
 */
struct arch_archive *
connect_if_url (t_uchar const * branch, t_uchar ** branch_out)
{
    struct arch_archive * result = NULL;
    if (!arch_valid_package_name (branch, arch_maybe_archive, arch_req_package, 1)
        && !arch_valid_archive_name (branch))
      {
        /* maybe url */
        /* try as archive */
        t_uchar * temp_location = escape_location (branch);
        result = arch_archive_connect_location (temp_location, 1);
        if (result)
          {
            arch_archive_register (result);
            if (branch_out)
                *branch_out = str_save (0, result->official_name);
          }
        else
          {
            t_uchar *rel_part = str_chr_rindex (branch, '/') + 1;
            if ((rel_part - 1) && str_length (rel_part) && rel_part - 1 > branch)
              {
                t_uchar *location = str_save_n (0, branch, rel_part - 1 - branch);
                location = str_replace (location, escape_location (location));
                result = arch_archive_connect_location (location, 1);
                if (result)
                  {
                    arch_archive_register (result);
                    if (branch_out)
                        *branch_out = str_alloc_cat_many (0, result->official_name, "/", rel_part, str_end);
                  }
                lim_free (0, location);
              }
          }
        lim_free (0, temp_location);
      }
    return result;
}

/**
 * \brief connect to a non url branch, with a supplied policy connection routine.
 */
struct arch_archive *
connect_if_not_url (t_uchar const * branch, t_uchar ** branch_out, struct arch_archive * (*connect_function)(t_uchar const *))
{
    struct arch_archive * result = NULL;
    if (arch_valid_archive_name (branch))
      {
        result = connect_function (branch);
        if (result && branch_out)
            *branch_out = str_save (0, result->official_name);
      }
    else if (arch_valid_package_name (branch, arch_maybe_archive, arch_req_category, 1))
      {
        t_uchar * archive_name = arch_parse_package_name (arch_ret_archive, NULL, branch);
        result = connect_function (archive_name);
        lim_free (0, archive_name);
        if (result && branch_out)
          {
            t_uchar *nonarch = arch_parse_package_name (arch_ret_non_archive, NULL, branch);
            *branch_out = str_alloc_cat_many (0, result->official_name, "/", nonarch, str_end);
            lim_free (0, nonarch);
          }
      }
    return result;
}

/**
 * \brief connect to a readonly or better branch (or revision).
 *
 * \param branch the branch to connect to - url, or namespace
 * \param branch_out if not NULL this is set to a namespace value
 * \return struct_arch_archive * a connected archive on success, or NULL on failure.
 */
struct arch_archive *
arch_archive_connect_branch (t_uchar const * branch, t_uchar ** branch_out)
{
    struct arch_archive * result = connect_if_url (branch, branch_out);
    if (!result)
        result = connect_if_not_url (branch, branch_out, arch_archive_connect_readonly);
    return result;
}

/**
 * \brief connect to a writable or better branch (or revision).
 *
 * if a url is supplied, it is presumed to be writable or better.
 * \param branch the branch to connect to - url, or namespace
 * \param branch_out if not NULL this is set to a namespace value
 * \return struct_arch_archive * a connected archive on success, or NULL on failure.
 */
struct arch_archive *
arch_archive_connect_writeable_branch (t_uchar const * branch, t_uchar ** branch_out)
{
    struct arch_archive * result = connect_if_url (branch, branch_out);
    if (!result)
        result = connect_if_not_url (branch, branch_out, arch_archive_connect_writable);
    return result;
}

/**
 * \brief connect to a commitable or better branch (or revision).
 *
 * if a url is supplied, it is presumed to be commitable or better.
 * \param branch the branch to connect to - url, or namespace
 * \param branch_out if not NULL this is set to a namespace value
 * \return struct_arch_archive * a connected archive on success, or NULL on failure.
 */
struct arch_archive *
arch_archive_connect_commitable_branch (t_uchar const * branch, t_uchar ** branch_out)
{
    struct arch_archive * result = connect_if_url (branch, branch_out);
    if (!result)
        result = connect_if_not_url (branch, branch_out, arch_archive_connect_commitable);
    return result;
}

/** 
 * \brief find any one of a set of locations in the connected set of archives
 * \param locations the locations to query
 * \return NULL if none is found.
 */
struct arch_archive *
arch_archive_connected_one_of (ar_archive_location const locations)
{
  struct arch_archive *result;
  for (result = connected_by_url;
       result; result = result->next)
    {
      int index;
      ar_for_each (locations, index)
        {
          if (!str_cmp (result->location, locations[index]->url))
              return result;
        }
    }
  return result;
}

/** 
 * \brief connect to an archive by name when commitable access is needed
 * \param name the official name of the archive
 * \return struct arch_archive *
 */
struct arch_archive *
arch_archive_connect_commitable (t_uchar const * official_name)
{
  struct arch_archive * answer = 0;

  ar_archive_location locations = arch_archive_locations (official_name);
    {
      int index;
      ar_for_each (locations, index)
        {
          if (locations[index]->readonly || !locations[index]->master)
            {
              talloc_free (locations[index]);
              ar_remove_archive_location(&locations, index);
              --index;
            }
        }
    }
  if (!ar_size_archive_location (locations))
    {
      debug (dbg_archive, 1, "No commitable locations for %s are registered\n", official_name);
      return NULL;
    }

  answer = arch_archive_connected_one_of (locations);

  if (answer)
      invariant (!!talloc_reference (NULL, answer));
  else
      answer = arch_archive_connect_first_location (official_name, locations); 
  ar_free_archive_location (&locations);
  return answer;
}

/** 
 * \brief connect to an archive by name when write access is needed
 * \param name the official name of the archive
 * \return struct arch_archive *
 */
struct arch_archive *
arch_archive_connect_writable (t_uchar const * official_name)
{
  struct arch_archive * answer = 0;

  ar_archive_location locations = arch_archive_locations (official_name);
    {
      int index;
      ar_for_each (locations, index)
        {
          if (locations[index]->readonly)
            {
              talloc_free (locations[index]);
              ar_remove_archive_location(&locations, index);
              --index;
            }
        }
    }
  if (!ar_size_archive_location (locations))
    {
      debug (dbg_archive, 1, "No writable locations for %s are registered\n", official_name);
      return NULL;
    }

  answer = arch_archive_connected_one_of (locations);

  if (answer)
      invariant (!!talloc_reference (NULL, answer));
  else
      answer = arch_archive_connect_first_location (official_name, locations); 
  ar_free_archive_location (&locations);
  return answer;
}

/**
 * \brief connect to the first accessible archive in an ordered list
 * \param locations the locations to try in order
 */
struct arch_archive *
arch_archive_connect_first_location (t_uchar const * official_name, ar_archive_location const locations)
{
  struct arch_archive * result = NULL;
  int index;
  ar_for_each (locations, index)
    {
      result = arch_archive_connect_location_ext (official_name, locations[index]->url, NULL, 1, 0);
      if (result)
          return result;
    }
  return result;
}

/** 
 * \brief connect to an archive by name when only read access is needed
 * \param name the official name of the archive
 * \return struct arch_archive *
 */
struct arch_archive *
arch_archive_connect_readonly (t_uchar const * official_name)
{
  struct arch_archive * answer = 0;

  ar_archive_location locations = arch_archive_locations (official_name);
  if (!ar_size_archive_location (locations))
      return NULL;

  answer = arch_archive_connected_one_of (locations);

  if (answer)
      invariant (!!talloc_reference (NULL, answer));
  else
      answer = arch_archive_connect_first_location (official_name, locations); 
  ar_free_archive_location (&locations);
  return answer;
}

int
arch_archive_finalise (void *archive_void)
{
  struct arch_archive * arch = (struct arch_archive *) archive_void;
    {
      struct arch_archive ** handle = &connected_by_url;

      while (*handle)
        {
          if (*handle != arch)
            handle = &((*handle)->next);
          else
            {
              *handle = (*handle)->next;
            }
        }
    }
  lim_free (0, arch->location);
  lim_free (0, arch->mirror_of);
  lim_free (0, arch->registered_name);
  lim_free (0, arch->official_name);
  lim_free (0, arch->version);
  return 0;
}

t_uchar *
arch_archive_version (struct arch_archive * arch)
{
  return arch->vtable->archive_version (arch);
}

rel_table
arch_archive_categories (struct arch_archive * arch)
{
  rel_table answer = 0;

  answer = arch->vtable->categories (arch);
  rel_sort_table_by_field (0, answer, 0);

  return answer;
}


rel_table
arch_archive_branches (struct arch_archive * arch, t_uchar * category)
{
  rel_table answer = 0;

  answer = arch->vtable->branches (arch, category);
  rel_sort_table_by_field (0, answer, 0);

  return answer;
}


rel_table
arch_archive_versions (struct arch_archive * arch, t_uchar * package)
{
  rel_table answer = 0;

  answer = arch->vtable->versions (arch, package);
  arch_sort_table_by_name_field (0, answer, 0);

  return answer;
}

t_uchar *
arch_archive_latest_revision (struct arch_archive * arch, t_uchar * unknown_component, int full)
{
  rel_table revs = 0;
  t_uchar * answer = 0;
  t_uchar * version = 0;

  /* First, see if we were just given a version */
  if ( arch_valid_package_name ( unknown_component, arch_no_archive, arch_req_version, 0))
    {
      version = str_save(0, unknown_component);
    }

  /* Ok. Not a version. Lets see if its a package */
  else if (arch_valid_package_name (unknown_component, arch_no_archive, arch_req_package, 0))
    {
      rel_table versions = 0;

      versions = arch_archive_versions (arch, unknown_component);
      arch_sort_table_by_name_field (1, versions, 0);
      if (! versions)
        {
          safe_printfmt (2, "arch_archive_latest_revision: ");
          safe_printfmt (2, "package %s has no versions\n", unknown_component);
          exit(1);
        }
     
      version = str_save(0, versions[0][0]);
    }

  /* Uh oh. we were given nothing we can use */
  else
    {
       safe_printfmt(2, "arch_archive_versions: %s neither package nor version.\n",
                     unknown_component);
       exit(1);
    }

  revs = arch_archive_revisions (arch, version, full);

  if (revs)
    answer = str_save (0, revs[rel_n_records (revs) - 1][0]);

  rel_free_table (revs);
  lim_free (0, version);
  return answer;
}


rel_table
arch_archive_revisions (struct arch_archive * arch, t_uchar const * version, int full)
{
  rel_table answer = 0;

  answer = arch->vtable->revisions (arch, (t_uchar *)version);
  arch_sort_table_by_patch_level_field (0, answer, 0);

  if (full)
    {
      t_uchar * fqv = 0;
      int x;

      if (full == 1)
        fqv = arch_fully_qualify (arch->official_name, (t_uchar *)version);
      else
        fqv = str_save (0, version);

      for (x = 0; x < rel_n_records (answer); ++x)
        {
          t_uchar * t = str_alloc_cat_many (0, fqv, "--", answer[x][0], str_end);
          rel_replace_record (answer, x, rel_make_record (t, NULL));
          lim_free (0, t);
        }

      lim_free (0, fqv);
    }

  return answer;
}

t_uchar *
arch_archive_log (struct arch_archive * arch, t_uchar * revision)
{
  t_uchar * answer = 0;

  answer = arch->vtable->archive_log (arch, revision);

  return answer;
}

/* RBC 20050109 the api here needs an overhaul.
 * is_cached should rather be a bitfield or just return into
 * a struct
 */
void
arch_revision_type (enum arch_revision_type * type, int * is_cached, int *has_ancestry, 
                   struct arch_archive * arch, arch_patch_id * revision)
{
  /* TRANSITIONAL : remove when pools are implemented */
  invariant_str_cmp (arch->official_name, arch_patch_id_archive (revision));

  if (arch->vtable->revision_type (type, is_cached, has_ancestry, arch, arch_patch_id_revision (revision)) < 0)
    {
      safe_printfmt (2, "failed to query archive:\n  revision: %s\n  location: %s\n", arch_patch_id_patch_id (revision), arch->location);
      exit (2);
    }
}

enum arch_revision_type 
arch_archive_get_revision_type (struct arch_archive * arch, t_uchar const * revision)
{
  enum arch_revision_type type;
  arch_patch_id * revision_patch = arch_patch_id_new_archive (arch->official_name, revision);
  arch_revision_type (&type, NULL, NULL, arch, revision_patch);
  talloc_free (revision_patch);
  return type;
}

int
arch_revision_exists (struct arch_archive * arch, t_uchar * revision)
{
  enum arch_revision_type type;
  return (arch->vtable->revision_type (&type, NULL, NULL, arch, revision) >= 0);
}


void
arch_get_patch_targz (int out_fd, struct arch_archive * arch, t_uchar * revision)
{
  arch->vtable->get_patch (out_fd, arch, revision);
}

void
arch_get_patch (struct arch_archive * arch, arch_patch_id * const revision, t_uchar const * const dest_dir)
{
  int here_fd;
  t_uchar * dest_dir_dir = 0;
  t_uchar * dest_dir_dir_path = 0;
  t_uchar * dest_dir_tail = 0;
  t_uchar * dest_dir_path = 0;
  t_uchar * tmpdir_path = 0;
  int out_fd = 0;
  int tar_pid = 0;
  t_uchar * patches_file_name = 0;
  t_uchar * patches_file_dest = 0;

  /* TRANSITIONAL : remove when pools are implemented */
  invariant_str_cmp (arch->official_name, arch_patch_id_archive (revision));

  here_fd = safe_open (".", O_RDONLY, 0);

  dest_dir_dir = file_name_directory_file (0, dest_dir);
  if (dest_dir_dir)
    safe_chdir (dest_dir_dir);
  dest_dir_dir_path = safe_current_working_directory ();
  dest_dir_tail = file_name_tail (0, dest_dir);
  dest_dir_path = file_name_in_vicinity (0, dest_dir_dir_path, dest_dir_tail);

  tmpdir_path = talloc_tmp_file_name (talloc_context, dest_dir_dir_path, ",,get-patch");
  patches_file_name = str_alloc_cat (0, arch_patch_id_revision (revision), ".patches");
  patches_file_dest = file_name_in_vicinity (0, "..", dest_dir_tail);

  safe_mkdir (tmpdir_path, 0777);
  safe_chdir (tmpdir_path);

  out_fd = invoke_tar_extract (&tar_pid);
  arch->vtable->get_patch (out_fd, arch, arch_patch_id_revision (revision));
  safe_close (out_fd);
  if (wait_for_tar (tar_pid))
    panic ("arch_get_patch: tar exitted with non-0 status");
  safe_rename (patches_file_name, patches_file_dest);

  safe_fchdir (here_fd);
  safe_close (here_fd);
  rmrf_file (tmpdir_path);

  lim_free (0, dest_dir_dir);
  lim_free (0, dest_dir_dir_path);
  lim_free (0, dest_dir_tail);
  lim_free (0, dest_dir_path);
  talloc_free (tmpdir_path);
  lim_free (0, patches_file_name);
  lim_free (0, patches_file_dest);
}

void
arch_get_cached_revision_targz (int out_fd, struct arch_archive * arch, t_uchar * revision)
{
  arch->vtable->get_cached (out_fd, arch, revision);
}

void
arch_get_cached_revision (struct arch_archive * arch, arch_patch_id * const revision, t_uchar const * const dest_dir)
{
  int here_fd;
  t_uchar * dest_dir_dir = 0;
  t_uchar * dest_dir_dir_path = 0;
  t_uchar * dest_dir_tail = 0;
  t_uchar * dest_dir_path = 0;
  t_uchar * tmpdir_path = 0;
  int out_fd = 0;
  int tar_pid = 0;
  t_uchar * patches_file_dest = 0;

  /* TRANSITIONAL : remove when pools are implemented */
  invariant_str_cmp (arch->official_name, arch_patch_id_archive (revision));

  here_fd = safe_open (".", O_RDONLY, 0);

  dest_dir_dir = file_name_directory_file (0, dest_dir);
  if (dest_dir_dir)
    safe_chdir (dest_dir_dir);
  dest_dir_dir_path = safe_current_working_directory ();
  dest_dir_tail = file_name_tail (0, dest_dir);
  dest_dir_path = file_name_in_vicinity (0, dest_dir_dir_path, dest_dir_tail);

  tmpdir_path = talloc_tmp_file_name (talloc_context, dest_dir_dir_path, ",,get-patch");
  patches_file_dest = file_name_in_vicinity (0, "..", dest_dir_tail);

  safe_mkdir (tmpdir_path, 0777);
  safe_chdir (tmpdir_path);

  out_fd = invoke_tar_extract (&tar_pid);
  arch->vtable->get_cached (out_fd, arch, arch_patch_id_revision (revision));
  safe_close (out_fd);
  if (wait_for_tar (tar_pid))
    panic ("arch_get_patch: tar exitted with non-0 status");
  safe_rename (arch_patch_id_revision (revision), patches_file_dest);

  safe_fchdir (here_fd);
  safe_close (here_fd);
  rmrf_file (tmpdir_path);

  lim_free (0, dest_dir_dir);
  lim_free (0, dest_dir_dir_path);
  lim_free (0, dest_dir_tail);
  lim_free (0, dest_dir_path);
  talloc_free (tmpdir_path);
  lim_free (0, patches_file_dest);
}

void
arch_get_import_targz (int out_fd, struct arch_archive * arch, t_uchar * revision)
{
  arch->vtable->get_import (out_fd, arch, revision);
}


void
arch_get_import_revision (struct arch_archive * arch, arch_patch_id * const revision, t_uchar const * const dest_dir)
{
  int here_fd;
  t_uchar * dest_dir_dir = 0;
  t_uchar * dest_dir_dir_path = 0;
  t_uchar * dest_dir_tail = 0;
  t_uchar * dest_dir_path = 0;
  t_uchar * tmpdir_path = 0;
  int out_fd = 0;
  int tar_pid = 0;
  t_uchar * patches_file_dest = 0;

  /* TRANSITIONAL : remove when pools are implemented */
  invariant_str_cmp (arch->official_name, arch_patch_id_archive (revision));

  here_fd = safe_open (".", O_RDONLY, 0);

  dest_dir_dir = file_name_directory_file (0, dest_dir);
  if (dest_dir_dir)
    safe_chdir (dest_dir_dir);
  dest_dir_dir_path = safe_current_working_directory ();
  dest_dir_tail = file_name_tail (0, dest_dir);
  dest_dir_path = file_name_in_vicinity (0, dest_dir_dir_path, dest_dir_tail);

  tmpdir_path = talloc_tmp_file_name (talloc_context, dest_dir_dir_path, ",,get-patch");
  patches_file_dest = file_name_in_vicinity (0, "..", dest_dir_tail);

  safe_mkdir (tmpdir_path, 0777);
  safe_chdir (tmpdir_path);

  out_fd = invoke_tar_extract (&tar_pid);
  arch->vtable->get_import (out_fd, arch, arch_patch_id_revision (revision));
  safe_close (out_fd);
  if (wait_for_tar (tar_pid))
    panic ("arch_get_patch: tar exitted with non-0 status");
  safe_rename (arch_patch_id_revision (revision), patches_file_dest);

  safe_fchdir (here_fd);
  safe_close (here_fd);
  rmrf_file (tmpdir_path);

  lim_free (0, dest_dir_dir);
  lim_free (0, dest_dir_dir_path);
  lim_free (0, dest_dir_tail);
  lim_free (0, dest_dir_path);
  talloc_free (tmpdir_path);
  lim_free (0, patches_file_dest);
}


arch_patch_id *
arch_get_continuation (struct arch_archive * arch, arch_patch_id * revision)
{
  t_uchar * raw_data = 0;
  t_uchar * start;
  t_uchar * end;
  arch_patch_id * answer = NULL;

  /* TRANSITIONAL : remove when pools are implemented */
  invariant_str_cmp (arch->official_name, arch_patch_id_archive (revision));

  raw_data = arch->vtable->get_continuation (arch, arch_patch_id_revision (revision));

  start = raw_data;
  while (*start && char_is_space (*start))
    ++start;

  end = start;
  while (*end && !char_is_space (*end))
    ++end;

  {
    t_uchar * temp = str_save_n (0, start, end - start);
    answer = arch_patch_id_new (temp);
    lim_free (0, temp);
  }

  lim_free (0, raw_data);

  return answer;
}


/**
 * \brief Set a meta-info value in an archive
 * \param archive The archive to modify
 * \param meta_info_name The name to modify
 * \param meta_info_value The value to assign.  If NULL, the value is unset.
 * \return 0 on success, other values on failure
 * \note This operation is non-transactional.
 */
int arch_set_meta_info (struct arch_archive * archive, 
                        t_uchar * meta_info_name, 
                        t_uchar * meta_info_value)
{
  return archive->vtable->set_meta_info (archive, meta_info_name, meta_info_value);
}

t_uchar *
arch_get_meta_info (struct arch_archive * arch, t_uchar * meta_info_name)
{
  t_uchar * answer = 0;

  answer = arch->vtable->get_meta_info (arch, meta_info_name);

  return answer;
}


int
arch_make_category (t_uchar ** errstr,
                    struct arch_archive * arch, t_uchar * category)
{
  int answer;

  ensure_writable (arch, 0);
  answer = arch->vtable->make_category (errstr, arch, category);
  arch_run_hook ("make-category", "ARCH_ARCHIVE", arch->official_name, "ARCH_CATEGORY", category, 0);
  return answer;
}

int
arch_make_branch (t_uchar ** errstr,
                    struct arch_archive * arch, t_uchar * branch)
{
  int answer;

  ensure_writable (arch, 0);
  answer = arch->vtable->make_branch (errstr, arch, branch);
  arch_run_hook ("make-branch", "ARCH_ARCHIVE", arch->official_name, "ARCH_BRANCH", branch, 0);
  return answer;
}

int
arch_make_version (t_uchar ** errstr,
                    struct arch_archive * arch, t_uchar * version)
{
  int answer;

  ensure_writable (arch, 0);
  answer = arch->vtable->make_version (errstr, arch, version);
  arch_run_hook ("make-version", "ARCH_ARCHIVE", arch->official_name, "ARCH_VERSION", version, 0);
  return answer;
}




int
arch_archive_lock_revision (t_uchar ** errstr, struct arch_archive * a,
                            t_uchar * version,
                            t_uchar * prev_level,
                            t_uchar * uid,
                            t_uchar * txn_id,
                            t_uchar * new_level)
{
  t_uchar *local_id = 0;
  int result;
  ensure_writable (a, 1);
  if (!txn_id)
    {
      local_id = arch_generate_txn_id ();
      txn_id = local_id;
    }
  result = a->vtable->lock_revision (errstr, a, version, prev_level, uid, txn_id, new_level);
  lim_free (0, local_id);
  return result;
}


int
arch_revision_ready (t_uchar ** errstr, struct arch_archive *a,
                     t_uchar * version,
                     t_uchar * prev_level,
                     t_uchar * uid,
                     t_uchar * txn_id,
                     t_uchar * new_level)
{
  return a->vtable->revision_ready (errstr, a, NULL, version, prev_level, uid, txn_id, new_level);
}

int
arch_mirror_revision_ready (t_uchar ** errstr, struct arch_archive *a,
                            struct arch_archive *from_archive,
                            t_uchar * version,
                            t_uchar * prev_level,
                            t_uchar * uid,
                            t_uchar * txn_id,
                            t_uchar * new_level)
{
  return a->vtable->revision_ready (errstr, a, from_archive, version, prev_level, uid, txn_id, new_level);
}



int
arch_archive_finish_revision (t_uchar ** errstr, struct arch_archive * a,
                              t_uchar * version,
                              t_uchar * prev_level,
                              t_uchar * uid,
                              t_uchar * txn_id,
                              t_uchar * new_level)
{
  ensure_writable (a, 0);
  return a->vtable->finish_revision (errstr, a, version, prev_level, uid, txn_id, new_level);
}

enum arch_revision_lock_state
arch_archive_revision_lock_state (t_uchar ** prev_level_ret,
                                  t_uchar ** uid_ret,
                                  t_uchar ** txn_id_ret,
                                  struct arch_archive * a,
                                  t_uchar * version)
{
  ensure_writable (a, 1);
  return a->vtable->lock_state (prev_level_ret, uid_ret, txn_id_ret, a, version);
}

int
arch_archive_break_revision_lock (t_uchar ** errstr, struct arch_archive * a,
                                  t_uchar * version,
                                  t_uchar * prev_level,
                                  t_uchar * uid,
                                  t_uchar * txn_id)
{
  ensure_writable (a, 1);
  return a->vtable->break_revision_lock (errstr, a, version, prev_level, uid, txn_id);
}




int
arch_archive_put_log (t_uchar ** errstr, struct arch_archive * a,
                      t_uchar * version,
                      t_uchar * prev_level,
                      t_uchar * uid,
                      t_uchar * txn_id,
                      t_uchar * log_text)
{
  ensure_writable (a, 0);
  return a->vtable->put_log (errstr, a, version, prev_level, uid, txn_id, log_text);
}



int
arch_archive_put_continuation (t_uchar ** errstr, struct arch_archive * a,
                               t_uchar * version,
                               t_uchar * prev_level,
                               t_uchar * uid,
                               t_uchar * txn_id,
                               t_uchar * continuation)
{
  ensure_writable (a, 0);
  return a->vtable->put_continuation (errstr, a, version, prev_level, uid, txn_id, continuation);
}


int
arch_archive_put_changeset_targz (t_uchar ** errstr, struct arch_archive * a,
                                  t_uchar * version,
                                  t_uchar * prev_level,
                                  t_uchar * uid,
                                  t_uchar * txn_id,
                                  t_uchar * level,
                                  int in_fd)
{
  int answer;

  answer = a->vtable->put_changeset (errstr, a, version, prev_level, uid, txn_id, level, in_fd);

  return answer;
}


int
arch_archive_put_changeset (t_uchar ** errstr, struct arch_archive * a,
                            t_uchar * version,
                            t_uchar * prev_level,
                            t_uchar * uid,
                            t_uchar * txn_id,
                            t_uchar * level,
                            t_uchar * dir)
{
  t_uchar * dir_tail = 0;
  t_uchar * desired_name = 0;
  t_uchar * tar_file_path = 0;
  int in_fd;
  int answer;

  dir_tail = file_name_tail (0, dir);
  desired_name = str_alloc_cat_many (0, version, "--", level, ".patches", str_end);
  invariant (!str_cmp (dir_tail, desired_name));


  /* GNU tar 1.13 is busted and doesn't output to pipes correctly.
   */

  ensure_writable (a, 0);

  tar_file_path = make_tmp_tar_archive (dir);

  in_fd = safe_open (tar_file_path, O_RDONLY, 0);

  answer = a->vtable->put_changeset (errstr, a, version, prev_level, uid, txn_id, level, in_fd);

  safe_close (in_fd);
  safe_unlink (tar_file_path);

  lim_free (0, dir_tail);
  lim_free (0, desired_name);
  lim_free (0, tar_file_path);

  return answer;
}


int
arch_archive_put_import_targz (t_uchar ** errstr, struct arch_archive * a,
                               t_uchar * version,
                               t_uchar * prev_level,
                               t_uchar * uid,
                               t_uchar * txn_id,
                               t_uchar * level,
                               int in_fd)
{
  int answer;

  answer = a->vtable->put_import (errstr, a, version, prev_level, uid, txn_id, level, in_fd);

  return answer;
}


int
arch_archive_put_import (t_uchar ** errstr, struct arch_archive * a,
                         t_uchar * version,
                         t_uchar * prev_level,
                         t_uchar * uid,
                         t_uchar * txn_id,
                         t_uchar * level,
                         t_uchar * dir)
{
  t_uchar * dir_tail = 0;
  t_uchar * desired_name = 0;
  t_uchar * tar_file_path = 0;
  int in_fd;
  int answer;

  dir_tail = file_name_tail (0, dir);
  desired_name = str_alloc_cat_many (0, version, "--", level, str_end);
  invariant (!str_cmp (dir_tail, desired_name));


  /* GNU tar 1.13 is busted and doesn't output to pipes correctly.
   */

  ensure_writable (a, 0);

  tar_file_path = make_tmp_tar_archive (dir);

  in_fd = safe_open (tar_file_path, O_RDONLY, 0);

  answer = a->vtable->put_import (errstr, a, version, prev_level, uid, txn_id, level, in_fd);

  safe_close (in_fd);
  safe_unlink (tar_file_path);

  lim_free (0, dir_tail);
  lim_free (0, desired_name);
  lim_free (0, tar_file_path);

  return answer;
}

int
arch_archive_put_cached_targz (t_uchar ** errstr, struct arch_archive * a, t_uchar * revision, int in_fd)
{
  return a->vtable->put_cached (errstr, a, NULL, revision, in_fd);
}

int
arch_archive_mirror_cached_targz (t_uchar ** errstr, struct arch_archive * a, struct arch_archive *from_archive, t_uchar * revision, int in_fd)
{
  return a->vtable->put_cached (errstr, a, from_archive, revision, in_fd);
}


int
arch_archive_put_cached (t_uchar ** errstr, struct arch_archive * a, t_uchar * revision, t_uchar * dir)
{
  t_uchar * dir_tail = 0;
  t_uchar * tar_file_path = 0;
  int in_fd;
  int answer;

  dir_tail = file_name_tail (0, dir);
  invariant (!str_cmp (dir_tail, revision));


  /* GNU tar 1.13 is busted and doesn't output to pipes correctly.
   */

  ensure_writable (a, 1);

  tar_file_path = make_tmp_tar_archive (dir);

  in_fd = safe_open (tar_file_path, O_RDONLY, 0);

  answer = arch_archive_put_cached_targz (errstr, a, revision, in_fd);

  safe_close (in_fd);
  safe_unlink (tar_file_path);

  lim_free (0, dir_tail);
  lim_free (0, tar_file_path);

  return answer;
}


int
arch_archive_delete_cached (t_uchar ** errstr, struct arch_archive * a, t_uchar * revision)
{
  ensure_writable (a, 1);
  return a->vtable->delete_cached (errstr, a, revision);
}

void
arch_archive_repair_non_txnal (int chatter_fd, struct arch_archive * a)
{
  ensure_writable (a, 1);
  a->vtable->repair_non_txnal (chatter_fd, a);
}

void
arch_archive_get_ancestry (int out_fd, struct arch_archive * arch, t_uchar * revision)
{
  arch->vtable->get_ancestry (out_fd, arch, revision);
}

int
arch_archive_put_ancestry (t_uchar ** errstr, struct arch_archive * a, t_uchar * revision, int in_fd)
{
  return a->vtable->put_ancestry (errstr, a, NULL, revision, in_fd);
}

int
arch_archive_mirror_ancestry (t_uchar ** errstr, struct arch_archive * a, struct arch_archive *from_archive,  t_uchar * revision, int in_fd)
{
  return a->vtable->put_ancestry (errstr, a, from_archive, revision, in_fd);
}

int
arch_archive_delete_ancestry (t_uchar ** errstr, struct arch_archive * a, t_uchar * revision)
{
  ensure_writable (a, 1);
  return a->vtable->delete_ancestry (errstr, a, revision);
}



t_uchar *
arch_generate_txn_id (void)
{
  time_t now;
  pid_t pid;
  char hostname[64];
  char number[64];
  t_uchar * answer;

  now = time (0);
  pid = getpid ();

  mem_set0 ((t_uchar *)hostname, sizeof (hostname));
  gethostname (hostname, sizeof (hostname));

  cvt_ulong_to_hex (number, hash_ul((t_ulong)now ^ (t_ulong)pid));
  answer = str_save (0, number);

  cvt_ulong_to_hex (number, hash_mem ((t_uchar *)hostname, sizeof (hostname)));
  answer = str_realloc_cat (0, answer, number);

  cvt_ulong_to_hex (number, hash_ul ((t_ulong)now ^ ~(t_ulong)pid));
  answer = str_realloc_cat (0, answer, number);

  return answer;
}


arch_patch_id *
arch_previous_revision (struct arch_archive * arch, arch_patch_id * revision)
{
  enum arch_patch_level_type current_type;
  enum arch_patch_level_type next_type;
  t_ulong current_n;
  t_ulong next_n;
  t_uchar * next_patch_level = 0;
  arch_patch_id * answer = 0;

  /* TRANSITIONAL : remove when pools are implemented */
  invariant_str_cmp (arch->official_name, arch_patch_id_archive (revision));

  current_type = arch_analyze_patch_level (&current_n, arch_patch_id_patchlevel (revision));

  if (current_type == arch_is_base0_level)
    {
      answer = NULL;
    }
  else
    {
      if (((current_type == arch_is_patch_level) || (current_type == arch_is_versionfix_level)) && (current_n > 1))
        {
          next_type = current_type;
          next_n = current_n - 1;
        }
      else if (current_n == 1)
        {
          next_n = 0;
          if (current_type == arch_is_patch_level)
            next_type = arch_is_base0_level;
          else
            next_type = arch_is_version_level;
        }
      else
        {
          rel_table revisions = 0;
          int x;

          revisions = arch_archive_revisions (arch, arch_patch_id_version (revision), 0);

          if (!revisions)
            {
              safe_printfmt (2, "version has no revisions (%s) in url: %s\n", arch_patch_id_patch_id (revision), arch->location);
              exit (2);
            }

          for (x = 0; x < rel_n_records (revisions); ++x)
            if (revisions[x][0][0] == 'v')
              break;

          invariant (x);

          if (x == rel_n_records (revisions))
            x = rel_n_records (revisions);

          if (x == 1)
            {
              next_type = arch_is_base0_level;
              next_n = 0;
            }
          else
            {
              next_type = arch_is_patch_level;
              next_n = x - 1;
            }

          rel_free_table (revisions);
        }

      next_patch_level = arch_form_patch_level (next_type, next_n);
      {
        t_uchar * temp_answer = str_alloc_cat_many (0, arch_patch_id_version (revision), "--", next_patch_level, str_end);
        answer = arch_patch_id_new_archive (arch->official_name, temp_answer);
        lim_free (0, temp_answer);
      }
    }

  lim_free (0, next_patch_level);

  return answer;
}


t_uchar *
arch_ancestor_revision (struct arch_archive * arch, t_uchar * revision)
{
  enum arch_revision_type type;

  arch_patch_id * revision_patch = arch_patch_id_new_archive (arch->official_name, revision);
  arch_revision_type (&type, NULL, NULL, arch, revision_patch);

  switch (type)
    {
    default:
      {
        panic ("arch_ancestor_revision: unrecognized revision type");
        talloc_free (revision_patch);
        return 0;
      }
    case arch_import_revision:
      {
        talloc_free (revision_patch);
        return 0;
      }
    case arch_simple_revision:
      {
        arch_patch_id * prev_revision = 0;
        t_uchar * answer = 0;

        prev_revision = arch_previous_revision (arch, revision_patch);
        answer = str_save (0, arch_patch_id_patch_id (prev_revision));

        talloc_free (prev_revision);
        talloc_free (revision_patch);

        return answer;
      }
    case arch_continuation_revision:
      {
        arch_patch_id * result = arch_get_continuation (arch, revision_patch);
        t_uchar * answer = str_save (0, arch_patch_id_patch_id (result));
        talloc_free (revision_patch);
        talloc_free (result);
        return answer;
      }
    }
}


/* this creates a temporary file name for use in archive 
 * operations.
 */
t_uchar *
archive_tmp_file_name (t_uchar * dir, t_uchar * basename)
{
  t_uchar * my_uid = 0;
  t_uchar * tmp_name = 0;
  t_uchar * talloced_tmp;

  my_uid = arch_my_id_uid_default ("Unknown User <example@example.com>");
  talloced_tmp = talloc_tmp_file_name (talloc_context, dir, basename);

  tmp_name = str_alloc_cat_many (0, talloced_tmp, ".", my_uid, str_end);

  lim_free (0, my_uid);
  talloc_free (talloced_tmp);

  return tmp_name;
}




static int
invoke_tar_extract (int * pid_ret)
{
  int pipe_fds[2];
  int pid;

  if (pipe (pipe_fds))
    panic ("unable to create pipe fds for tar");

  pid = fork ();

  if (pid == -1)
    panic ("unable to fork for patch");

  if (pid)
    {
      *pid_ret = pid;
      safe_close (pipe_fds[0]);
      return pipe_fds[1];
    }
  else
    {
      t_uchar ** argv;

      safe_close (pipe_fds[1]);

      argv = 0;

      ar_push_uchar_star (&argv, cfg__gnu_tar);
      ar_push_uchar_star (&argv, "-m");
      ar_push_uchar_star (&argv, "--preserve");
      ar_push_uchar_star (&argv, "-zxf");
      ar_push_uchar_star (&argv, "-");
      ar_push_uchar_star (&argv, 0);

      safe_move_fd (pipe_fds[0], 0);

      arch_util_execvp (cfg__gnu_tar, argv);
      panic ("invoke_tar_extract: execvp for patch returned to caller");
      exit (2);
    }
  panic ("invoke_tar_extract: not reached");
  return -1;
}

static int
wait_for_tar (int pid)
{
  int status;
  int wait_pid;

  wait_pid = waitpid (pid, &status, 0);
  if (wait_pid < 0)
    {
      panic_msg ("error waiting for tar subprocess");
      kill (0, SIGKILL);
      panic ("error waiting for subprocess");
    }
  if (WIFSIGNALED (status))
    {
      safe_printfmt (2, "\n");
      safe_printfmt (2, "wait_for_tar: tar subprocess killed by signal %d\n", WTERMSIG (status));
      safe_printfmt (2, "\n");
      exit (2);
      return -1;
    }
  else if (!WIFEXITED (status))
    {
      panic_msg ("waitpid returned for a non-exited process");
      kill (0, SIGKILL);
      panic ("waitpid returned for a non-exited process");
      return -1;
    }
  else
    {
      int exit_status;

      exit_status = WEXITSTATUS (status);
      return exit_status;
    }
}


t_uchar *
make_tmp_tar_archive (t_uchar * dir)
{
  t_uchar * dir_dir = 0;
  t_uchar * dir_tail = 0;
  t_uchar * tmp_stem = 0;
  t_uchar * tmp_in_cwd = 0;
  t_uchar * tmp_path = 0;

  dir_dir = file_name_directory_file (0, dir);
  if (!dir_dir)
    dir_dir = str_save (0, ".");

  dir_tail = file_name_tail (0, dir);

  tmp_stem = str_alloc_cat_many (0, ",,", dir_tail, ".tar.gz", str_end);
  tmp_in_cwd = talloc_tmp_file_name (talloc_context, ".", tmp_stem);
  tmp_path = file_name_in_vicinity (0, dir_dir, tmp_in_cwd);

  {
    int pid;
    int dev_null_fd;

    dev_null_fd = safe_open ("/dev/null", O_WRONLY, 0);

    pid = fork ();

    if (pid == -1)
      panic ("unable to fork for patch");

    if (!pid)
      {
        t_uchar ** argv;

        safe_chdir (dir_dir);

        argv = 0;

        ar_push_uchar_star (&argv, cfg__gnu_tar);
        ar_push_uchar_star (&argv, "--force-local");
        ar_push_uchar_star (&argv, "-zcf");
        ar_push_uchar_star (&argv, tmp_in_cwd);
        ar_push_uchar_star (&argv, dir_tail);
        ar_push_uchar_star (&argv, 0);

        safe_move_fd (dev_null_fd, 1);
        safe_dup2 (1, 2);

        arch_util_execvp (cfg__gnu_tar, argv);
        panic ("make_tmp_tar_archive: execvp for patch returned to caller");
        exit (2);
      }
    else
      {
        int status;
        int wait_pid;

        safe_close (dev_null_fd);

        wait_pid = waitpid (pid, &status, 0);
        if (wait_pid < 0)
          {
            panic_msg ("error waiting for tar subprocess");
            kill (0, SIGKILL);
            panic ("error waiting for subprocess");
          }
        if (WIFSIGNALED (status))
          {
            safe_printfmt (2, "\n");
            safe_printfmt (2, "wait_for_tar: tar subprocess killed by signal %d\n", WTERMSIG (status));
            safe_printfmt (2, "\n");
            exit (2);
            return 0;
          }
        else if (!WIFEXITED (status))
          {
            panic_msg ("waitpid returned for a non-exited process");
            kill (0, SIGKILL);
            panic ("waitpid returned for a non-exited process");
            return 0;
          }
        else
          {
            int exit_status;

            exit_status = WEXITSTATUS (status);

            if (exit_status)
              panic ("make_tmp_tar_archive: tar exitted with non-0 status");
          }
      }
  }

  lim_free (0, dir_dir);
  lim_free (0, dir_tail);
  talloc_free (tmp_in_cwd);
  lim_free (0, tmp_stem);

  return tmp_path;
}

static void
ensure_writable (struct arch_archive * arch, int mirror_ok)
{
  t_uchar * write_failure;

  write_failure = arch_archive_not_writable (arch, mirror_ok);

  if (write_failure)
   {
     safe_printfmt (2, write_failure);
     exit(2);
   }
}

int
arch_get_meta_int_info(struct arch_archive * arch, t_uchar * key)
{
  t_uchar * key_existence = arch_get_meta_info (arch, key);
  int result  = !!key_existence;

  lim_free (0, key_existence);

  return result;
}


/**
 * Performs a tar on the specified dir, writing output to out_fd
 * out_fd The file descriptor to write to
 * dir The directory to tar up
 */
static void tar_to_fd (int out_fd, t_uchar * dir)
{
  t_uchar * tar_file_path = make_tmp_tar_archive (dir);

  int in_fd = safe_open (tar_file_path, O_RDONLY, 0);
  copy_fd (in_fd, out_fd);
  safe_unlink (tar_file_path);
  safe_close (in_fd);
}


/**
 * If the archive is cached, cache this changeset
 * arch The archive containing the revision
 * revision The revision of the changeset
 * changeset_path The path to the changeset directory
 */
extern void arch_maybe_cache_commit (struct arch_archive *arch, 
                                     t_uchar * revision,
                                     t_uchar * anc_archive,
                                     t_uchar * anc_revision,
                                     t_uchar * changeset_path)
{
  if (str_cmp(arch->vtable->type, "cache") != 0)
    return;
  else
    {
      t_uchar * query = arch_revision_query (arch, revision, "delta.tar.gz");
      t_uchar * tmp_name = 0;
      int put_fd = arch_cache_put (&tmp_name, query);
      t_uchar * fq_ancestor = arch_fully_qualify (anc_archive, anc_revision);
      tar_to_fd (put_fd, changeset_path);
      safe_close (put_fd);
      arch_cache_commit (tmp_name, query);
      
      lim_free (0, query);
      lim_free (0, tmp_name);

      query = arch_revision_query (arch, revision, "ancestor");
      arch_cache_put_line (query, fq_ancestor);
      lim_free (0, query);
      lim_free (0, fq_ancestor);
    }
}

t_uchar *
arch_fs_archive_category_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar * category)
{
  if (arch->type == arch_archive_baz)
      panic ("baz does not support stand alone categories");
  return file_name_in_vicinity (0, archive_path, category);
}

t_uchar *
arch_fs_archive_branch_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar * package)
{
  t_uchar * category = 0;
  t_uchar * category_path = 0;
  t_uchar * branch_path = 0;

  if (arch->type == arch_archive_baz)
      panic ("baz does not support stand alone branches");

  invariant (arch_valid_package_name (package, arch_no_archive, arch_req_package, 0));

  category = arch_parse_package_name (arch_ret_category, 0, package);
  category_path = arch_fs_archive_category_path (arch, archive_path, category);
  branch_path = file_name_in_vicinity (0, category_path, package);

  lim_free (0, category);
  lim_free (0, category_path);
  return branch_path;
}


t_uchar *
arch_fs_archive_version_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar * version)
{
  t_uchar * package = 0;
  t_uchar * package_path = 0;
  t_uchar * version_path = 0;

  invariant (arch_valid_package_name (version, arch_no_archive, arch_req_version, 0));
  
  if (arch->type == arch_archive_baz)
      return str_save (0, version);

  package = arch_parse_package_name (arch_ret_package, 0, version);
  package_path = arch_fs_archive_branch_path (arch, archive_path, package);
  version_path = file_name_in_vicinity (0, package_path, version);

  lim_free (0, package);
  lim_free (0, package_path);

  return version_path;
}

t_uchar *
arch_fs_archive_revision_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar const * revision)
{
  t_uchar * version = 0;
  t_uchar * version_path = 0;
  t_uchar * level = 0;
  t_uchar * revision_path = 0;

  invariant (arch_valid_package_name (revision, arch_no_archive, arch_req_patch_level, 0));

  version = arch_parse_package_name (arch_ret_package_version, 0, revision);
  version_path = arch_fs_archive_version_path (arch, archive_path, version);
  level = arch_parse_package_name (arch_ret_patch_level, 0, revision);
  revision_path = file_name_in_vicinity (0, version_path, level);

  lim_free (0, version);
  lim_free (0, version_path);
  lim_free (0, level);

  return revision_path;
}

t_uchar *
arch_fs_archive_revision_log_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar const * revision)
{
  t_uchar * revision_path = 0;
  t_uchar * log_path = 0;

  revision_path = arch_fs_archive_revision_path (arch, archive_path, revision);
  log_path = file_name_in_vicinity (0, revision_path, "log");

  lim_free (0, revision_path);

  return log_path;
}

t_uchar *
arch_fs_archive_changeset_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar const * revision)
{
  t_uchar * revision_path = 0;
  t_uchar * changeset_path;

  revision_path = arch_fs_archive_revision_path (arch, archive_path, revision);
  changeset_path = file_name_in_vicinity (0, revision_path, revision);
  changeset_path = str_realloc_cat (0, changeset_path, ".patches.tar.gz");

  lim_free (0, revision_path);

  return changeset_path;
}

t_uchar *
arch_fs_archive_import_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar const * revision)
{
  t_uchar * revision_path = 0;
  t_uchar * changeset_path;

  revision_path = arch_fs_archive_revision_path (arch, archive_path, revision);
  changeset_path = file_name_in_vicinity (0, revision_path, revision);
  changeset_path = str_realloc_cat (0, changeset_path, ".src.tar.gz");

  lim_free (0, revision_path);

  return changeset_path;
}

t_uchar *
arch_fs_archive_cached_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar const * revision)
{
  t_uchar * revision_path = 0;
  t_uchar * changeset_path;

  revision_path = arch_fs_archive_revision_path (arch, archive_path, revision);
  changeset_path = file_name_in_vicinity (0, revision_path, revision);
  changeset_path = str_realloc_cat (0, changeset_path, ".tar.gz");

  lim_free (0, revision_path);

  return changeset_path;
}

static t_uchar *
arch_fs_archive_per_revision_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar const * revision, t_uchar const *filename)
{
  t_uchar * revision_path = 0;
  t_uchar * checksum_path = 0;

  revision_path = arch_fs_archive_revision_path (arch, archive_path, revision);
  checksum_path = file_name_in_vicinity (0, revision_path, filename);

  lim_free (0, revision_path);

  return checksum_path;
}

t_uchar *
arch_fs_archive_cached_checksum_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar const * revision)
{
    return arch_fs_archive_per_revision_path (arch, archive_path, revision, "checksum.cacherev");
}

t_uchar *
arch_fs_archive_ancestry_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar const * revision)
{
    return arch_fs_archive_per_revision_path (arch, archive_path, revision, "ancestry.gz");
}

t_uchar *
arch_fs_archive_ancestry_checksum_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar const * revision)
{
    return arch_fs_archive_per_revision_path (arch, archive_path, revision, "ancestry.gz.checksum");
}

t_uchar *
arch_fs_archive_continuation_path (struct arch_archive * arch, t_uchar * archive_path, t_uchar const * revision)
{
  t_uchar * revision_path = 0;
  t_uchar * continuation_path;

  revision_path = arch_fs_archive_revision_path (arch, archive_path, revision);
  continuation_path = file_name_in_vicinity (0, revision_path, "CONTINUATION");

  lim_free (0, revision_path);

  return continuation_path;
}



t_uchar *
arch_fs_archive_revision_lock_unlocked_path (struct arch_archive * arch,
					     t_uchar * archive_path,
                                             t_uchar * version,
                                             t_uchar * prev_level)
{
  t_uchar * answer = 0;

  invariant (arch_valid_package_name (version, arch_no_archive, arch_req_version, 0));

  if (!prev_level)
    {
      t_uchar * version_path = 0;

      version_path = arch_fs_archive_version_path (arch, archive_path, version);
      answer = file_name_in_vicinity (0, version_path, "++revision-lock");

      lim_free (0, version_path);
    }
  else
    {
      t_uchar * revision = 0;
      t_uchar * revision_path = 0;

      revision = str_alloc_cat_many (0, version, "--", prev_level, str_end);
      revision_path = arch_fs_archive_revision_path (arch, archive_path, revision);

      answer = file_name_in_vicinity (0, revision_path, "++revision-lock");

      lim_free (0, revision);
      lim_free (0, revision_path);
    }

  return answer;
}


t_uchar *
arch_fs_archive_revision_lock_locked_path (struct arch_archive * arch,
					   t_uchar * archive_path,
                                           t_uchar * version,
                                           t_uchar * prev_level,
                                           t_uchar * arch_user_id,
                                           t_uchar * txn_id)
{
  t_uchar * version_path = 0;
  t_uchar * lock_basename = 0;
  t_uchar * answer = 0;

  invariant (arch_valid_package_name (version, arch_no_archive, arch_req_version, 0));

  version_path = arch_fs_archive_version_path (arch, archive_path, version);

  lock_basename = str_alloc_cat_many (0,
                                      "++revision-lock-held--",
                                      (prev_level ? prev_level : (t_uchar *)"absolute-0"),
                                      "--",
                                      arch_user_id,
                                      (txn_id ? "--" : 0),
                                      txn_id,
                                      str_end);

  answer = file_name_in_vicinity (0, version_path, lock_basename);

  lim_free (0, version_path);
  lim_free (0, lock_basename);
  return answer;
}


t_uchar *
arch_fs_archive_revision_lock_locked_contents_path (struct arch_archive * arch,
						    t_uchar * archive_path,
                                                    t_uchar * version,
                                                    t_uchar * prev_level,
                                                    t_uchar * arch_user_id,
                                                    t_uchar * txn_id)
{
  t_uchar * locked_path = 0;
  t_uchar * answer = 0;

  locked_path = arch_fs_archive_revision_lock_locked_path (arch, archive_path, version, prev_level, arch_user_id, txn_id);
  answer = file_name_in_vicinity (0, locked_path, "+contents");

  lim_free (0, locked_path);
  return answer;
}


t_uchar *
arch_fs_archive_revision_lock_broken_path (struct arch_archive * arch,
					   t_uchar * archive_path,
                                           t_uchar * version,
                                           t_uchar * prev_level)
{
  t_uchar * version_path = 0;
  t_uchar * lock_dir_basename = 0;
  t_uchar * lock_basename = 0;
  t_uchar * broken_dir = 0;
  t_uchar * answer = 0;

  version_path = arch_fs_archive_version_path (arch, archive_path, version);

  lock_dir_basename = str_alloc_cat (0, "++revision-lock-broken--", (prev_level ? prev_level : (t_uchar *)"absolute-0"));
  lock_basename = str_alloc_cat (0, ",,remade-lock--", (prev_level ? prev_level : (t_uchar *)"absolute-0"));

  broken_dir = file_name_in_vicinity (0, version_path, lock_dir_basename);
  answer = file_name_in_vicinity (0, broken_dir, lock_basename);

  lim_free (0, version_path);
  lim_free (0, lock_dir_basename);
  lim_free (0, lock_basename);
  lim_free (0, broken_dir);
  return answer;
}

void
arch_archive_connection_cache_init (arch_archive_connection_cache * cache)
{
    cache->cache = NULL;
}

void 
arch_archive_connection_cache_finalise (arch_archive_connection_cache * cache, struct arch_archive * dontclose)
{
    int position;

    for (position = 0; position < ar_size_arch_archive (cache->cache); ++position)
      {
        if (cache->cache[position] != dontclose)
          arch_archive_close (cache->cache[position]);
      }
    ar_free_arch_archive (&cache->cache);
    cache->cache = NULL;
}

struct arch_archive *
arch_archive_connection_cache_find_or_maybe_connect (arch_archive_connection_cache * cache, t_uchar *name, int soft_errors)
{
  int position;
  ar_archive_location locations;
  struct arch_archive * arch = 0;

  for (position = 0; position < ar_size_arch_archive (cache->cache); ++position)
    {
      if (!str_cmp (name, cache->cache[position]->official_name))
        return (cache->cache)[position];
    }

  locations = arch_archive_locations (name);

  if (ar_size_archive_location(locations))
    {
      arch = arch_archive_connect_branch (name, NULL);
      invariant (!arch || !str_cmp (name, arch->official_name));
      if (arch)
          arch_archive_connection_cache_add (cache, arch);
    }

  ar_free_archive_location (&locations);
  return arch;
}

void
arch_archive_connection_cache_add (arch_archive_connection_cache * cache, struct arch_archive * arch)
{
  ar_push_arch_archive (&cache->cache, arch);
}

/**
 * \brief determine if arch has any registry-style configuration details 
 * \param arch the archive to query
 * \return non zero if the arch does have registry style config details
 */
int 
arch_archive_has_registry_entry (struct arch_archive * arch)
{
    arch->in_registry = arch_archives_has_registry_entry (arch->official_name);
    return arch->in_registry;
}

/** 
 * \brief check an archives signed status against its expected status
 * \param fd the fd to output warnings on
 * \param archive the archive
 * \return 0 if the archive handle can be used to read data
 */
int
arch_archive_check_signed_status (struct arch_archive *archive, int status_fd)
{
    if (archive->signed_archive)
        return 0;
    if (!archive->when_unsigned)
        return 0;
    if (status_fd > -1 )
      {
        safe_printfmt (status_fd, "\n");
        safe_printfmt (status_fd, archive->when_unsigned == arch_fail_error ? _("ERROR: ") : _("WARNING: "));
        safe_printfmt (status_fd, _("archive %s is configured as a signed archive, but its copy at location %s is not signed.\n"
                                    "Someone may have tampered with this archive.\n"
                                    "\n"),
                       archive->official_name, archive->location);
      }
    return archive->when_unsigned == arch_fail_error;
}

/**
 * \brief inform an archive that it should be or not be a mirror
 */
int
arch_archive_set_mirror (t_uchar ** errstr, struct arch_archive *archive, int enabled)
{
    int errn;
    
    if ((enabled && archive->mirror_of) || (!enabled && !archive->mirror_of))
        return 0;

    if (!(errn = archive->vtable->set_mirror (errstr, archive, enabled)))
      {
        if (enabled)
            archive->mirror_of = str_save (0, archive->official_name);
        else
          {
            lim_free (0, archive->mirror_of);
            archive->mirror_of = NULL;
          }
      }
    
    return errn;
}

/**
 * \brief strcmp that understands locations are urls
 */
int
arch_archive_cmp_location (t_uchar const *left, t_uchar const *right)
{
    t_uchar *normleft = arch_uncached_location (left);
    t_uchar *normright = arch_uncached_location (right);
    int result = str_cmp (normleft, normright);
    lim_free (0, normleft);
    lim_free (0, normright);
    return result;
}

/**
 * \brief is this archive conenction cached ?
 * 
 * while its bogus to have to ask this, until the archive format doesn't depend on bit equivalence for tarballs, we have to
 */
int 
arch_archive_is_cached_connection (struct arch_archive *archive)
{
  return !str_cmp (archive->vtable->type, "cache");
}


/**
 * \brief Determine whether the archive is writable
 * \param archive The archive to test
 * \return true if the archive is writable, false if it's readonly
 * \note This operation is race-safe, because it only depends on whether
 * the file can be written, not its contents.
 */
static int
writable_archive (struct arch_archive *archive)
{
  int writable = archive->access == arch_archive_writable;
  if (!writable)
    return writable;
  writable = arch_set_meta_info (archive, "writable", "test") == 0;
  if (writable)
    arch_set_meta_info (archive, "writable", NULL);
  return writable;
}


/* tag: Tom Lord Tue May 20 00:52:06 2003 (archive.c)
 */


syntax highlighted by Code2HTML, v. 0.9.1