/* archive-fs.c: 
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 * 
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "hackerlab/bugs/panic.h"
#include "hackerlab/os/errno.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/vu/safe.h"
#include "file-utils/libfsutils/dir-listing.h"
#include "file-utils/libfsutils/file-contents.h"
#include "file-utils/libfsutils/copy-file.h"
#include "file-utils/libfsutils/read-line.h"
#include "file-utils/libfsutils/rmrf.h"
#include "file-utils/libfsutils/tmp-files.h"
#include "file-utils/libarch/namespace.h"
#include "file-utils/libarch/archives.h"
#include "file-utils/libarch/archive-fs.h"


struct arch_fs_archive;

/* __STDC__ prototypes for static functions */
static void fs_close (struct arch_archive * a);
static t_uchar * fs_archive_version (struct arch_archive * a);
static rel_table fs_categories (struct arch_archive * a);
static rel_table fs_branches (struct arch_archive * a, t_uchar * category);
static rel_table fs_versions (struct arch_archive * a, t_uchar * package);
static rel_table fs_revisions (struct arch_archive * a, t_uchar * version);
static t_uchar * fs_archive_log (struct arch_archive * a, t_uchar * revision);
static void fs_revision_type  (enum arch_revision_type * type, int * archive_cached,
                               struct arch_archive * a, t_uchar * revision);
static void fs_get_patch (int out_fd, struct arch_archive * a, t_uchar * revision);
static void fs_get_cached (int out_fd, struct arch_archive * a, t_uchar * revision);
static void fs_get_import (int out_fd, struct arch_archive * a, t_uchar * revision);
static t_uchar * fs_get_continuation (struct arch_archive * a, t_uchar * revision);
static int fs_make_category (t_uchar ** errstr, struct arch_archive * a, t_uchar * category);
static int fs_make_branch (t_uchar ** errstr, struct arch_archive * a, t_uchar * branch);
static int fs_make_version (t_uchar ** errstr, struct arch_archive * a, t_uchar * version);
static int fs_lock_revision (t_uchar ** errstr, struct arch_archive * a,
                             t_uchar * version,
                             t_uchar * prev_level,
                             t_uchar * uid,
                             t_uchar * txn_id);
static int fs_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);
static enum arch_revision_lock_state fs_lock_state (t_uchar ** prev_level_ret,
                                                    t_uchar ** uid_ret,
                                                    t_uchar ** txn_id_ret, 
                                                    struct arch_archive * a,
                                                    t_uchar * version);
static int fs_break_revision_lock (t_uchar ** errstr, struct arch_archive * a,
                                   t_uchar * version,
                                   t_uchar * prev_level,
                                   t_uchar * uid,
                                   t_uchar * txn_id);
static void safely_remove_stale_broken_lock_dir (t_uchar * broken_dir, t_uchar * prev_level);
static void safely_remove_spent_lock (t_uchar * spent_lock);
static int fs_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);
static int fs_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);
static int fs_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,
                             int in_fd);
static int fs_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,
                          int in_fd);
static int fs_put_cached (t_uchar ** errstr, struct arch_archive * a, t_uchar * revision, int in_fd);
static int fs_delete_cached (t_uchar ** errstr, struct arch_archive * a, t_uchar * revision);



static struct arch_archive_vtable fs_vtable = 
{
  "local",

  fs_close,

  fs_archive_version,

  fs_categories,
  fs_branches,
  fs_versions,
  fs_revisions,

  fs_archive_log,
  fs_revision_type,
  fs_get_patch,
  fs_get_cached,
  fs_get_import,
  fs_get_continuation,

  fs_make_category,
  fs_make_branch,
  fs_make_version,

  fs_lock_revision,
  fs_finish_revision,
  fs_break_revision_lock,
  fs_lock_state,

  fs_put_log,
  fs_put_continuation,
  fs_put_changeset,
  fs_put_import,

  fs_put_cached,
  fs_delete_cached,
};

struct arch_fs_archive
{
  struct arch_archive arch;

  t_uchar * root_path;
};

 


void
arch_fs_archive_connect (struct arch_archive ** arch)
{
  int here_fd;
  struct arch_fs_archive * answer = 0;

  answer = lim_realloc (0, (t_uchar *)*arch, sizeof (*answer));
  mem_set0 ((t_uchar *)answer + sizeof (**arch), sizeof (*answer) - sizeof (**arch));

  *arch = (struct arch_archive *)answer;

  answer->arch.vtable = &fs_vtable;

  here_fd = safe_open (".", O_RDONLY, 0);
  safe_chdir ((*arch)->location);
  answer->root_path = safe_current_working_directory ();
  safe_fchdir (here_fd);
  safe_close (here_fd);
}


static void
fs_close (struct arch_archive * a)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;

  lim_free (0, arch->root_path);
}


static t_uchar *
fs_archive_version (struct arch_archive * a)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * path = 0;
  t_uchar * answer = 0;

  path = arch_fs_archive_archive_version_path (arch->root_path);
  answer = read_line_from_file (path);

  lim_free (0, path);
  return answer;
}


