/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <opencm.h>
#include <repos/Repository.h>
#include <repos/FSRepos.h>
#include <client/KeyGen.h>
#include <common/SSLcommon.h>
#include <sys/file.h>
#include <opencm-version.h>
#include "../../zlib/zlib.h"

static EVP_PKEY *rpkey;

void fsrepos_maybe_upgrade_repository(Repository *r);

static const char *
make_muturi(const char *server, const char *mutable_name)
{
  const char *mutname = xstrcat("/", mutable_name);
  mutname = xstrcat(server, mutname);
  mutname = xstrcat("opencm://", mutname);
  return mutname;
}

int
WriteOneLineFile(const char *path, const char *string)
{
  int result;
  FILE *f = NULL;

  TRY {
    f = xfopen(path, 'c', 't');
  }
  DEFAULT(ex) {
    THROW(ex, format("Could not write file \"%s\": errno %d", path,
		     errno));
  }
  END_CATCH;

  result = fprintf(f, "%s\n", string);
  fclose(f);

  return result;
}

char *
ReadOneLineFile(const char *path)
{
  char *buf;
  size_t len = path_file_length(path);
  FILE *f = xfopen(path, 'r', 't');

  buf = GC_MALLOC_ATOMIC(len + 1);

  /* Length may get truncated if this is a WIN32 text file */
  len = fread(buf, 1, len, f);

  buf[len--] = 0;
  while (buf[len] == '\n' || buf[len] == '\r')
    buf[len--] = 0;

  fclose(f);

  return xstrdup(buf);
}

/* Return repository's private key.
 * Private key must be stored in a PEM file (which
 * is what the _CreateRepository command does anyway)
 * Private key is cached. */
static EVP_PKEY *
get_private_key(Repository *r)
{
  FILE *f = NULL;
  const char *pem = path_join(r->uri->path, R_CONFIG);

  if (rpkey)
    return rpkey;

  /* Retrieve private key */
  pem = path_join(pem, R_KEY_FILE);
  if (!path_exists(pem)) {
    THROW(ExNoAccess, 
	  format("Repository is corrupted. Can't find: %s.\n", pem));
  }

  f = xfopen(pem, 'r', 't');

  TRY {
    rpkey = PEM_read_PrivateKey(f, NULL, NULL, NULL);

    if (rpkey == NULL)
      THROW(ExNoAccess,
	    format("Error reading private key from %s [%s]", 
		   pem, ERR_reason_error_string(ERR_get_error())));
    fclose(f);
  }
  DEFAULT(ex) {
    fclose(f);
    RETHROW(ExNoAuth);
  }
  END_CATCH;

  return rpkey;
}

static unsigned char * 
sign_this(Repository *r, Buffer *buf, unsigned int *sign_len)
{
  unsigned char *retval = NULL;
  EVP_MD_CTX ctx;
  int result = 0;
  EVP_PKEY *private_key = get_private_key(r);
  unsigned char *signature = GC_MALLOC_ATOMIC(EVP_PKEY_size(private_key));

  /* Now use EVP library to sign content */
  EVP_SignInit(&ctx, EVP_sha1());

  {
    ocmoff_t end = buffer_length(buf);
    ocmoff_t pos = 0;

    while (pos < end) {
      BufferChunk bc = buffer_getChunk(buf, pos, end - pos);
      assert(bc.len <= (end - pos));

      EVP_SignUpdate(&ctx, bc.ptr, bc.len);
      
      pos += bc.len;
    }
  }
  result = EVP_SignFinal(&ctx, signature, sign_len, private_key);

  if (!result)
    THROW(ExBadValue, 
	  format("Signing error [%s]",
		 ERR_reason_error_string(ERR_get_error())));
  
  retval = GC_MALLOC_ATOMIC(*sign_len);
  memcpy(retval, signature, *sign_len);

  return retval;
}


static void
sign_and_save_mutable(Repository *r, Mutable *m)
{
  SDR_stream *signStream = 0, *strm = 0;
  const char *path, *tmppath, *truepath;
  unsigned char *raw_sign;
  unsigned int retint = 0;
  URI *uri = uri_create(m->uri);

  /* Make sure we sign this new revision */
  m->signature = NULL;
  m->seqNo++;

  TRY {
    signStream = stream_createBuffer(SDR_RAW);
    sdr_write("MUTABLE", signStream, m);
  }
  DEFAULT(ex) {
    if (signStream) stream_close(signStream);
    RETHROW(ex);
  }
  END_CATCH;

  stream_close(signStream);

  raw_sign = sign_this(r, stream_asBuffer(signStream), &retint);

  m->signature = hex_encode(raw_sign, retint);

  path = path_join(r->uri->path, R_MUTABLES);
  path = path_join(path, uri->netloc);

  TRY {
    tmppath = path_mktmpnm(path, "tmpmut-");
    truepath = path_join(path, path_car(uri->path+1));

    TRY {
      strm = stream_createfile(tmppath, SDR_CONTENT);
      sdr_write("MUTABLE", strm, m);
    }
    DEFAULT(ex) {
      if (strm) stream_close(strm);
      RETHROW(ex);
    }
    END_CATCH;

    stream_close(strm);

    /* Need to make sure that we didn't get outraced before rewriting.
     * NOTE:  we're expecting that the repos_GetMutable() call fails... */
    if (path_exists(truepath)) {
      Mutable *check = NULL;

      TRY {  
        /* NOTE: This check may fail if there is no pre-existing mutable! */
        check = repos_GetMutable(r, m->uri);
      }
      DEFAULT(AnyException) {
        /* No action -- just disarm the exception */
      }
      END_CATCH;

      if (check && check->seqNo >= m->seqNo)
        THROW(ExLostLock, 
              "Mutable commit failed -- somebody got"
              " there first.\n");
    }

    path_rename(tmppath, truepath);
  }
  END_CATCH;

  m->modified = 0;
}

static void
sign_revision(Repository *r, Revision *rev)
{
  SDR_stream *signStream;
  unsigned char *raw_sign;
  unsigned int retint = 0;

  /* Leave signature out for purposes of signature generation. */
  rev->signature = NULL;
  signStream = stream_createBuffer(SDR_RAW);
  sdr_write("REVISION", signStream, rev);
  stream_close(signStream);

  raw_sign = sign_this(r, stream_asBuffer(signStream), &retint);

  rev->signature = hex_encode(raw_sign, retint);
}

static Mutable *
doCreateMutable(Repository *r, const char *nm, const char *desc,
		unsigned flags, OC_bool isAdmin)
{
  Mutable *m;
  const char *mutDir = path_join(r->uri->path, R_MUTABLES);
  const char *newName;
  const char *curName;

  m = mutable_create(nm, desc);
  m->flags = flags;

  mutDir = path_join(mutDir, r->repositoryID);
  if (!path_exists(mutDir))
    path_smkdir(mutDir);

  do {
    newName = truename_NewName();
    curName = path_join(mutDir, newName);
  } while (path_exists(curName));

  m->uri = make_muturi(r->repositoryID, newName);

  /* Set up the read/write groups: */

  /* Administrator mutable creation is self-access. All others are
     access according to creator. */
  if (isAdmin) {
    m->modGroupURI  = m->uri;
    m->readGroupURI  = m->uri;
  }
  else {
    m->modGroupURI  = r->authMutable->uri;
    m->readGroupURI  = r->authMutable->uri;
  }

  sign_and_save_mutable(r, m);

  return m;
}

