/* archive.c:
 *
 ****************************************************************
 * 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 "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/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 "tla/libfsutils/tmp-files.h"
#include "tla/libfsutils/rmrf.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/my.h"
#include "tla/libarch/exec.h"
#include "tla/libarch/archives.h"
#include "tla/libarch/archive-version.h"
#include "tla/libarch/pfs.h"
#include "tla/libarch/archive-pfs.h"
#include "tla/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 * connected_by_name = 0;



void
arch_make_archive (const t_uchar * name,
                   const t_uchar * location,
                   const t_uchar * mirror_of,
                   int dot_listing_lossage,
                   int signed_archive,
                   int tla_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)
    {
      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);

  arch_pfs_make_archive (name, location, version, mirror_of, dot_listing_lossage, signed_archive);

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

  lim_free (0, version);
}


struct arch_archive *
arch_archive_connect_location (const t_uchar * name,
                               const t_uchar * location,
                               const t_uchar * want_mirror_of)
{
  struct arch_archive * answer = 0;

  answer = lim_malloc (0, sizeof (*answer));
  mem_set0 ((t_uchar *)answer, sizeof (*answer));

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

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

  arch_pfs_archive_connect (&answer);

  answer->version = answer->vtable->archive_version (answer);
  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));
      exit (2);
    }

  answer->mirror_of = arch_get_meta_info_trimming (answer, "mirror");
  answer->official_name = arch_get_meta_info_trimming (answer, "name");
  if (!answer->official_name)
    {
      int ign;

      printfmt (&ign, 2, "Unable to read the official name of archive %s\n  Archive damaged?\n", name);
      exit (2);
    }
  lim_free (0, answer->name);
  answer->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 (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)"));
      exit (2);
    }

  return answer;
}


struct arch_archive *
arch_archive_connect (const t_uchar * name,
                      const t_uchar * want_mirror_of)
{
  struct arch_archive * answer = 0;

  for (answer = connected_by_name;
       answer && str_cmp (answer->name, name);
       answer = answer->next)
    ;

  if (answer)
    {
      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, want_mirror_of, (answer->mirror_of ? answer->mirror_of : (t_uchar *)"(not a mirror)"));
          exit (2);
        }

      ++answer->refs;
      return answer;
    }
  else
    {
      t_uchar * location = 0;

      location = arch_archive_location (name, 0);

      answer = arch_archive_connect_location (name, location, want_mirror_of);
      lim_free (0, answer->name);
      answer->name = str_save (0, name);
      answer->refs = 1;
      answer->next = connected_by_name;
      connected_by_name = answer;

      lim_free(0, location);
      return answer;
    }
}


void
arch_archive_close (struct arch_archive * arch)
{
  if (!arch)
    return;

  if (arch->refs)
    --arch->refs;

  if (!arch->refs)
    {
      struct arch_archive ** handle = &connected_by_name;

      while (*handle)
        {
          if (*handle != arch)
            handle = &((*handle)->next);
          else
            {
              *handle = (*handle)->next;
            }
        }

      arch->vtable->close (arch);
      lim_free (0, arch->name);
      lim_free (0, arch->official_name);
      lim_free (0, arch->location);
      lim_free (0, arch->version);
      lim_free (0, arch);
    }
}


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  = rel_table_nil;

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

  return answer;
}


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

  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,
                       const t_uchar * package)
{
  rel_table answer  = rel_table_nil;

  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,
                              const t_uchar * version,
                              int full)
{
  rel_table revs  = rel_table_nil;
  t_uchar * answer = 0;

  revs = arch_archive_revisions (arch, version, full);

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

  rel_free_table (revs);
  return answer;
}


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

  answer = arch->vtable->revisions (arch, 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->name, version);
      else
        fqv = str_save (0, version);

      for (x = 0; x < rel_n_records (answer); ++x)
        {
          const t_uchar * t;
          t_uchar * new_t;

          t = rel_peek_str (answer, x, 0);
          new_t = str_alloc_cat_many (0, fqv, "--", t, str_end);
          rel_set_taking (answer, x, 0, rel_make_field_str (new_t));
          lim_free (0, new_t);
        }

      lim_free (0, fqv);
    }

  return answer;
}

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

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

  return answer;
}


void
arch_revision_type (enum arch_revision_type * type,
                    int * is_cached,
                    struct arch_archive * arch,
                    const t_uchar * revision)
{
  if (arch->vtable->revision_type (type, is_cached, arch, revision) < 0)
    {
      safe_printfmt (2, "corrupt archive\n  name: %s\n  location: %s\n  revision: %s\n", arch->name, arch->location, revision);
      exit (2);
    }
}


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


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


void
arch_get_patch (struct arch_archive * arch,
                const t_uchar * revision,
                const t_uchar * 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;

  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 = tmp_file_name (dest_dir_dir_path, ",,get-patch");
  patches_file_name = str_alloc_cat (0, 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, 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);
  lim_free (0, 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,
                                const t_uchar * revision)
{
  arch->vtable->get_cached (out_fd, arch, revision);
}


void
arch_get_cached_revision (struct arch_archive * arch,
                          const t_uchar * revision,
                          const t_uchar * 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;

  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 = tmp_file_name (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, revision);
  safe_close (out_fd);
  if (wait_for_tar (tar_pid))
    panic ("arch_get_patch: tar exitted with non-0 status");
  safe_rename (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);
  lim_free (0, tmpdir_path);
  lim_free (0, patches_file_dest);
}


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


void
arch_get_import_revision (struct arch_archive * arch,
                          const t_uchar * revision,
                          const t_uchar * 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;

  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 = tmp_file_name (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, revision);
  safe_close (out_fd);
  if (wait_for_tar (tar_pid))
    panic ("arch_get_patch: tar exitted with non-0 status");
  safe_rename (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);
  lim_free (0, tmpdir_path);
  lim_free (0, patches_file_dest);
}


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

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

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

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

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

  lim_free (0, raw_data);

  return answer;
}

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

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

  return answer;
}


t_uchar *
arch_get_meta_info_trimming (struct arch_archive * arch,
                             const t_uchar * meta_info_name)
{
  t_uchar * intermediate = 0;
  t_uchar * answer = 0;

  intermediate = arch_get_meta_info (arch, meta_info_name);
  if (!intermediate)
    return 0;

  answer = str_save_trimming (0, intermediate);

  lim_free (0, intermediate);

  return answer;
}


int
arch_make_category (t_uchar ** errstr,
                    struct arch_archive * arch,
                    const 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->name, "ARCH_CATEGORY", category, (t_uchar*)0);
  return answer;
}

int
arch_make_branch (t_uchar ** errstr,
                  struct arch_archive * arch,
                  const 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->name, "ARCH_BRANCH", branch, (t_uchar*)0);
  return answer;
}

int
arch_make_version (t_uchar ** errstr,
                   struct arch_archive * arch,
                   const 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->name, "ARCH_VERSION", version, (t_uchar*)0);
  return answer;
}




int
arch_archive_lock_revision (t_uchar ** errstr,
                            struct arch_archive * a,
                            const t_uchar * version,
                            const t_uchar * prev_level,
                            const t_uchar * uid,
                            const t_uchar * txn_id,
                            const t_uchar * new_level)
{
  ensure_writable (a, 1);
  return a->vtable->lock_revision (errstr, a, version, prev_level, uid, txn_id, new_level);
}


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



int
arch_archive_finish_revision (t_uchar ** errstr,
                              struct arch_archive * a,
                              const t_uchar * version,
                              const t_uchar * prev_level,
                              const t_uchar * uid,
                              const t_uchar * txn_id,
                              const 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,
                                  const 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,
                                  const t_uchar * version,
                                  const t_uchar * prev_level,
                                  const t_uchar * uid,
                                  const 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,
                      const t_uchar * version,
                      const t_uchar * prev_level,
                      const t_uchar * uid,
                      const t_uchar * txn_id,
                      const 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,
                               const t_uchar * version,
                               const t_uchar * prev_level,
                               const t_uchar * uid,
                               const t_uchar * txn_id,
                               const 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,
                                  const t_uchar * version,
                                  const t_uchar * prev_level,
                                  const t_uchar * uid,
                                  const t_uchar * txn_id,
                                  const 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,
                            const t_uchar * version,
                            const t_uchar * prev_level,
                            const t_uchar * uid,
                            const t_uchar * txn_id,
                            const t_uchar * level,
                            const 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,
                               const t_uchar * version,
                               const t_uchar * prev_level,
                               const t_uchar * uid,
                               const t_uchar * txn_id,
                               const 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,
                         const t_uchar * version,
                         const t_uchar * prev_level,
                         const t_uchar * uid,
                         const t_uchar * txn_id,
                         const t_uchar * level,
                         const 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,
                               const t_uchar * revision,
                               int in_fd)
{
  int answer;

  answer = a->vtable->put_cached (errstr, a, revision, in_fd);

  return answer;
}

int
arch_archive_put_cached (t_uchar ** errstr,
                         struct arch_archive * a,
                         const t_uchar * revision,
                         const 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 = a->vtable->put_cached (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,
                            const 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);
}





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;
}


t_uchar *
arch_previous_revision (struct arch_archive * arch,
                        const t_uchar * revision)
{
  t_uchar * version = 0;
  t_uchar * patch_level = 0;
  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;
  t_uchar * answer = 0;


  version = arch_parse_package_name (arch_ret_package_version, 0, revision);
  patch_level = arch_parse_package_name (arch_ret_patch_level, 0, revision);

  current_type = arch_analyze_patch_level (&current_n, patch_level);

  if (current_type == arch_is_base0_level)
    {
      answer = 0;
    }
  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  = rel_table_nil;
          int x;

          revisions = arch_archive_revisions (arch, version, 0);

          if (!rel_n_records (revisions))
            {
              safe_printfmt (2, "version has no revisions (%s/%s)\n", arch->name, version);
              exit (2);
            }

          for (x = 0; x < rel_n_records (revisions); ++x)
            if (rel_peek_str (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);
      answer = str_alloc_cat_many (0, version, "--", next_patch_level, str_end);
    }

  lim_free (0, version);
  lim_free (0, patch_level);
  lim_free (0, next_patch_level);

  return answer;
}


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

  arch_revision_type (&type, 0, arch, revision);

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

        prev_revision = arch_previous_revision (arch, revision);
        answer = arch_fully_qualify (arch->name, prev_revision);

        lim_free (0, prev_revision);

        return answer;
      }
    case arch_continuation_revision:
      {
        return arch_get_continuation (arch, revision);
      }
    }
}



t_uchar *
archive_tmp_file_name (const t_uchar * dir,
                       const t_uchar * basename)
{
  t_uchar * my_uid = 0;
  t_uchar * tmp_name = 0;

  my_uid = arch_my_id_uid ();
  tmp_name = tmp_file_name (dir, basename);

  tmp_name = str_realloc_cat_many (0, tmp_name, ".", my_uid, str_end);

  lim_free (0, my_uid);

  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;

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = cfg__gnu_tar;
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-m";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "--preserve";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-zxf";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = 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 (const 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 = tmp_file_name (".", 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;

        *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = cfg__gnu_tar;
        *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-zcf";
        *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = tmp_in_cwd;
        *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = dir_tail;
        *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = 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);
  lim_free (0, tmp_in_cwd);
  lim_free (0, tmp_stem);

  return tmp_path;
}



static void
ensure_writable (struct arch_archive * arch,
                 int mirror_ok)
{
  if (arch->access != arch_archive_writable)
    {
      safe_printfmt (2, "attempt to modify archive that has only read-compatibility\n    archive: %s\n", arch->name);
      exit (2);
    }

  if (!mirror_ok)
    {
      if (!arch->client_anticipates_mirror && !!arch->mirror_of)
        {
          safe_printfmt (2, "attempt to write directly to mirror\n    archive: %s\n    mirror of: %s\n", arch->name, arch->mirror_of);
          exit (2);
        }

      if (arch->client_anticipates_mirror && !arch->mirror_of)
        {
          safe_printfmt (2, "archive is not a mirror of %s\n    archive: %s\n    expected mirror of: %s\n", arch->client_anticipates_mirror, arch->name, arch->client_anticipates_mirror);
          exit (2);
        }

      if (arch->client_anticipates_mirror && str_cmp (arch->client_anticipates_mirror, arch->mirror_of))
        {
          safe_printfmt (2, "attempt to write to the wrong mirror\n    archive: %s\n    expected mirror of: %s\n    got mirror of: %s\n",
                         arch->name, arch->client_anticipates_mirror, arch->mirror_of);
          exit (2);
        }
    }
}


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

  lim_free (0, key_existence);

  return result;
}

t_uchar *
arch_fs_archive_category_path (const struct arch_archive * arch,
                               const t_uchar * archive_path,
                               const 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 (const struct arch_archive * arch,
                             const t_uchar * archive_path,
                             const 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 (const struct arch_archive * arch,
                              const t_uchar * archive_path,
                              const 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 (const struct arch_archive * arch,
                               const t_uchar * archive_path,
                               const t_uchar * 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 (const struct arch_archive * arch,
                                   const t_uchar * archive_path,
                                   const t_uchar * 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 (const struct arch_archive * arch,
                                const t_uchar * archive_path,
                                const t_uchar * 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 (const struct arch_archive * arch,
                             const t_uchar * archive_path,
                             const t_uchar * 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 (const struct arch_archive * arch,
                             const t_uchar * archive_path,
                             const t_uchar * 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;
}

t_uchar *
arch_fs_archive_cached_checksum_path (const struct arch_archive * arch,
                                      const t_uchar * archive_path,
                                      const t_uchar * revision)
{
  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, "checksum.cacherev");

  lim_free (0, revision_path);

  return checksum_path;
}


t_uchar *
arch_fs_archive_continuation_path (const struct arch_archive * arch,
                                   const t_uchar * archive_path,
                                   const t_uchar * 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 (const struct arch_archive * arch,
					     const t_uchar * archive_path,
                                             const t_uchar * version,
                                             const 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 (const struct arch_archive * arch,
					   const t_uchar * archive_path,
                                           const t_uchar * version,
                                           const t_uchar * prev_level,
                                           const t_uchar * arch_user_id,
                                           const 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 (const struct arch_archive * arch,
						    const t_uchar * archive_path,
                                                    const t_uchar * version,
                                                    const t_uchar * prev_level,
                                                    const t_uchar * arch_user_id,
                                                    const 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 (const struct arch_archive * arch,
					   const t_uchar * archive_path,
                                           const t_uchar * version,
                                           const 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;
}






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