static rel_table
fs_categories (struct arch_archive * a)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  rel_table files = 0;
  rel_table answer = 0;

  files = directory_files (arch->root_path);
  answer = arch_pick_categories_by_field (files, 0);

  rel_free_table (files);
  return answer;
}


static rel_table
fs_branches (struct arch_archive * a, t_uchar * category)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * category_path = 0;
  rel_table files = 0;
  rel_table answer = 0;

  category_path = arch_fs_archive_category_path (arch->root_path, category);

  files = directory_files (category_path);
  answer = arch_pick_branches_by_field (files, 0);

  lim_free (0, category_path);
  rel_free_table (files);
  return answer;
}

static rel_table
fs_versions (struct arch_archive * a, t_uchar * package)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * branch_path = 0;
  rel_table files = 0;
  rel_table answer = 0;


  branch_path = arch_fs_archive_branch_path (arch->root_path, package);

  files = directory_files (branch_path);
  answer = arch_pick_versions_by_field (files, 0);

  lim_free (0, branch_path);
  rel_free_table (files);
  return answer;
}


static rel_table
fs_revisions (struct arch_archive * a, t_uchar * version)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * version_path = 0;
  rel_table files = 0;
  rel_table answer = 0;


  version_path = arch_fs_archive_version_path (arch->root_path, version);

  files = directory_files (version_path);
  answer = arch_pick_patch_levels_by_field (files, 0);

  lim_free (0, version_path);
  rel_free_table (files);
  return answer;
}

static t_uchar *
fs_archive_log (struct arch_archive * a, t_uchar * revision)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * revision_log_path = 0;
  int in_fd;
  t_uchar * answer;
  size_t len;


  revision_log_path = arch_fs_archive_revision_log_path (arch->root_path, revision);
  in_fd = safe_open (revision_log_path, O_RDONLY, 0);

  answer = 0;
  len = 0;
  safe_file_to_string (&answer, &len, in_fd);
  answer = lim_realloc (0, answer, len + 1);
  answer[len] = 0;

  safe_close (in_fd);
  lim_free (0, revision_log_path);
  return answer;
}


static void
fs_revision_type  (enum arch_revision_type * type, int * archive_cached,
                   struct arch_archive * a, t_uchar * revision)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * continuation_path = 0;
  t_uchar * changeset_path = 0;
  t_uchar * import_path = 0;
  t_uchar * cached_path = 0;

  continuation_path = arch_fs_archive_continuation_path (arch->root_path, revision);
  changeset_path = arch_fs_archive_changeset_path (arch->root_path, revision);
  import_path = arch_fs_archive_import_path (arch->root_path, revision);
  cached_path = arch_fs_archive_cached_path (arch->root_path, revision);

  if (!safe_access (continuation_path, F_OK))
    {
      if (type)
        *type = arch_continuation_revision;
    }
  else if (!safe_access (changeset_path, F_OK))
    {
      if (type)
        *type = arch_simple_revision;
    }
  else if (!safe_access (import_path, F_OK))
    {
      if (type)
        *type = arch_import_revision;
    }
  else
    {
      safe_printfmt (2, "corrupt archive\n  name: %s\n  location: %s\n  revision: %s\n", arch->arch.name, arch->arch.location, revision);
      exit (2);
    }

  if (archive_cached)
    *archive_cached = !safe_access (cached_path, F_OK);

  lim_free (0, continuation_path);
  lim_free (0, changeset_path);
  lim_free (0, import_path);
  lim_free (0, cached_path);
}

static void
fs_get_patch (int out_fd, struct arch_archive * a, t_uchar * revision)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * changeset_path = 0;
  int in_fd;

  changeset_path = arch_fs_archive_changeset_path (arch->root_path, revision);

  in_fd = safe_open (changeset_path, O_RDONLY, 0);
  copy_fd (in_fd, out_fd);

  safe_close (in_fd);
  lim_free (0, changeset_path);
}

static void
fs_get_cached (int out_fd, struct arch_archive * a, t_uchar * revision)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * cached_path = 0;
  int in_fd;

  cached_path = arch_fs_archive_cached_path (arch->root_path, revision);

  in_fd = safe_open (cached_path, O_RDONLY, 0);
  copy_fd (in_fd, out_fd);

  safe_close (in_fd);
  lim_free (0, cached_path);
}


static void
fs_get_import (int out_fd, struct arch_archive * a, t_uchar * revision)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * changeset_path = 0;
  int in_fd;

  changeset_path = arch_fs_archive_import_path (arch->root_path, revision);

  in_fd = safe_open (changeset_path, O_RDONLY, 0);
  copy_fd (in_fd, out_fd);

  safe_close (in_fd);
  lim_free (0, changeset_path);
}


static t_uchar *
fs_get_continuation (struct arch_archive * a, t_uchar * revision)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * continuation_path = 0;
  t_uchar * answer = 0;

  continuation_path  = arch_fs_archive_continuation_path (arch->root_path, revision);

  answer = file_contents (continuation_path);

  lim_free (0, continuation_path);
  return answer;
}