static const char *
keyBindingName(PubKey *pk)
{
  /* Prefix the id with "PKB_" (Public Key Binding), because one of
     the possible leading characters from the hash is '-', and UNIX
     utilities have a hard time with files that begin with '-'. */
  return xstrcat("PKB_", pubkey_getid(pk));
}

static const char *
keyBindingPath(Repository *r, PubKey *pk)
{
  const char *path = path_join(r->uri->path, R_USERS);
  return path_join(path, keyBindingName(pk));
}

static void
store_user_access(Repository *r, PubKey *pk, unsigned int access)
{
  SDR_stream *strm = NULL;
  const char *path = keyBindingPath(r, pk);

  if (!path_exists(path))
    THROW(ExObjectExists, 
	  format("User not found (path=%s)\n", path));

  TRY {
    strm = stream_createfile(path_join(path, R_USR_ACCESS), SDR_CONTENT);
    sdr_w_u32(R_USR_ACCESS, strm, access);
  }
  DEFAULT(ex) {
    if (strm) stream_close(strm);
    RETHROW(ex);
  }
  END_CATCH;
  stream_close(strm);
}

static unsigned int
get_user_access(Repository *r, PubKey *pk)
{
  unsigned int access = 0x0;
  SDR_stream *strm = NULL;
  const char *path = keyBindingPath(r, pk);

  if (!path_exists(path))
    THROW(ExObjectExists, 
	  format("User not found (path=%s)\n", path));

  strm = stream_fromfile(path_join(path, R_USR_ACCESS), SDR_AUTO);
  access = sdr_r_u32(R_USR_ACCESS, strm);
  stream_close(strm);

  return access;
}

static void
doBindKey(Repository *r, PubKey *pk, Mutable *m)
{
  const char *mut_file = NULL;
  const char *newpath = keyBindingPath(r, pk);

  if (path_exists(newpath))
    THROW(ExObjectExists, 
	  format("User already exists (keybinding=%s)\n", keyBindingName(pk)));

  /* Make a new filesystem directory using the binding as the name.
   * In that new directory, store the mutable name for this user. */
  path_mkdir(newpath);
  mut_file = path_join(newpath, R_USR_LINK);
  WriteOneLineFile(mut_file, m->uri);
}

/* doBindUser() used to be most of fsrepos_BindUser(), but I
   needed to separate out the user creation from the directory
   addition in order to support the new CreateRepository code. */
static Mutable *
doBindUser(Repository *r, PubKey *pk, unsigned int access, OC_bool isAdmin)
{
  Mutable   *m;
  Directory *homeDir;
  Mutable   *homeDirM;
  User      *u = user_create(pk, 0);
  const char *nm = pubkey_GetEmail(u->pubKey);

  if (nm == 0)
    THROW(ExBadValue, "Public key has no email field");

  if (!path_exists(keyBindingPath(r,pk))) {

    /* User doesn't exist on this repos, so create him */
    m = doCreateMutable(r, nm, "Repository User", MF_NOTRAIL, isAdmin);

    /* Create user's home directory as well */
    homeDir = directory_create();
    directory_Add(homeDir, R_SELF, m->uri);

    if (isAdmin) {
      /* The isAdmin case is only called from inside
	 CreateRepository. In that case, forge the necessary
	 authentication info here by setting things up so that u appears
	 to have authenticated. This is untrue, because the objects are
	 not yet written down, but it is sufficient to let the other
	 parts of the access control system behave correctly. */
      
      r->authUser = u;
      r->authMutable = m;
      r->authAccess = access;
    }
    
    homeDirM = repos_CreateMutable(r, "home", NULL, homeDir, MF_NOTRAIL);

    assert(homeDirM->uri);

    u->dirURI = homeDirM->uri;

    repos_ReviseEntity(r, m->uri, NULL, u);
    repos_ReviseEntity(r, homeDirM->uri, NULL, homeDir);

    /* The repos_CreateMutable() call above that created homeDirM
       proceed on behalf of the current (administrative) user before
       we knew the home directory mutable for that user. It
       consequently serialized the revision 0 record using an invalid
       User object. Now that we know the home directory mutable, fetch
       and re-serialize that revision by hand: */
    {
      Revision *rev = repos_GetRevision(r, homeDirM->uri, 0);
      rev->revisor = ser_getTrueName(u);
      fsrepos_WriteRevision(r, rev, TRUE);
    }

    /* Need to use ReviseMutable() rather than CreateMutable() because
     * when we do this from CreateRepository() we do not yet have an
     * administrator */
    m = repos_ReviseMutable(r, m->uri, m->nRevisions, u);

    /* FIX: Why is this logic duplicated here? */
    if (isAdmin) {
      r->authMutable = m;
      r->authAccess = access;
    }

    if (!isAdmin) {
      assert(r->adminGroup);

      /* The order of the following is important. If we change them
       * in the wrong order, we will lose read access before we set
       * the read group and the set of the read group will therefore
       * fail because it requires ACC_READ|ACC_WRITE (which perhaps
       * it shouldn't). */
      m = repos_SetMutableACL(r, m->uri, ACC_WRITE, r->adminGroup);
      m = repos_SetMutableACL(r, m->uri, ACC_READ, m->uri);
      if (isAdmin)
	r->authMutable = m;
    }

    /* This next step sets the proper access for the new user's newly
     * created directory.  This step MUST be done after the actual user
     * mutable has been serialized or this call will fail when SetMutableACL
     * tests whether the new ACL value is a valid user or group.
     */
    homeDirM = repos_SetMutableACL(r, homeDirM->uri, ACC_READ|ACC_WRITE, 
	                           m->uri);

    /* Bind the public key to the newly created mutable */
    doBindKey(r, pk, m);

  } else {  /* User already exists */

    /* Need to set return value */
    m = repos_GetUser(r, pk);
  }

  /* Now store/update this user's access for this repository */
  store_user_access(r, pk, access);

  /* There. Wasn't that painless? :-) */
  return m;
}

static void
doPutRevision(Repository *r, Revision *rev, OC_bool overWrite)
{
  SDR_stream *strm = 0;
  const char *path = path_join(r->uri->path, R_REVISIONS);
  URI *uri = uri_create(rev->mutURI);

  path = path_join(path, uri->netloc);
  path = path_join(path, path_car(uri->path+1));

  if (!path_exists(path))
    path_smkdir(path);

  path = path_join(path, xunsigned64_str(rev->seq_number));

  if (path_exists(path) && !overWrite)
    THROW(ExObjectExists, "Attempt to rewrite existing revision");

  TRY {
    strm = stream_createfile(path, SDR_CONTENT);
    sdr_write("REVISION", strm, (Serializable *)rev);
  }
  DEFAULT(ex) {
    if (strm) stream_close(strm);
    RETHROW(ex);
  }
  END_CATCH;

  stream_close(strm);
}

void
fsrepos_WriteRevision(Repository *r, Revision *rev, OC_bool overWrite)
{
  sign_revision(r, rev);
  doPutRevision(r, rev, overWrite);
}