static int
fs_make_category (t_uchar ** errstr, struct arch_archive * a, t_uchar * category)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * cat_path;
  int result;

  result = -1;
  if (errstr)
    *errstr = "internal error in archive-fs.c(fs_make_category)";
  
  invariant (arch_valid_package_name (category, arch_no_archive, arch_req_category, 0));

  cat_path = file_name_in_vicinity (0, arch->root_path, category);

  if (!safe_access (cat_path, F_OK))
    {
      if (errstr)
        *errstr = "category already exists";
      result = -1;
    }
  else
    {
      t_uchar * cat_path_dir = 0;
      t_uchar * cat_tmp_path = 0;

      /* Why mkdir/rename?
       * 
       * Because the archive might exist on NFS on which mkdir can 
       * succeed fore more than one client.
       * 
       * Rename to a common target can succeed for more than one NFS
       * client as well, but not if each client uses a unique
       * source of the rename.
       */

      cat_path_dir = file_name_directory_file (0, cat_path);
      cat_tmp_path = archive_tmp_file_name (cat_path_dir, ",,new-category");

      safe_mkdir (cat_tmp_path, 0777);
      safe_rename (cat_tmp_path, cat_path);

      result = 0;
      if (errstr)
        *errstr = 0;

      lim_free (0, cat_tmp_path);
      lim_free (0, cat_path_dir);
    }

  lim_free (0, cat_path);
  return result;
}


static int
fs_make_branch (t_uchar ** errstr, struct arch_archive * a, t_uchar * branch)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * branch_path;
  int result;

  result = -1;
  if (errstr)
    *errstr = "internal error in archive-fs.c(fs_make_branch)";
  
  invariant (arch_valid_package_name (branch, arch_no_archive, arch_req_package, 0));

  branch_path = arch_fs_archive_branch_path (arch->root_path, branch);

  if (!safe_access (branch_path, F_OK))
    {
      if (errstr)
        *errstr = "branch already exists";
      result = -1;
    }
  else
    {
      t_uchar * branch_path_dir = 0;
      t_uchar * branch_tmp_path = 0;

      branch_path_dir = file_name_directory_file (0, branch_path);
      branch_tmp_path = archive_tmp_file_name (branch_path_dir, ",,new-branch");

      safe_mkdir (branch_tmp_path, 0777);
      safe_rename (branch_tmp_path, branch_path);

      result = 0;
      if (errstr)
        *errstr = 0;

      lim_free (0, branch_tmp_path);
      lim_free (0, branch_path_dir);
    }

  lim_free (0, branch_path);
  return result;
}


static int
fs_make_version (t_uchar ** errstr, struct arch_archive * a, t_uchar * version)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * version_path = 0;
  int result;

  result = -1;
  if (errstr)
    *errstr = "internal error in archive-fs.c(fs_make_version)";
  
  invariant (arch_valid_package_name (version, arch_no_archive, arch_req_version, 0));

  version_path = arch_fs_archive_version_path (arch->root_path, version);

  if (!safe_access (version_path, F_OK))
    {
      if (errstr)
        *errstr = "version already exists";
      result = -1;
    }
  else
    {
      t_uchar * version_path_dir = 0;
      t_uchar * version_tmp_path = 0;
      t_uchar * lock_dir_path = 0;
      t_uchar * contents_dir_path = 0;

      version_path_dir = file_name_directory_file (0, version_path);
      version_tmp_path = archive_tmp_file_name (version_path_dir, ",,new-branch");
      lock_dir_path = file_name_in_vicinity (0, version_tmp_path, "++revision-lock");
      contents_dir_path = file_name_in_vicinity (0, lock_dir_path, "+contents");

      safe_mkdir (version_tmp_path, 0777);
      safe_mkdir (lock_dir_path, 0777);
      safe_mkdir (contents_dir_path, 0777);
      safe_rename (version_tmp_path, version_path);

      result = 0;
      if (errstr)
        *errstr = 0;

      lim_free (0, version_tmp_path);
      lim_free (0, version_path_dir);
      lim_free (0, lock_dir_path);
      lim_free (0, contents_dir_path);
    }

  lim_free (0, version_path);
  return result;
}



/****************************************************************
 * Revision Locks
 * 
 * A version contains a a sequence of revisions.  Each new revision
 * must be created in-sequence, in an atomic, isolated, and durable
 * event.
 * 
 * In file system archives, a version is a directory, and each revision
 * a subdirectory with a patch level for a name (base-0, patch-1, ...)
 * 
 * The ordinary rename system call almost gives us a mechanism for this:
 * a client could mkdir ,,wants-to-be-patch-1-txnid, fill that with
 * the revisions data, then rename it to patch-1.   We could trust clients
 * not to create patch-N unless patch-(N-1) already exists, and the rename
 * will fail if a concurrent client succeeds at committing patch-N first.
 * 
 * Alas, theres a catch here.   The "revision after" base-0 or any
 * patch-N does not have a fixed name.  It might be patch-(N+1), or it 
 * might be "version-0".   It would be disasterous if two concurrent clients
 * simultaneously created both patch-(N+1) and version-0 as successors
 * to patch-N (or base-0).   rename doesn't directly solve this problem.
 * 
 * (Additionally, we want to support "persistent locks" -- to allow a 
 * user to claim the lock for a new revision prior to actually committing it, 
 * in order to impose an advisory barrier to anybody else trying to commit
 * that revision.)
 * 
 * So, here's how it works instead:
 * 
 * Every subtree for a given version contains, at all times, exactly
 * one (non-nested) "revision-lock directory".  That directory always
 * has a subdirectory called "+contents".  In the normal course of
 * things, the "+contents" directory will eventually be renamed to
 * become the new revision directory.
 * 
 * In the general, the name of the revision-lock directory indicates
 * the state of the lock.   When a write transaction holds the lock,
 * it fills "+contents" with the data for the new revision (including
 * a new, nested revision lock directory, then renames "+contents"
 * to patch-N (or version-0 or base-0 or versionfix-N), and cleans up.
 * 
 *  
 *  Possible Revision Lock States:
 *  
 *  [A] No revisions exist yet, lock is unlocked in the usual way:
 * 
 *  version/
 *    version/+revision-lock                              == $lock
 *      version/+revision-lock/+contents
 *  
 *  
 *  [B] Patch level LVL is the most recent, lock is unlocked in the usual way:
 *  
 *  version/
 *    version/LVL
 *      version/LVL/+revision-lock                        == $lock
 *        version/LVL/+revision-lock/+contents		  (empty)
 *  
 * 
 * In the rest, if no revisions yet exist, then LVL is "absolute-0"
 *  
 *  [C] Lock was recently broken, or a lock break operation was interrupted
 *      after breaking the lock, but before cleaning up:
 *  
 *  version/
 *    version/+revision-lock-broken--LVL                  == $broken_dir
 *      version/+revision-lock-broken--LVL/,,remade-lock--LVL == $broken  (the lock itself)
 *        version/+revision-lock-broken--LVL/,,remade-lock--LVL/+contents
 *      version/+revision-lock-broken--LVL/...            junk from failed transaction
 *  
 *  
 *  [D] Persistent lock is currently held by user UID
 *  
 *  version/
 *    version/+revision-lock-held--LVL--UID                       == $locked
 *      version/+revision-lock-held--LVL--UID/+contents		  (empty)
 *  
 *  
 *  [E] Lock is currently held by user UID for client process CLIENT
 *  
 *  version/
 *    version/+revision-lock-held--LVL--UID.CLIENT                 == $locked
 *      version/+revision-lock-held--LVL--UID/+contents
 *        version/+revision-lock-held--LVL--UID/+contents/...      (new revision data)
 *  
 *  
 *  [-] Junk that can be left around when cleanups dont finish (safe to delete 
 *      things marked with "!"):  
 *  
 *   version/
 * !   version/+revision-lock-held--LVL--UID*
 * !    (no +contents subdir, not necessarilly empty, otherwise)
 *  
 *   version/
 * !   version/+revision-lock-broken--LVL
 * !     (no ,,remade-lock--LVL subdir, not necessarilly empty, otherwise)
 *  
 *   version/
 *     version/revision--(LVL + 1)                          i.e., completed next revision
 * !     version/revision--(LVL + 1)/++version-lock/,*      failed attempt to break lock
 * 
 */ 