static Mutable *
doFinishSetupUser(Repository *r, Mutable *userM)
{
  /* Given a newly created user, add them to the everyone group and
     also to the universal directory of users. A bunch of this crud
     really ought to be cached, but can't be if we allow multiple
     users referencing a local repository... */

  const char *everyoneURI;
  const char *configPath = path_join(r->uri->path, R_CONFIG);
  Serializable *s;
  Mutable *homeDirM;
  Directory *homeDir;
  User *user;

  user = (User *)repos_GetMutableContent(r, userM);

  homeDirM = repos_GetMutable(r, user->dirURI);
  homeDir = (Directory *) repos_GetMutableContent(r, homeDirM);

  everyoneURI = ReadOneLineFile(path_join(configPath, R_EVERYONE));
  {
    /* Add them to the universal users group. */
    Mutable *m;
    Group *g;
    const char *oldtn;

    m = repos_GetMutable(r, everyoneURI);
    s = repos_GetMutableContent(r, m);

    g = (Group *)s;
    oldtn = ser_getTrueName(g);
 
    group_append(g, userM->uri);

    /* If the group changed, upload it: */
    if (!nmequal(oldtn, ser_getTrueName(g))) {
      repos_ReviseEntity(r, m->uri, oldtn, g);
      m = repos_ReviseMutable(r, m->uri, m->nRevisions, g);
    }
  }

  {
    const char *oldtn;

    oldtn = ser_getTrueName(homeDir);

    directory_Add(homeDir, R_EVERYONE, everyoneURI);

    if (!nmequal(oldtn, ser_getTrueName(homeDir))) {
      /* Revise the home directory so that the changes are processed: */
      repos_ReviseEntity(r, homeDirM->uri, oldtn, homeDir);
      homeDirM = repos_ReviseMutable(r, homeDirM->uri,
				     homeDirM->nRevisions,
				     homeDir);
    }
  }

  userM = repos_SetMutableACL(r, userM->uri, ACC_READ, everyoneURI);

  return userM;
}

Repository *
fsrepos_create(const char *path, const char *fstype, PubKey *adminKey)
{
  URI *uri = 0;
  const char *verfile;
  const char *configPath;
  const char *typefile;
  const char *lockPath;

  Repository *r = (Repository *)GC_MALLOC(sizeof(Repository));

  /* Check for valid fstype */
  if (
      strcmp(fstype, CM_REPOS_FS) != 0 && 
      strcmp(fstype, CM_REPOS_GZFS) != 0 && 
      strcmp(fstype, CM_REPOS_SXD2) != 0
      )
    THROW(ExBadValue, 
	  format("Invalid fstype: %s", fstype));

  /* Make a uri out of the path, so we can easily sanity check the path */
  if (path == 0)
      THROW(ExNullArg, format("Bad path in URI %s", uri->URI));
  else
      uri = uri_create(xstrcat(CM_REPOS_FILE, xstrcat(":", path)));

  assert(uri);

  if (!uri->well_formed)
    THROW(ExBadValue, "Malformed server URI.\n");

  if (uri->relative)
    THROW(ExNoObject, 
	  format("Bad server URI %s please use a non-relative URI",
		 uri->URI));

  log_trace(DBG_REPOS_CREATE, "Path to new repository is \"%s\".\n", uri->path);

  if (path_exists(uri->path) && !path_isdir(uri->path))
    THROW(ExNoConnect, 
	  format("URI repository path %s exists and is not a directory",
		 uri->path));

  path_smkdir(uri->path);
  
  configPath = path_join(uri->path, R_CONFIG);

  /* Check for pre-existing ID or VERSION files */

  verfile = path_join(configPath, CM_VERSION_FILE);
  if (path_exists(verfile)) {
    THROW(ExObjectExists, 
	  format("Repository version file %s already exists",
		 verfile));
  }

  /* Create the config subdir. This will throw if the directory
     already exists, and we are relying on that to happen. We need the
     directory so that we can create the lock file in the proper
     location. This avoids the possibility that some wanker will try a
     local connect on this repository while we are attempting to
     fabricate it. */
  path_mkdir(configPath);

  lockPath = path_join(configPath, "LOCK");
  os_create_local_lockfile(lockPath);

  /* Create the repository version file */
  log_trace(DBG_REPOS_CREATE, "Creating version file \"%s\"\n", verfile);

  WriteOneLineFile(verfile, VERSION_STRING);

  /* Create the repository type file */
  typefile = path_join(configPath, R_STORETYPE);
  if (path_exists(typefile))
    THROW(ExObjectExists, 
	  format("Repository type file %s already exists",
		 typefile));

  log_trace(DBG_REPOS_CREATE, "Creating repos type file \"%s\"\n", typefile);

  WriteOneLineFile(typefile, fstype);

  path = path_join(uri->path, R_REVISIONS);
  path_mkdir(path);
  
  path = path_join(uri->path, R_USERS);
  path_mkdir(path);

  path = path_join(uri->path, R_MUTABLES);
  path_mkdir(path);

  Generate_X509_Key(path_join(configPath, R_KEY_FILE),
                    path_join(configPath, R_CERT_FILE),
                    FALSE, /* DON'T encrypt the server's private key */
                    3650); /* 3650 days == 10 years */

  /* Now that repository has actually been built, we can
   * continue the bootstrapping process by calling the 'open' function */
  r = repository_open(uri->URI);
  
  /* Generate initial unique identity for this repository by taking
   * the SHA-1 hash of the public key */
  r->svrPubKey = pubkey_from_PemFile(path_join(configPath, R_CERT_FILE));
  r->repositoryID = pubkey_getid(r->svrPubKey);

  WriteOneLineFile(path_join(configPath, R_IDENTITY),
		   r->repositoryID);

  /* The create repository code creates some directories before it
   * reconnects officially. Those calls now call repos_GetMutable()
   * during the sign and save logic, which fails if we haven't saved
   * the certificate association for the newly created repository. In
   * order to ensure that the cert association gets saved, hand
   * validate the server key here -- this is completely unncessary,
   * but it has the side effect of saving the association in the
   * user's ~/.opencm/servers/ tree. */
  repos_validate_pubkey(r->repositoryID, r->svrPubKey);

  {
    /* This will establish the repos's authMutable and authUser fields */
    Mutable *m;
    m = doBindUser(r, adminKey, ACC_READ|ACC_WRITE, TRUE);
  }

  {
    /* Now that we have an administrative user and a properly
       authenticated "current" user, we need to create the universal
       group and the directory of all users. This is an intervening step
       that is not done by repos_BindUser(): */

    Mutable *m;
    Group *g;

    /* Slightly inefficient to reread, but we are only going to do
       this at repository creation time and it lets us reuse the user
       creation code. */
    Mutable *adminHomeDirM;
    Directory *adminHomeDir;
    Serializable *s;

    adminHomeDirM = repos_GetMutable(r, r->authUser->dirURI);
    s = repos_GetMutableContent(r, adminHomeDirM);

    adminHomeDir = (Directory *)s;

    /* Create the group that will hold all users. Everyone should be
       able to read that, and it needs to appear in the
       administrator's home directory: */
    g = group_create();
    m = repos_CreateMutable(r, R_EVERYONE, NULL, g, MF_NOTRAIL);
    m = repos_SetMutableACL(r, m->uri, ACC_READ,  m->uri);

    path = path_join(configPath, R_EVERYONE);
    WriteOneLineFile(path, m->uri);

    /* Admin group is initially read/mod only to admin user, but admin
       user can change this. */
    g = group_create();
    group_append(g, r->authMutable->uri);
    m = repos_CreateMutable(r, "Administrators", NULL, g, MF_NOTRAIL);

    directory_Add(adminHomeDir, R_ADMIN_GROUP, m->uri);

    r->adminGroup = m->uri;

    /* Write down the administrative group binding: */
    path = path_join(configPath, R_ADMIN_GROUP);
    WriteOneLineFile(path, m->uri);

    /* Revise the administrator directory again so that the changes
       are processed: */
    repos_ReviseEntity(r, m->uri, NULL, adminHomeDir);
    adminHomeDirM = repos_ReviseMutable(r, adminHomeDirM->uri,
					adminHomeDirM->nRevisions, 
					adminHomeDir);
  }

  /* Need to add the administrative user into the universal group and
     directory, but the access check will fail because the
     administrative user is not yet a member of the universal
     group. */
  r->authMutable = doFinishSetupUser(r, r->authMutable);

  /* Finally, make the repository a self-authenticating user.  This user
   * (which has read access to everything in the repository) is
   * responsible for such things as event notification.  Note that this
   * user differs from the admin user on two counts:  1) it has universal
   * read access and 2) the private key for this new user is known by the
   * repository. */

  /* FIX: Once we verify that ACC_READ only does indeed create a truly
   * read-only user, change this line by removing ACC_WRITE: */
  repos_BindUser(r, r->svrPubKey, ACC_READ | ACC_WRITE);

  path_remove(lockPath);

  return r;
} 