static int
fs_lock_revision (t_uchar ** errstr, struct arch_archive * a,
                  t_uchar * version,
                  t_uchar * prev_level,
                  t_uchar * uid,
                  t_uchar * txn_id)
{
  int errn;
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * unlocked_path = 0;
  t_uchar * persistent_locked_path = 0;
  t_uchar * final_locked_path = 0;
  t_uchar * final_locked_contents_path = 0;
  t_uchar * broken_path = 0;
  int rename_occurred = 0;
  int result;

  result = -1;
  if (errstr)
    *errstr = "internal error in archive-fs.c(fs_lock_revision)";
  

  /* txn_id == 0
   * 
   *  [A] -> [D]
   *  [B] -> [D]
   *  [C] -> [D]
   *  [D] -> [D]
   *  [E] -> error	(user must break lock or txn complete)
   * 
   * txn_id != 0
   * 
   *  [A] -> [E]
   *  [B] -> [E]
   *  [C] -> [E]
   *  [D] -> [E]
   *  [E] -> [E]  (if the lock is currently held with our txn_id)
   *  [E] -> error (otherwise)
   *
   */

  unlocked_path = arch_fs_archive_revision_lock_unlocked_path (arch->root_path, version, prev_level);
  persistent_locked_path = arch_fs_archive_revision_lock_locked_path (arch->root_path, version, prev_level, uid, 0);
  final_locked_path = arch_fs_archive_revision_lock_locked_path (arch->root_path, version, prev_level, uid, txn_id);
  final_locked_contents_path = arch_fs_archive_revision_lock_locked_contents_path (arch->root_path, version, prev_level, uid, txn_id);
  broken_path = arch_fs_archive_revision_lock_broken_path (arch->root_path, version, prev_level);

  /* final_locked_path is [D] or [E] as appropriate to the txn_id argument.
   * 
   * unlocked_path is [A] or [B] as appropriate to the prev_level argument.
   */

  /* [A/B] -> [D/E]
   * 
   */
  if (!vu_rename (&errn, unlocked_path, final_locked_path))
    {
      rename_occurred = 1;
    }
  /* [C] -> [D/E] */
  else if (!vu_rename (&errn, broken_path, final_locked_path))
    {
      t_uchar * broken_dir = 0;

      rename_occurred = 1;

      /* clean up the old broken lock dir */
      broken_dir = file_name_directory_file (0, broken_dir);
      safely_remove_stale_broken_lock_dir (broken_dir, prev_level);

      lim_free (0, broken_dir);
    }
  /* [D] -> [E] (if appropriate) */
  else if (str_cmp (persistent_locked_path, final_locked_path) && vu_rename (&errn, persistent_locked_path, final_locked_path))
    {
      rename_occurred = 1;
    }
  /* [D] -> [D] or [E] -> [E] (as appropriate) */
  else if (!safe_access (final_locked_path, F_OK))
    {
      rename_occurred = 1;
    }

  /* A correctly named "++version-lock-held..." directory exists -- but is it actually
   * the lock?
   */
  if (rename_occurred)
    {
      struct stat statb;

      if (!vu_stat (&errn, final_locked_contents_path, &statb) && S_ISDIR (statb.st_mode))
        {
          result = 0;
          if (errstr)
            *errstr = 0;
        }
      else
        {
          result = -1;
          if (errstr)
            {
              *errstr = "lock held or revision already committed";
            }
        }
    }

  lim_free (0, unlocked_path);
  lim_free (0, persistent_locked_path);
  lim_free (0, final_locked_path);
  lim_free (0, final_locked_contents_path);
  lim_free (0, broken_path);

  return result;
}


static int
fs_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)
{
  int errn;
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * locked_path = 0;
  t_uchar * locked_contents_path = 0;
  t_uchar * new_lock_path = 0;
  t_uchar * new_contents_path = 0;
  t_uchar * revision = 0;
  t_uchar * new_rev_path = 0;
  struct stat statb;
  int result;

  result = -1;
  if (errstr)
    *errstr = "internal error in archive-fs.c(fs_lock_revision)";
  

  /* 
   *  [E] -> [A]	if lock held with our txn_id
   *  [E] -> error	otherwise
   *  [B,C,D] -> error
   */

  locked_path = arch_fs_archive_revision_lock_locked_path (arch->root_path, version, prev_level, uid, txn_id);
  locked_contents_path = arch_fs_archive_revision_lock_locked_contents_path (arch->root_path, version, prev_level, uid, txn_id);

  new_lock_path = file_name_in_vicinity (0, locked_contents_path, "++revision-lock");
  new_contents_path = file_name_in_vicinity (0, new_lock_path, "+contents");

  revision = str_alloc_cat_many (0, version, "--", new_level, str_end);
  new_rev_path = arch_fs_archive_revision_path (arch->root_path, revision);

  if (safe_access (locked_path, F_OK))
    {
      result = -1;
      *errstr = "lock not held";
    }
  else
    {
      vu_mkdir (&errn, new_lock_path, 0777);
      vu_mkdir (&errn, new_contents_path, 0777);

      errn = 0;
      if (!vu_lstat (&errn, new_contents_path, &statb) && S_ISDIR (statb.st_mode) && !vu_rename (&errn, locked_contents_path, new_rev_path))
        {
          result = 0;
          if (errstr)
            *errstr = 0;
          safely_remove_spent_lock (locked_path);
        }
      else
        {
          if (!errn)
            {
              safe_printfmt (2, "corrupt archive (+contents is not a directory)\n    archive: %s\n    path: %s\n",
                             arch->arch.name, new_contents_path);
              exit (2);
            }
          else
            {
              safe_printfmt (2, "i/o error modifying archive (%s)\n    archive: %s\n    path: %s\n",
                             errno_to_string (errn), arch->arch.name, new_contents_path);
              exit (2);
            }
        }
    }
    

  lim_free (0, locked_path);
  lim_free (0, locked_contents_path);
  lim_free (0, new_lock_path);
  lim_free (0, new_contents_path);
  lim_free (0, revision);
  lim_free (0, new_rev_path);

  return result;
}