static void
fsrepos_connect(Repository *r, PubKey *pk)
{
  Serializable *s;
  OC_bool firstConnect = FALSE;

  const char *configPath = path_join(r->uri->path, R_CONFIG);

  /* Check if first-time connect and do some sanity checks: */
  if (r->repositoryID == NULL)
    firstConnect = TRUE;   

  if (firstConnect) { 
    if (!path_exists(r->uri->path))
      THROW(ExNoObject, 
	    format("Repository %s does not exist", r->uri->URI));

    if (os_is_local_path(configPath) == FALSE)
      THROW(ExNoConnect, 
	    format("Repository %s is not mounted on a local file system", r->uri->URI));

    os_create_local_lockfile(path_join(configPath, R_LOCKFILE));

    if (!path_exists(path_join(configPath, CM_VERSION_FILE)))
      THROW(ExNoObject, 
	    format("Cannot determine repository %s schema version",
		   r->uri->URI));

    if (!path_exists(path_join(configPath, R_IDENTITY)))
      THROW(ExNoObject, 
	    format("Cannot determine repository %s identity",
		   r->uri->URI));

    if (!path_exists(path_join(configPath, R_ADMIN_GROUP)))
      THROW(ExNoObject, 
	    format("Cannot determine repository %s admin group",
		   r->uri->URI));

    r->repositoryID = ReadOneLineFile(path_join(configPath, R_IDENTITY));
    r->svrPubKey = pubkey_from_PemFile(path_join(configPath, R_CERT_FILE));

    /* This is here to ensure that the cert gets validated in the
       users cert records. */
    repos_validate_pubkey(r->repositoryID, r->svrPubKey);

    /* Rename the entity directories aggressively, since this saves us
       the hassle of supporting both names in the Get and Revise code. */

    if (!path_exists(path_join(r->uri->path, R_ENTDIR)))
      path_smkdir(path_join(r->uri->path, R_ENTDIR));

    if (path_exists(path_join(r->uri->path, R_OLD_FLATENTITIES)))
      path_rename(path_join(r->uri->path, R_OLD_FLATENTITIES), 
		  path_join(r->uri->path, R_FLATENTITIES));

    if (path_exists(path_join(r->uri->path, R_OLD_GZENTITIES)))
      path_rename(path_join(r->uri->path, R_OLD_GZENTITIES), 
		  path_join(r->uri->path, R_GZENTITIES));
  }

  if (pk) {
    assert(r->authUser == 0);
    assert(r->authMutable == 0);

    /* Until proven otherwise: */
    r->authAccess = 0;

    TRY {
      r->authMutable = repos_GetUser(r, pk);
      s = repos_GetMutableContent(r, r->authMutable);

      r->authUser = (User *)s;
      r->authAccess = get_user_access(r, pk);
    } 
    CATCH(ExNoObject) {
      RETHROW(ExNoAuth);
    }
    END_CATCH;
  }

  if (firstConnect) {
    r->adminGroup = ReadOneLineFile(path_join(configPath, R_ADMIN_GROUP));

    /* The checks above must continue to hold for all versions of the
       repository schema. If you can't even get far enough to
       determine the repository version, it's hardly possible to
       upgrade, now is it?

       The following function will upgrade the repository schema if
       that is called for. */
    fsrepos_maybe_upgrade_repository(r);
  }

  {
    const char *configPath = path_join(r->uri->path, R_CONFIG);
    if ( path_exists(path_join(configPath, R_READONLY)) &&
	 !repos_user_in_group(r, r->adminGroup, ACC_READ) )
      r->authAccess &= ~ACC_WRITE;
  }
}

static void
fsrepos_disconnect(Repository *r)
{
  const char *configPath = path_join(r->uri->path, R_CONFIG);

  path_remove(path_join(configPath, R_LOCKFILE));

}

static const char *
fsrepos_GetVersion(Repository *r)
{
  const char *version = ReadOneLineFile(path_join(r->uri->path,
					path_join(R_CONFIG, CM_VERSION_FILE)));

  return version;
}
  
static Mutable *
fsrepos_CreateMutable(Repository *r, const char *nm, const char *desc,
                      void *s, unsigned flags)
{
  /* Must create without flags so that we can do the first revise. */
  Mutable *m = doCreateMutable(r, nm, desc, 0, FALSE);

  /* Do just what repos_ReviseMutable does */
  if (s) {
    repos_ReviseEntity(r, m->uri, NULL, s);
    m = repos_ReviseMutable(r, m->uri, m->nRevisions, s);
  }

  if (flags)
    m = repos_SetMutableFlags(r, m->uri, flags);

  return m;
}

static Mutable *
fsrepos_DupMutable(Repository *r, const char *nm, const char *mURI,
		   OC_bool keepACLs,
		   uint64_t revNo,
		   unsigned flags)
{
  Mutable *dupM;
  Mutable *m = repos_GetMutable(r, mURI);
  Revision *rv;
  Revision *newRev;
  const char *mutDir = path_join(r->uri->path, R_MUTABLES);
  const char *newName;
  const char *curName;

  mutDir = path_join(mutDir, r->repositoryID);
  if (!path_exists(mutDir))
    path_smkdir(mutDir);

  /* We're dup'ing the description as well! It can always
   * be changed later (via the repos_SetMutableDesc() call). */
  dupM = mutable_create(nm, m->description);

  /* It is safe to dup the pre-existing ACLs or to set both to
   * current user. It is not safe to do anything else.
   */
  if (keepACLs) {
    dupM->readGroupURI = m->readGroupURI;
    dupM->modGroupURI = m->modGroupURI;
  } else {
    dupM->modGroupURI  = r->authMutable->uri;
    dupM->readGroupURI  = r->authMutable->uri;
  }

  dupM->flags = flags;

  rv = repos_GetRevision(r, m->uri, revNo);

  do {
    newName = truename_NewName();
    curName = xstrcat(mutDir, newName);
  } while (path_exists(curName));

  dupM->uri = make_muturi(r->repositoryID, newName);

  newRev = revision_create(dupM->uri,
			   rv->revisor,
			   rv->newObject);
  newRev->reviseTime = rv->reviseTime;

  fsrepos_WriteRevision(r, newRev, FALSE);

  assert(newRev->seq_number == 0);
  dupM->nRevisions = 1;

  sign_and_save_mutable(r, dupM);

  return dupM;
}

static void
fsrepos_TrimRevisions(Repository *r, Mutable *m)
{
  if (m->flags & MF_NOTRAIL) {
    URI *uri = uri_create(m->uri);
    const char *revSubDir = path_join(r->uri->path, R_REVISIONS);
    OC_DIR *dir;
    const char* rev;
    StrVec *sv = strvec_create();
    
    revSubDir = path_join(revSubDir, uri->netloc);
    revSubDir = path_join(revSubDir, path_car(uri->path+1));

    dir = path_opendir(revSubDir, TRUE);
    while ((rev = path_readdir(dir))) {
      const char *fullname = path_join(revSubDir, rev);

      if (path_should_skip_dirent(rev))
	continue;

      if (!path_isfile(fullname))
	continue;

      /* For the moment, we are renaming dead revisions rather than
	 removing them. */
      if (rev[0] == 'd')
	continue;

      if (strcmp(rev, xunsigned64_str(m->nRevisions -1)) == 0)
	continue;

      strvec_append(sv, rev);
    }
    path_closedir(dir);

    {
      unsigned u;

      const char *staleRevSubDir = path_join(revSubDir, R_STALEREVS);
      if (vec_size(sv) > 0)
	path_smkdir(staleRevSubDir);

      for(u = 0; u < vec_size(sv); u++) {
	const char *rev = vec_fetch(sv, u, const char *);
	const char *oldRevName = path_join(revSubDir, rev);
	const char *newRevName = path_join(staleRevSubDir, rev);

	log_trace(DBG_REPOS_NOTRAILS,
		  "Hiding revision %s\n", oldRevName);
	// path_remove(vec_fetch(sv, u, const char *));
	path_rename(oldRevName, newRevName);
      }
    }
  }
}

static Mutable * 
fsrepos_SetMutableFlags(Repository *r, const char *mURI, unsigned flags)
{
  Mutable *m = repos_GetMutable(r, mURI);

  if (flags > 0) {
    m->flags = flags;
    sign_and_save_mutable(r, m);
  }

  fsrepos_TrimRevisions(r, m);

  return m;
}

static Mutable *
fsrepos_GetMutable(Repository *r, const char *uri_s)
{
  Mutable *m;
  SDR_stream *strm;
  const char *path;
  const char *fullpath;
  URI *uri = uri_create(uri_s);
  
  path = path_join(R_MUTABLES, uri->netloc);
  path = path_join(path, path_car(uri->path+1));
  fullpath = path_join(r->uri->path, path);

  /* Possibly need to do GC reachability: */
  if (! path_exists(fullpath) ) {
    const char* gcPath = path_join(r->uri->path, path_join(R_GCDIR, path));

    if (path_exists(gcPath)) {
      path_srename(gcPath, fullpath);
      fsrepos_maybe_remove_dirtree(path_join(r->uri->path, R_GCDIR), 
				   path_dirname(gcPath));
    }
  }

  strm = stream_fromfile(fullpath, SDR_AUTO);

  m = sdr_read("MUTABLE", strm);
  stream_close(strm);

  return m;
}

static Mutable *
fsrepos_ReviseMutable(Repository *r, const char *mURI, uint64_t curTopRev, void *s)
{
  Mutable *m = repos_GetMutable(r, mURI);
  const char *trueName = NULL;
  Revision *rev;
  
  URI *uri = uri_create(mURI);

  if (m->nRevisions != curTopRev)
    THROW(ExBadValue, 
	  format("Top revision does not match most recent"
		 " in mutable %s", mURI));

  /* Mutables may ONLY be revised by owning repository */
  if (!nmequal(uri->netloc, r->repositoryID)) {
    THROW(ExNoAccess, 
	  format("Only the owning repository can revise a "
		 "Mutable (mut=%s)", mURI));
  }

  /* Can't modify a frozen */
  if (m->flags & MF_FROZEN) {
    THROW(ExNoAccess, 
	  format("Cannot modify frozen mutable %s", mURI));
  }

  /* Revision can be created initially with a null content pointer.
   * So, don't generate a true name for a null pointer: */
  if (s) {
    trueName = ser_getTrueName(s);
    assert(trueName);
  }

  rev = revision_create(m->uri, ser_getTrueName(r->authUser),
			trueName);

  rev->seq_number = m->nRevisions;

  fsrepos_WriteRevision(r, rev, FALSE);

  m->nRevisions++;

  sign_and_save_mutable(r, m);

  return m;
}

static Revision *
fsrepos_GetRevision(Repository *r, const char *mURI,
		    uint64_t revNo)
{
  Revision *rev;
  const char *relDir;
  const char *revDir;
  Mutable *m = fsrepos_GetMutable(r, mURI);
  URI *uri = uri_create(m->uri);

  relDir = path_join(R_REVISIONS, uri->netloc);
  relDir = path_join(relDir, path_car(uri->path+1));
  revDir = path_join(r->uri->path, relDir);

  /* Possibly need to do GC reachability: */
  if (! path_exists(revDir) ) {
    const char* gcPath = path_join(r->uri->path, path_join(R_GCDIR, relDir));

    if (path_exists(gcPath)) {
      path_srename(gcPath, revDir);
      fsrepos_maybe_remove_dirtree(path_join(r->uri->path, R_GCDIR), 
				   path_dirname(gcPath));
    }
  }

  if (!path_exists(revDir))
    THROW(ExNoObject, format("No revisions found for: %s.", mURI));

  if (!path_isdir(revDir))
    THROW(ExIntegrityFail, 
	  format("Revisions directory in repository %s is corrupted",
		 r->uri->URI));

  {
    SDR_stream *strm;
    const char *path;

    path = path_join(revDir, xunsigned64_str(revNo));

    if (!path_exists(path))
      THROW(ExNoObject, format("Revision %s/%s not available.", 
	                       m->uri, xunsigned64_str(revNo)));

    strm = stream_fromfile(path, SDR_AUTO);
    rev = (Revision *) sdr_read("REVISION", strm);
    stream_close(strm);

    if (GETTYPE(rev) != TY_Revision)
      THROW(ExMalformed, format("Fetch revision %s/%s is malformed.",
	                        m->uri, xunsigned64_str(revNo)));
	                      
  }

  return rev;
}

static void
fsrepos_PutRevision(Repository *r, Revision *rev)
{
}