static enum arch_revision_lock_state
fs_lock_state (t_uchar ** prev_level_ret,
               t_uchar ** uid_ret,
               t_uchar ** txn_id_ret, 
               struct arch_archive * a,
               t_uchar * version)
{
  int errn;
  struct stat statb;
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;

  rel_table revisions = 0;
  t_uchar * prev_level = 0;

  t_uchar * unlocked_path = 0;
  t_uchar * unlocked_contents_path = 0;
  t_uchar * broken_path = 0;
  t_uchar * broken_contents_path = 0;

  revisions = arch_archive_revisions (&arch->arch, version, 0);
  if (!revisions)
    prev_level = 0;
  else
    prev_level = str_save (0, revisions[rel_n_records (revisions) - 1][0]);

  if (prev_level_ret)
    *prev_level_ret = prev_level ? str_save (0, prev_level) : 0;

  unlocked_path = arch_fs_archive_revision_lock_unlocked_path (arch->root_path, version, prev_level);
  unlocked_contents_path = file_name_in_vicinity (0, unlocked_path, "+contents");
  broken_path = arch_fs_archive_revision_lock_broken_path (arch->root_path, version, prev_level);
  broken_contents_path = file_name_in_vicinity (0, broken_path, "+contents");
  
  if (uid_ret)
    *uid_ret = 0;
  if (txn_id_ret)
    *txn_id_ret = 0;

  if (!vu_stat (&errn, unlocked_contents_path, &statb) || !vu_stat (&errn, broken_contents_path, &statb))
    {
      if (S_ISDIR (statb.st_mode))
        return arch_revision_unlocked;
      else
        return arch_revision_illegal_lock_state;
    }
  else
    {
      t_uchar * version_path = 0;
      rel_table files = 0;
      t_uchar * persistent_locked_path_stem = 0;
      int x;
      
      version_path = arch_fs_archive_version_path (arch->root_path, version);
      files = directory_files (version_path);

      {
        t_uchar * t = 0;

        t = arch_fs_archive_revision_lock_locked_path (arch->root_path, version, prev_level, 0, 0);
        persistent_locked_path_stem = file_name_tail (0, t);

        lim_free (0, t);
      }

      for (x = 0; x < rel_n_records (files); ++x)
        {
          if (!str_cmp_prefix (persistent_locked_path_stem, files[x][0]))
            {
              t_uchar * contents_rel = 0;
              t_uchar * contents_path = 0;

              contents_rel = file_name_in_vicinity (0, files[x][0], "+contents");
              contents_path = file_name_in_vicinity (0, version_path, contents_rel);

              if (!vu_stat (&errn, contents_path, &statb))
                {
                  t_uchar * uid_start;
                  t_uchar * uid_end;

                  if (!S_ISDIR (statb.st_mode))
                    return arch_revision_illegal_lock_state;

                  uid_start = files[x][0] + str_length (persistent_locked_path_stem);
                  uid_end = str_chr_rindex (uid_start, '-');
                  invariant (!!uid_end);
                  --uid_end;

                  if (uid_end < uid_start)
                    {
                      if (uid_ret)
                        *uid_ret = str_save (0, uid_start);
                      if (txn_id_ret)
                        *txn_id_ret = 0;
                      return arch_revision_user_locked;
                    }
                  else
                    {
                      t_uchar * txn_id;

                      txn_id = uid_end + 2;
                      if (uid_ret)
                        *uid_ret = str_save_n (0, uid_start, uid_end - uid_start);
                      if (txn_id_ret)
                        *txn_id_ret = str_save (0, txn_id);
                      return arch_revision_txn_locked;
                    }
                }

              lim_free (0, contents_rel);
              lim_free (0, contents_path);
            }
        }

      lim_free (0, version_path);
      rel_free_table (files);
      lim_free (0, persistent_locked_path_stem);
    }


  rel_free_table (revisions);
  lim_free (0, prev_level);

  lim_free (0, unlocked_path);
  lim_free (0, unlocked_contents_path);
  lim_free (0, broken_path);
  lim_free (0, broken_contents_path);

  return arch_revision_unknown_lock_state;
}