static OC_bool
user_or_group(Repository *r, const char *uri)
{
  /* Make sure name represents a Group or User: */

  Serializable *s = NULL;
  Revision *rev = NULL;
  Mutable *m = repos_GetMutable(r, uri);

  TRY {
    rev = (Revision *) repos_GetTopRev(r, m);
  } 
  DEFAULT(AnyException) {
  }
  END_CATCH;
  if (rev == NULL)
    return FALSE;

  assert(rev->newObject);

  TRY {
    s = repos_GetEntity(r, m->uri, rev->newObject);
  }
  DEFAULT(AnyException) {
  }
  END_CATCH;
  if (s == NULL)
    return FALSE;

  return (GETTYPE(s) == TY_Group) || (GETTYPE(s) == TY_User);
}

static Mutable *
fsrepos_SetMutableACL(Repository *r, const char *mURI, unsigned int which, 
		      const char *uri)
{
  Mutable *m = repos_GetMutable(r, mURI);

  if (user_or_group(r, uri)) {
    if (which & ACC_WRITE)
      m->modGroupURI = uri;

    if (which & ACC_READ)
      m->readGroupURI = uri;

    sign_and_save_mutable(r, m);

    return m;
  } else {
    THROW(ExMalformed, "An ACL can only be set to a user or group");
  }

  return m;
}

static Mutable *
fsrepos_SetMutableName(Repository *r, const char *mURI, const char *name)
{
  Mutable *m = repos_GetMutable(r, mURI);

  m->name = name;
  sign_and_save_mutable(r, m);

  return m;
}

static Mutable *
fsrepos_SetMutableDesc(Repository *r, const char *mURI, const char *description)
{
  Mutable *m = repos_GetMutable(r, mURI);

  m->description = description;
  sign_and_save_mutable(r, m);

  return m;
}

static void
fsrepos_ReviseEntity(Repository *r,
		     const char *mURI,
		     const char *prevTrueName, void *s)
{
  /* Unwind block for internal temporary names */
  TRY {
    /* Only revise one way. */
    if (strcmp(r->storeType, CM_REPOS_FS) == 0)
      flatfile_ReviseEntity(r, mURI, prevTrueName, s);

    else if (strcmp(r->storeType, CM_REPOS_GZFS) == 0)
      gzfile_ReviseEntity(r, mURI, prevTrueName, s);

    else if (strcmp(r->storeType, CM_REPOS_SXD2) == 0)
      sxd2_ReviseEntity(r, mURI, prevTrueName, s);
  } END_CATCH;
}

static void
fsrepos_ReviseEntityDelta(Repository *r,
			  const char *mURI,
			  const char *prevTrueName, WireDelta *wd)
{
  void *ent;

  /* This is a quick and dirty, stupid implementation! */
  if (wd->baseTrueName != 0)
    THROW(ExNoObject, 
	  format("Object %s not found", wd->baseTrueName));
      
  {
    SDR_stream *strm;
    Buffer *buf = wd->delta;

    strm = stream_fromBuffer(buf, SDR_AUTO);
    ent = sdr_read("ENTITY", strm);
    stream_close(strm);
  }

  fsrepos_ReviseEntity(r, mURI, prevTrueName, ent);
}

static Buffer *
fsrepos_GetEntityBuffer(Repository *r, const char *mURI /* unused */,
			const char *trueName)
{
  Buffer *buf = 0;

  /* FIX: This is serial because of a bug in the try/catch logic, but
     I need to look around at other code before fixing that bug. */
  TRY {
    buf = flatfile_GetEntityBuffer(r, mURI, trueName);
  }
  CATCH(ExNoObject) {
    /* Do nothing */
  } END_CATCH;

  if (!buf) {
    TRY {
      buf = sxd2_GetEntityBuffer(r, mURI, trueName);
    }
    CATCH(ExNoObject) {
      /* Do nothing */
    } END_CATCH;
  }

  /* Let the last one throw: */
  if (!buf)
    buf = gzfile_GetEntityBuffer(r, mURI, trueName);

  /* Check the storage format. We aren't actually going to read this
     stream. We are building it only for the sake of format
     autodetection: */
  {
    SDR_stream *strm = stream_fromBuffer(buf, SDR_AUTO);
    if (stream_format(strm) != SDR_BINARY)
      THROW(ExMalformed, 
	    format("Object %s read from repository is not in BINARY format.",
		   trueName));
    stream_close(strm);
  }

  return buf;
}

static void *
fsrepos_GetEntity(Repository *r, const char *mURI /* unused */,
		  const char *trueName)
{
  void *ent;
  SDR_stream *strm;
  Buffer *buf = fsrepos_GetEntityBuffer(r, mURI, trueName);

  strm = stream_fromBuffer(buf, SDR_AUTO);
  ent = sdr_read("ENTITY", strm);
  stream_close(strm);

  return ent;
}

/* First (bad) cut at GetEntityDelta: just get the entire buffer and
   return that as a non-delta WireDelta object. */
static WireDelta *
fsrepos_GetEntityDelta(Repository *r, const char *mURI /* unused */,
		       const char *trueName, const char *baseTrueName)
{
  Buffer *buf = fsrepos_GetEntityBuffer(r, mURI, trueName);
  WireDelta *wdl = wireDelta_create(0, buf);

  return wdl;
}

static Mutable *
fsrepos_GetUser(Repository *r, PubKey *pk)
{
  Mutable *m;
  char *uri;
  const char *path = keyBindingPath(r, pk);

  if (!path_exists(path)) 
    THROW(ExNoObject, 
	  format("Can't find user %s", keyBindingName(pk)));

  uri = ReadOneLineFile(path_join(path, R_USR_LINK));

  m = fsrepos_GetMutable(r, uri);

  return m;
}

static unsigned int
fsrepos_GetUserAccess(Repository *r, PubKey *pk)
{
  if (!repos_user_in_group(r, r->adminGroup, ACC_READ))
    THROW(ExNoAccess, "Only administrator can get user access");

  return get_user_access(r, pk);
}

static Mutable *
fsrepos_RebindUser(Repository *r, const char *mURI, PubKey *newkey)
{
  Mutable *m = repos_GetMutable(r, mURI);
  PubKey *oldkey;
  User *user;
  User *new_user_state;

  /* Paranoia check:  make sure this is a User object: */
  {
    Revision *rev = repos_GetTopRev(r, m);
    Serializable *s = NULL;

    TRY {
      s = repos_GetEntity(r, m->uri, rev->newObject);
    }
    DEFAULT(any) {
    }
    END_CATCH;

    if (s == NULL || GETTYPE(s) != TY_User)
      THROW(ExNoObject, 
	    format("Mutable \"%s\" is not a User object.", m->uri));

    user = (User *)s;
  }
  oldkey = user->pubKey;

  /* Revise the existing mutable to contain new public key */
  new_user_state = user_dup(user);
  new_user_state->pubKey = newkey;

  /* Upload new User state and then revise the Mutable accordingly */
  repos_ReviseEntity(r, mURI, NULL, new_user_state);
  m = repos_ReviseMutable(r, mURI, m->nRevisions, new_user_state);

  /* Rename the binding */
  path_rename(keyBindingPath(r, oldkey), keyBindingPath(r, newkey));

  return m;
}

static Mutable *
fsrepos_BindUser(Repository *r, PubKey *pk, unsigned int access)
{
  Mutable *m;

  if (!repos_user_in_group(r, r->adminGroup, ACC_READ))
    THROW(ExNoAccess, "Only administrator can bind users");

  m = doBindUser(r, pk, access, FALSE);
  m = doFinishSetupUser(r, m);

  return m;
}