static int
fs_break_revision_lock (t_uchar ** errstr, struct arch_archive * a,
                        t_uchar * version,
                        t_uchar * prev_level,
                        t_uchar * uid,
                        t_uchar * txn_id)
{
  int errn;
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * locked_path = 0;
  t_uchar * locked_contents_path = 0;
  t_uchar * unlocked_path = 0;
  struct stat statb;
  int result;

  result = -1;
  if (errstr)
    *errstr = "internal error in archive-fs.c(fs_lock_revision)";
  

  /* 
   *  [D/E] -> [C] -> [A]	if lock held with our uid/txn_id
   *  [D/E] -> error		otherwise
   *  [A,B,C] -> error
   */

  locked_path = arch_fs_archive_revision_lock_locked_path (arch->root_path, version, prev_level, uid, txn_id);
  locked_contents_path = arch_fs_archive_revision_lock_locked_contents_path (arch->root_path, version, prev_level, uid, txn_id);
  unlocked_path = arch_fs_archive_revision_lock_unlocked_path (arch->root_path, version, prev_level);

  errn = 0;
  if (vu_stat (&errn, locked_contents_path, &statb) || !S_ISDIR (statb.st_mode))
    {
      result = -1;
      if (errstr)
        *errstr = (errn ? "lock not held" : "lock corrupted in archive");
    }
  else
    {
      t_uchar * remade_basename = 0;
      t_uchar * new_lock_path = 0;
      t_uchar * new_lock_contents_path = 0;
      t_uchar * version_path = 0;
      t_uchar * broken_lock_path = 0;
      t_uchar * broken_lock_dir = 0;
      t_uchar * junk_path = 0;

      remade_basename = str_alloc_cat (0, ",,remade-lock--", (prev_level ? prev_level : (t_uchar *)"absolute-0"));
      new_lock_path = file_name_in_vicinity (0, locked_contents_path, remade_basename);
      new_lock_contents_path = file_name_in_vicinity (0, new_lock_path, "+contents");

      version_path = arch_fs_archive_version_path (arch->root_path, version);
      broken_lock_path = arch_fs_archive_revision_lock_broken_path (arch->root_path, version, prev_level);
      broken_lock_dir = file_name_directory_file (0, broken_lock_path);
      junk_path = tmp_file_name (version_path, ",,junk");

      vu_mkdir (&errn, new_lock_path, 0777);
      if (vu_mkdir (&errn, new_lock_contents_path, 0777) && (errn != EEXIST))
        {
          result = -1;
          if (errstr)
            *errstr = "lock not held";
        }
      else
        {

          safely_remove_stale_broken_lock_dir (broken_lock_dir, prev_level);

          if (vu_rename (&errn, locked_contents_path, broken_lock_dir))
            {
              result = -1;
              if (errstr)
                *errstr = "lock not held";
            }
          else
            {
              t_uchar * latest_rev = 0;

              /* Did the lock actually become broken (perhaps by our rename
               * above, or by some other process?   The only other possibility
               * (and the only reliable way to check) is to see if, instead,
               * the commit completed.
               */

              {
                rel_table levels = 0;

                levels = arch_archive_revisions (&arch->arch, version, 0);
                if (levels)
                  latest_rev = str_save (0, levels[rel_n_records(levels) - 1][0]);

                rel_free_table (levels);
              }

              if (str_cmp (prev_level, latest_rev))
                {
                  result = -1;
                  if (errstr)
                    *errstr = "lock not held";
                }
              else
                {
                  vu_rename (&errn, broken_lock_path, unlocked_path);
                  /* We really don't care whether that rename succeeded or not.
                   * It's just for "neatness"
                   */
                  result = 0;
                  if (errstr)
                    *errstr = 0;
                }

              /* Note that safely_remove_stale_broken_lock_dir doesn't actually
               * remove the broken lock dir in the case that it isn't stale.
               */
              safely_remove_stale_broken_lock_dir (broken_lock_dir, prev_level);
              safely_remove_spent_lock (locked_path);

              lim_free (0, latest_rev);
            }
        }
      

      lim_free (0, new_lock_path);
      lim_free (0, new_lock_contents_path);
      lim_free (0, version_path);
      lim_free (0, broken_lock_path);
    }

  lim_free (0, locked_path);
  lim_free (0, locked_contents_path);
  lim_free (0, unlocked_path);

  return result;
}


static void
safely_remove_stale_broken_lock_dir (t_uchar * broken_dir, t_uchar * prev_level)
{
  t_uchar * broken_dir_tail = 0;
  rel_table nested = 0;
  int x;
  t_uchar * precious_basename = 0;
  int ign;

  broken_dir_tail = file_name_tail (0, broken_dir);
  invariant (!str_cmp_prefix ("++revision-lock-broken--", broken_dir_tail));


  nested = maybe_directory_files (broken_dir);
  precious_basename = str_alloc_cat (0, ",,remade-lock--", (prev_level ? prev_level : (t_uchar *)"absolute-0"));

  for (x = 0; x < rel_n_records (nested); ++x)
    {
      t_uchar * basename;

      basename = nested[x][0];

      if (str_cmp (".", basename) && str_cmp ("..", basename) && str_cmp (precious_basename, basename))
        {
          t_uchar * path = 0;

          path = file_name_in_vicinity (0, broken_dir, basename);
          rmrf_file (path);

          lim_free (0, path);
        }
    }

  vu_rmdir (&ign, broken_dir);

  lim_free (0, broken_dir_tail);
  rel_free_table (nested);
  lim_free (0, precious_basename);
}


static void
safely_remove_spent_lock (t_uchar * spent_lock)
{
  t_uchar * spent_lock_tail = 0;
  rel_table nested = 0;
  t_uchar * tmp_path = 0;
  int x;
  int ign;

  spent_lock_tail = file_name_tail (0, spent_lock);

  invariant (!str_cmp_prefix ("++revision-lock-held--", spent_lock_tail));
  
  nested = maybe_directory_files (spent_lock);
  tmp_path = tmp_file_name (spent_lock, ",,junk");

  rmrf_file (tmp_path);

  for (x = 0; x < rel_n_records (nested); ++x)
    {
      t_uchar * basename;

      basename = nested[x][0];

      if (str_cmp (".", basename) && str_cmp ("..", basename) && str_cmp ("+contents", basename))
        {
          t_uchar * path = 0;

          path = file_name_in_vicinity (0, spent_lock, basename);
          vu_rename (&ign, path, tmp_path);
          rmrf_file (tmp_path);

          lim_free (0, path);
        }
    }

  vu_rmdir (&ign, spent_lock);

  lim_free (0, spent_lock_tail);
  rel_free_table (nested);
  lim_free (0, tmp_path);
}