static void
fsrepos_ShutDown(Repository *r)
{
  if (!repos_user_in_group(r, r->adminGroup, ACC_READ))
    THROW(ExNoAccess, "Only administrator can shut the repository down");
}

static void
UpdateRepositoryVersionTo(Repository *r, const char *version_string)
{
  const char *verfile_path = 
    path_join(r->uri->path,
	      path_join(R_CONFIG, CM_VERSION_FILE));

  const char *vertmpfile_path =
    xstrcat(verfile_path, ".new");

  onthrow_remove(vertmpfile_path);

  WriteOneLineFile(vertmpfile_path, VERSION_STRING);

  path_rename(vertmpfile_path, verfile_path);
}

/* fsrepos_maybe_upgrade_repository(r) -- decide whether the
   repository schema needs to be updated, and if so, perform the update. */
#define OLDEST_RECOGNIZED_SCHEMA "0.1.0alpha5"
void 
fsrepos_maybe_upgrade_repository(Repository *r)
{
  struct sigaction old_sigint;
  struct sigaction old_sighup;
  struct sigaction old_sigquit;
  struct sigaction old_sigterm;
  struct sigaction sig_ignore;

  const char *schema_v = fsrepos_GetVersion(r);

  if (compare_rpm_versions(schema_v, VERSION_STRING) > 0)
    THROW(ExVersionError, 
	  "Repository schema is more recent than OpenCM version\n");

  if (compare_rpm_versions(schema_v, VERSION_STRING) == 0) {
    log_trace(DBG_UPGRADE, "Repository schema version is current\n");
    return;
  }

  memset(&sig_ignore, 0, sizeof(sig_ignore));
  sig_ignore.sa_handler = SIG_IGN;
  sig_ignore.sa_flags = 0;

  log_trace(TRC_UPGRADE, "Repository version %s being upgraded...\n"
	     "    Do not interrupt this.\n", schema_v);

  if (compare_rpm_versions(schema_v, OLDEST_RECOGNIZED_SCHEMA) < 0)
    THROW(ExVersionError, "Repository schema is too old to be upgraded by OpenCM");

  sigaction(SIGINT, &sig_ignore, &old_sigint);
  sigaction(SIGHUP, &sig_ignore, &old_sighup);
  sigaction(SIGQUIT, &sig_ignore, &old_sigquit);
  sigaction(SIGTERM, &sig_ignore, &old_sigterm);

#if 0
  /* Beginning in 0.1.6 we turned off all trails on users and
     directories by default. */
  if (compare_rpm_versions(schema_v, "0.1.6") < 0)
    upgrade_from_pre_0_1_6(r);
#endif
      
  /* The general idea here is to apply, sequentially, all of the
     necessary upgrade procedures. It is typical that the repository
     schema does NOT change with a given version of OpenCM, and in
     that case we can just blast the number and declare victory. 

     A word to the maintainer: this is a VERY sensitive sequence of
     operations. If there is just ONE place in the OpenCM code where
     you document obsessively, this should be it.

     The sigaction() calls notwithstanding, a proper upgrade procedure
     should endeavour to be robust against system crashes and/or
     shutdowns. Also, the user could always send us SIGKILL, which
     would stop us pretty much dead in our tracks. 

     FIX: Perhaps we should just fail the connect rather than do an
     in-place upgrade.  It would give the administrator a chance to
     backup the repository first.  The problem is that this would
     break things for naive users. I am considering adding a test for
     the presence of
    
        {REPOS-ROOT}/config/NO_AUTO_UPGRADE

     If present, it means don't do any upgrades by magic. Require
     instead that the administrator do an upgrade explicitly via the
     (not yet implemented) 'opencm upgrade' command.
  */

  /* Now that we have applied all of the required upgrades, rewrite
     the repository version to be the current one. */

  UpdateRepositoryVersionTo(r, VERSION_STRING);
  log_trace(TRC_UPGRADE, "Repository schema has been successfully upgraded to %s\n", VERSION_STRING);

  sigaction(SIGINT, &old_sigint, 0);
  sigaction(SIGHUP, &old_sighup, 0);
  sigaction(SIGQUIT, &old_sigquit, 0);
  sigaction(SIGTERM, &old_sigterm, 0);
}

static TnVec *
fsrepos_GetParents(Repository *r, const char *mURI /* unused */,
		   const char *trueName)
{
  TnVec *tv = tnvec_create();
  Change *c = (Change *) repos_GetEntity(r, mURI, trueName);

  if (GETTYPE(c) != TY_Change)
    return 0;

  if (CMGET(c,parent))
    tnvec_append(tv, CMGET(c,parent));
  if (CMGET(c,mergeParent))
    tnvec_append(tv, CMGET(c,mergeParent));

  return tv;
}

static void
fsrepos_SetCompression(Repository *r, const char *method, unsigned level)
{
}

static void
fsrepos_GarbageCollect(Repository *r, unsigned gcArg)
{
  const char *gcDirPath = path_join(r->uri->path, R_GCDIR);

  if (!repos_user_in_group(r, r->adminGroup, ACC_READ))
    THROW(ExNoAccess, "Only administrator can enumerate entities/mutables");

  switch (gcArg) {
  case RGC_START:
    {
      const char *mutDir = path_join(r->uri->path, R_MUTABLES);
      const char *revDir = path_join(r->uri->path, R_REVISIONS);
      const char *entDir = path_join(r->uri->path, R_ENTDIR);

      const char *gcMutDir = 
	path_join(r->uri->path, path_join(R_GCDIR, R_MUTABLES));
      const char *gcRevDir = 
	path_join(r->uri->path, path_join(R_GCDIR, R_REVISIONS));
      const char *gcEntDir = 
	path_join(r->uri->path, path_join(R_GCDIR, R_ENTDIR));

      if (path_exists(gcDirPath))
	THROW(ExBadValue, "GC is already in progress");

      path_mkdir(gcDirPath);

      path_rename(mutDir, gcMutDir);
      path_mkdir(mutDir);	/* for reachable objects */

      path_rename(revDir, gcRevDir);
      path_mkdir(revDir);	/* for reachable objects */

      path_rename(entDir, gcEntDir);
      path_mkdir(entDir);	/* for reachable objects */

      break;
    }
  case RGC_END:
    {
      path_rmdir_recursive(path_join(r->uri->path, R_GCDIR));

      break;
    }
  }
}

static void
fsrepos_do_enumerate_entities(const char *dirName, StrVec *sv)
{
  OC_DIR *dir;
  const char* ent;

  dir = path_opendir(dirName, FALSE);

  /* If the directory isn't there, that obviously there are not any entities
     inside of it */
  if(!dir)
    return;

  while ((ent = path_readdir(dir))) {
    if (path_should_skip_dirent(ent))
      continue;

    if (strcmp(ent, R_SUBDIR) == 0)
      continue;

    {
      const char *fullPath;
      fullPath = path_join(dirName, ent);
      if (path_isdir(fullPath))
	fsrepos_do_enumerate_entities(fullPath, sv);
      else
	strvec_append(sv, xstrdup(ent));
      /* FIX: This needs to filter out the tmpfiles */
    }
  }

  path_closedir(dir);
}

static TnVec *
fsrepos_Enumerate(Repository *r, const char *host,
		  unsigned obType, unsigned flags)
{
  TnVec *tv = tnvec_create();

  if (!repos_user_in_group(r, r->adminGroup, ACC_READ))
    THROW(ExNoAccess, "Only administrator can enumerate entities/mutables");

  switch(obType) {
  case RENUM_MUTABLES:
    {
      unsigned u;
      const char *mutables = path_join(r->uri->path, R_MUTABLES);
      const char *gcMutables = 
	path_join(r->uri->path, path_join(R_GCDIR, R_MUTABLES));

      StrVec *host_vec = strvec_create();

      if (host) {
	const char *host_dir = path_join(mutables, host);

	/* Only enumerate valid hosts */
	if(path_isdir(host_dir))
	  strvec_append(host_vec, host);

	host_dir = path_join(gcMutables, host);
	if(path_isdir(host_dir))
	  strvec_append(host_vec, host);
      }
      else {
	/* Enumerate the hosts, since no specific host was identified
	   for enumaration. */

	OC_DIR *dir;
	const char* ent;

	if (path_isdir(mutables)) {
	  dir = path_opendir(mutables, TRUE);
	  while ((ent = path_readdir(dir))) {
	    if (path_should_skip_dirent(ent))
	      continue;

	    strvec_append(host_vec, xstrdup(ent));
	  }

	  path_closedir(dir);
	}

	if (path_isdir(gcMutables)) {
	  dir = path_opendir(gcMutables, TRUE);
	  while ((ent = path_readdir(dir))) {
	    if (path_should_skip_dirent(ent))
	      continue;

	    strvec_append(host_vec, xstrdup(ent));
	  }

	  path_closedir(dir);
	}
      }

      /* For each host now in host_vec, enumerate the mutable URIs
	 associated with that host: */
      for (u = 0; u < vec_size(host_vec); u++) {
	const char *host = vec_fetch(host_vec, u, const char *);
	const char *host_dir = path_join(mutables, host);
	OC_DIR *dir;
        const char* ent;

	if (path_isdir(host_dir)) {
	  dir = path_opendir(host_dir, TRUE);
	  while ((ent = path_readdir(dir))) {
	    if (path_should_skip_dirent(ent))
	      continue;

	    tnvec_append(tv, format("opencm://%s/%s", host, ent));
	  }

	  path_closedir(dir);
	}

	host_dir = path_join(gcMutables, host);
	if (path_isdir(host_dir)) {
	  dir = path_opendir(host_dir, TRUE);
	  while ((ent = path_readdir(dir))) {
	    if (path_should_skip_dirent(ent))
	      continue;

	    tnvec_append(tv, format("opencm://%s/%s", host, ent));
	  }

	  path_closedir(dir);
	}
      }

      break;
    }

  case RENUM_ENTITIES:
    {
      StrVec *sv = strvec_create();

      {
	const char *dir = path_join(r->uri->path, R_GZENTITIES);

	if (path_isdir(dir)) {
	  fsrepos_do_enumerate_entities(dir, sv);

	  dir = path_join(r->uri->path, path_join(R_GCDIR, R_GZENTITIES));
	  fsrepos_do_enumerate_entities(dir, sv);
	}
      }
      {
	const char *dir = path_join(r->uri->path, R_FLATENTITIES);

	if (path_isdir(dir)) {
	  fsrepos_do_enumerate_entities(dir, sv);

	  dir = path_join(r->uri->path, path_join(R_GCDIR, R_FLATENTITIES));
	  fsrepos_do_enumerate_entities(dir, sv);
	}
      }


      /* We now have the raw version of the enumeration, but if the
	 repository is in mid-conversion it may (shouldn't, but may)
	 contain duplicates. The good news is that all of the file
	 type markers are suffixes, so this can be done with a sort
	 followed by a linear copy pass. */
      tnvec_sort(sv);

      {
	unsigned u;
	const char *last_copy = 0;

	for (u = 0; u < vec_size(sv); u++) {
	  const char *s = vec_fetch(sv, u, const char *);

	  if ( (flags & REFLG_NEEDCONVERT) && 
	       entity_frag_in_current_format(r, s))
	    continue;

	  s = entity_remove_store_suffix(s);

	  if (last_copy == 0 || (strcmp(s, last_copy) != 0)) {
	    tnvec_append(tv, s);
	    last_copy = s;
	  }
	}
      }

      break;
    }
  case RENUM_USERS:
    {
      const char *users = path_join(r->uri->path, R_USERS);
      OC_DIR *dir;
      const char* ent;
      TnVec *rtv = tnvec_create(); /* "raw" version of tv */

      dir = path_opendir(users, TRUE);

      while ((ent = path_readdir(dir))) {
	const char *user_binding;
	const char *mutableName;
	if (path_should_skip_dirent(ent))
	  continue;

	user_binding = path_join(users, ent);
	mutableName = ReadOneLineFile(path_join(user_binding, R_USR_LINK));

	tnvec_append(rtv, xstrdup(mutableName));
      }

      path_closedir(dir);

      /* Return them uniquely: */
      tnvec_sort(rtv);

      {
	unsigned u;
	const char *last_copy = 0;

	for (u = 0; u < vec_size(rtv); u++) {
	  const char *s = vec_fetch(rtv, u, const char *);

	  if (last_copy == 0 || (strcmp(s, last_copy) != 0)) {
	    tnvec_append(tv, s);
	    last_copy = s;
	  }
	}
      }
    }
  }

  return tv;
}

void
fsrepos_init(Repository *r)
{
  r->doesAccess        = FALSE;

  r->GetVersion        = fsrepos_GetVersion;
  r->Connect           = fsrepos_connect;
  r->Disconnect        = fsrepos_disconnect;

  /* Entity management:  */
  r->ReviseEntity      = fsrepos_ReviseEntity;
  r->GetEntity         = fsrepos_GetEntity;
  r->GetEntityDelta    = fsrepos_GetEntityDelta;
  r->ReviseEntityDelta = fsrepos_ReviseEntityDelta;

  /* Mutable management: */
  r->GetMutable        = fsrepos_GetMutable;
  r->CreateMutable     = fsrepos_CreateMutable;
  r->DupMutable        = fsrepos_DupMutable;
  r->ReviseMutable     = fsrepos_ReviseMutable;
  r->GetRevision       = fsrepos_GetRevision;
  r->PutRevision       = fsrepos_PutRevision;
  r->SetMutableACL     = fsrepos_SetMutableACL;
  r->SetMutableFlags   = fsrepos_SetMutableFlags;
  r->SetMutableName    = fsrepos_SetMutableName;
  r->SetMutableDesc    = fsrepos_SetMutableDesc;

  r->BindUser          = fsrepos_BindUser;
  r->RebindUser        = fsrepos_RebindUser;
  r->GetUser           = fsrepos_GetUser;
  r->GetUserAccess     = fsrepos_GetUserAccess;
  r->ShutDown          = fsrepos_ShutDown;
  r->GetParents        = fsrepos_GetParents;
  r->SetCompression    = fsrepos_SetCompression;
  r->Enumerate         = fsrepos_Enumerate;
  r->GarbageCollect    = fsrepos_GarbageCollect;

#ifdef ENTITY_CACHE
  r->cache = NULL;
#endif
}