/****************************************************************
 * Write Txn Steps
 */

static int
fs_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)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * locked_contents_path = 0;
  t_uchar * log_path = 0;
  int out_fd;

  invariant (!!txn_id);

  locked_contents_path = arch_fs_archive_revision_lock_locked_contents_path (arch->root_path, version, prev_level, uid, txn_id);

  log_path = file_name_in_vicinity (0, locked_contents_path, "log");

  out_fd = safe_open (log_path, O_WRONLY | O_CREAT | O_EXCL, 0444);

  safe_printfmt (out_fd, "%s", log_text);

  safe_close (out_fd);

  if (errstr)
    *errstr = 0;

  lim_free (0, locked_contents_path);
  lim_free (0, log_path);

  return 0;
}


static int
fs_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)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * locked_contents_path = 0;
  t_uchar * continuation_path = 0;
  int out_fd;

  invariant (!!txn_id);

  locked_contents_path = arch_fs_archive_revision_lock_locked_contents_path (arch->root_path, version, prev_level, uid, txn_id);

  continuation_path = file_name_in_vicinity (0, locked_contents_path, "CONTINUATION");

  out_fd = safe_open (continuation_path, O_WRONLY | O_CREAT | O_EXCL, 0444);

  safe_printfmt (out_fd, "%s", continuation);

  safe_close (out_fd);

  if (errstr)
    *errstr = 0;

  lim_free (0, locked_contents_path);
  lim_free (0, continuation_path);

  return 0;
}


static int
fs_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,
                  int in_fd)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * locked_contents_path = 0;
  t_uchar * patches = 0;
  t_uchar * changeset_path = 0;
  int out_fd;

  invariant (!!txn_id);

  locked_contents_path = arch_fs_archive_revision_lock_locked_contents_path (arch->root_path, version, prev_level, uid, txn_id);

  patches = str_alloc_cat_many (0, version, "--", level, ".patches.tar.gz", str_end);

  changeset_path = file_name_in_vicinity (0, locked_contents_path, patches);

  out_fd = safe_open (changeset_path, O_WRONLY | O_CREAT | O_EXCL, 0444);

  copy_fd (in_fd, out_fd);

  safe_close (out_fd);

  if (errstr)
    *errstr = 0;

  lim_free (0, locked_contents_path);
  lim_free (0, patches);
  lim_free (0, changeset_path);

  return 0;
}

static int
fs_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,
               int in_fd)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * locked_contents_path = 0;
  t_uchar * import = 0;
  t_uchar * import_path = 0;
  int out_fd;

  invariant (!!txn_id);

  locked_contents_path = arch_fs_archive_revision_lock_locked_contents_path (arch->root_path, version, prev_level, uid, txn_id);

  import = str_alloc_cat_many (0, version, "--", level, ".src.tar.gz", str_end);

  import_path = file_name_in_vicinity (0, locked_contents_path, import);

  out_fd = safe_open (import_path, O_WRONLY | O_CREAT | O_EXCL, 0444);

  copy_fd (in_fd, out_fd);

  safe_close (out_fd);

  if (errstr)
    *errstr = 0;

  lim_free (0, locked_contents_path);
  lim_free (0, import);
  lim_free (0, import_path);

  return 0;
}


static int
fs_put_cached (t_uchar ** errstr, struct arch_archive * a, t_uchar * revision, int in_fd)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * cached_path = 0;
  t_uchar * cached_dir = 0;
  t_uchar * cached_tmp = 0;
  int out_fd;

  cached_path = arch_fs_archive_cached_path (arch->root_path, revision);
  cached_dir = file_name_directory_file (0, cached_path);
  cached_tmp = tmp_file_name (cached_dir, ",,cached-revision");

  rmrf_file (cached_tmp);
  out_fd = safe_open (cached_tmp, O_WRONLY | O_CREAT | O_EXCL, 0444);
  copy_fd (in_fd, out_fd);
  safe_close (out_fd);
  safe_rename (cached_tmp, cached_path);

  lim_free (0, cached_path);
  lim_free (0, cached_dir);
  lim_free (0, cached_tmp);

  return 0;
}


static int
fs_delete_cached (t_uchar ** errstr, struct arch_archive * a, t_uchar * revision)
{
  struct arch_fs_archive * arch = (struct arch_fs_archive *)a;
  t_uchar * cached_path = 0;

  cached_path = arch_fs_archive_cached_path (arch->root_path, revision);

  if (!safe_access (cached_path, F_OK))
    safe_unlink (cached_path);

  lim_free (0, cached_path);

  return 0;
}







/* tag: Tom Lord Tue May 20 13:35:38 2003 (archive-fs.c)
 */
