Alien-SVN

 view release on metacpan or  search on metacpan

src/subversion/subversion/libsvn_fs_fs/lock.c  view on Meta::CPAN

                  const char *digest_path,
                  walk_digests_callback_t walk_digests_func,
                  void *walk_digests_baton,
                  svn_boolean_t have_write_lock,
                  apr_pool_t *pool)
{
  apr_hash_index_t *hi;
  apr_hash_t *children;
  apr_pool_t *subpool;
  svn_lock_t *lock;

  /* First, send up any locks in the current digest file. */
  SVN_ERR(read_digest_file(&children, &lock, fs_path, digest_path, pool));

  SVN_ERR(walk_digests_func(walk_digests_baton, fs_path, digest_path,
                            children, lock,
                            have_write_lock, pool));

  /* Now, recurse on this thing's child entries (if any; bail otherwise). */
  if (! apr_hash_count(children))
    return SVN_NO_ERROR;
  subpool = svn_pool_create(pool);
  for (hi = apr_hash_first(pool, children); hi; hi = apr_hash_next(hi))
    {
      const char *digest = svn__apr_hash_index_key(hi);
      svn_pool_clear(subpool);
      SVN_ERR(walk_digest_files
              (fs_path, digest_path_from_digest(fs_path, digest, subpool),
               walk_digests_func, walk_digests_baton, have_write_lock, subpool));
    }
  svn_pool_destroy(subpool);
  return SVN_NO_ERROR;
}

/* A recursive function that calls GET_LOCKS_FUNC/GET_LOCKS_BATON for
   all locks in and under PATH in FS.
   HAVE_WRITE_LOCK should be true if the caller (directly or indirectly)
   has the FS write lock. */
static svn_error_t *
walk_locks(svn_fs_t *fs,
           const char *digest_path,
           svn_fs_get_locks_callback_t get_locks_func,
           void *get_locks_baton,
           svn_boolean_t have_write_lock,
           apr_pool_t *pool)
{
  struct walk_locks_baton wlb;

  wlb.get_locks_func = get_locks_func;
  wlb.get_locks_baton = get_locks_baton;
  wlb.fs = fs;
  SVN_ERR(walk_digest_files(fs->path, digest_path, locks_walker, &wlb,
                            have_write_lock, pool));
  return SVN_NO_ERROR;
}


/* Utility function:  verify that a lock can be used.  Interesting
   errors returned from this function:

      SVN_ERR_FS_NO_USER: No username attached to FS.
      SVN_ERR_FS_LOCK_OWNER_MISMATCH: FS's username doesn't match LOCK's owner.
      SVN_ERR_FS_BAD_LOCK_TOKEN: FS doesn't hold matching lock-token for LOCK.
 */
static svn_error_t *
verify_lock(svn_fs_t *fs,
            svn_lock_t *lock,
            apr_pool_t *pool)
{
  if ((! fs->access_ctx) || (! fs->access_ctx->username))
    return svn_error_createf
      (SVN_ERR_FS_NO_USER, NULL,
       _("Cannot verify lock on path '%s'; no username available"),
       lock->path);

  else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
    return svn_error_createf
      (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
       _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
       fs->access_ctx->username, lock->path, lock->owner);

  else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
    return svn_error_createf
      (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
       _("Cannot verify lock on path '%s'; no matching lock-token available"),
       lock->path);

  return SVN_NO_ERROR;
}


/* This implements the svn_fs_get_locks_callback_t interface, where
   BATON is just an svn_fs_t object. */
static svn_error_t *
get_locks_callback(void *baton,
                   svn_lock_t *lock,
                   apr_pool_t *pool)
{
  return verify_lock(baton, lock, pool);
}


/* The main routine for lock enforcement, used throughout libsvn_fs_fs. */
svn_error_t *
svn_fs_fs__allow_locked_operation(const char *path,
                                  svn_fs_t *fs,
                                  svn_boolean_t recurse,
                                  svn_boolean_t have_write_lock,
                                  apr_pool_t *pool)
{
  path = svn_fs__canonicalize_abspath(path, pool);
  if (recurse)
    {
      /* Discover all locks at or below the path. */
      const char *digest_path;
      SVN_ERR(digest_path_from_path(&digest_path, fs->path, path, pool));
      SVN_ERR(walk_locks(fs, digest_path, get_locks_callback,
                         fs, have_write_lock, pool));
    }
  else
    {
      /* Discover and verify any lock attached to the path. */
      svn_lock_t *lock;
      SVN_ERR(get_lock_helper(fs, &lock, path, have_write_lock, pool));
      if (lock)
        SVN_ERR(verify_lock(fs, lock, pool));
    }
  return SVN_NO_ERROR;
}

/* Baton used for lock_body below. */
struct lock_baton {
  svn_lock_t **lock_p;
  svn_fs_t *fs;
  const char *path;
  const char *token;
  const char *comment;
  svn_boolean_t is_dav_comment;
  apr_time_t expiration_date;
  svn_revnum_t current_rev;
  svn_boolean_t steal_lock;
  apr_pool_t *pool;
};


/* This implements the svn_fs_fs__with_write_lock() 'body' callback
   type, and assumes that the write lock is held.
   BATON is a 'struct lock_baton *'. */
static svn_error_t *
lock_body(void *baton, apr_pool_t *pool)
{
  struct lock_baton *lb = baton;
  svn_node_kind_t kind;
  svn_lock_t *existing_lock;
  svn_lock_t *lock;
  svn_fs_root_t *root;
  svn_revnum_t youngest;
  const char *rev_0_path;

  /* Until we implement directory locks someday, we only allow locks
     on files or non-existent paths. */
  /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
     library dependencies, which are not portable. */
  SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
  SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
  SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
  if (kind == svn_node_dir)
    return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);

  /* While our locking implementation easily supports the locking of
     nonexistent paths, we deliberately choose not to allow such madness. */
  if (kind == svn_node_none)
    {
      if (SVN_IS_VALID_REVNUM(lb->current_rev))
        return svn_error_createf(
          SVN_ERR_FS_OUT_OF_DATE, NULL,
          _("Path '%s' doesn't exist in HEAD revision"),
          lb->path);
      else
        return svn_error_createf(
          SVN_ERR_FS_NOT_FOUND, NULL,
          _("Path '%s' doesn't exist in HEAD revision"),
          lb->path);
    }

  /* We need to have a username attached to the fs. */
  if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
    return SVN_FS__ERR_NO_USER(lb->fs);

  /* Is the caller attempting to lock an out-of-date working file? */
  if (SVN_IS_VALID_REVNUM(lb->current_rev))
    {
      svn_revnum_t created_rev;
      SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
                                          pool));

      /* SVN_INVALID_REVNUM means the path doesn't exist.  So
         apparently somebody is trying to lock something in their
         working copy, but somebody else has deleted the thing
         from HEAD.  That counts as being 'out of date'. */
      if (! SVN_IS_VALID_REVNUM(created_rev))
        return svn_error_createf
          (SVN_ERR_FS_OUT_OF_DATE, NULL,
           _("Path '%s' doesn't exist in HEAD revision"), lb->path);

      if (lb->current_rev < created_rev)
        return svn_error_createf
          (SVN_ERR_FS_OUT_OF_DATE, NULL,
           _("Lock failed: newer version of '%s' exists"), lb->path);
    }

  /* If the caller provided a TOKEN, we *really* need to see
     if a lock already exists with that token, and if so, verify that
     the lock's path matches PATH.  Otherwise we run the risk of
     breaking the 1-to-1 mapping of lock tokens to locked paths. */
  /* ### TODO:  actually do this check.  This is tough, because the
     schema doesn't supply a lookup-by-token mechanism. */

  /* Is the path already locked?

     Note that this next function call will automatically ignore any
     errors about {the path not existing as a key, the path's token
     not existing as a key, the lock just having been expired}.  And
     that's totally fine.  Any of these three errors are perfectly
     acceptable to ignore; it means that the path is now free and
     clear for locking, because the fsfs funcs just cleared out both
     of the tables for us.   */
  SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
  if (existing_lock)
    {
      if (! lb->steal_lock)
        {
          /* Sorry, the path is already locked. */
          return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
        }
      else
        {
          /* STEAL_LOCK was passed, so fs_username is "stealing" the
             lock from lock->owner.  Destroy the existing lock. */
          SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
        }
    }

  /* Create our new lock, and add it to the tables.
     Ensure that the lock is created in the correct pool. */
  lock = svn_lock_create(lb->pool);
  if (lb->token)
    lock->token = apr_pstrdup(lb->pool, lb->token);
  else
    SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
                                           lb->pool));
  lock->path = apr_pstrdup(lb->pool, lb->path);
  lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
  lock->comment = apr_pstrdup(lb->pool, lb->comment);
  lock->is_dav_comment = lb->is_dav_comment;
  lock->creation_date = apr_time_now();
  lock->expiration_date = lb->expiration_date;
  SVN_ERR(svn_fs_fs__path_rev_absolute(&rev_0_path, lb->fs, 0, pool));
  SVN_ERR(set_lock(lb->fs->path, lock, rev_0_path, pool));
  *lb->lock_p = lock;

  return SVN_NO_ERROR;
}

/* Baton used for unlock_body below. */
struct unlock_baton {
  svn_fs_t *fs;
  const char *path;
  const char *token;
  svn_boolean_t break_lock;
};

/* This implements the svn_fs_fs__with_write_lock() 'body' callback
   type, and assumes that the write lock is held.
   BATON is a 'struct unlock_baton *'. */
static svn_error_t *
unlock_body(void *baton, apr_pool_t *pool)
{
  struct unlock_baton *ub = baton;
  svn_lock_t *lock;

  /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
  SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, TRUE, pool));

  /* Unless breaking the lock, we do some checks. */
  if (! ub->break_lock)
    {
      /* Sanity check:  the incoming token should match lock->token. */
      if (strcmp(ub->token, lock->token) != 0)
        return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);

      /* There better be a username attached to the fs. */
      if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
        return SVN_FS__ERR_NO_USER(ub->fs);

      /* And that username better be the same as the lock's owner. */
      if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
        return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
           ub->fs, ub->fs->access_ctx->username, lock->owner);
    }

  /* Remove lock and lock token files. */
  return delete_lock(ub->fs, lock, pool);
}


/*** Public API implementations ***/

svn_error_t *
svn_fs_fs__lock(svn_lock_t **lock_p,
                svn_fs_t *fs,
                const char *path,
                const char *token,
                const char *comment,
                svn_boolean_t is_dav_comment,
                apr_time_t expiration_date,
                svn_revnum_t current_rev,
                svn_boolean_t steal_lock,
                apr_pool_t *pool)
{
  struct lock_baton lb;

  SVN_ERR(svn_fs__check_fs(fs, TRUE));
  path = svn_fs__canonicalize_abspath(path, pool);

  lb.lock_p = lock_p;
  lb.fs = fs;
  lb.path = path;
  lb.token = token;
  lb.comment = comment;
  lb.is_dav_comment = is_dav_comment;
  lb.expiration_date = expiration_date;
  lb.current_rev = current_rev;
  lb.steal_lock = steal_lock;
  lb.pool = pool;

  return svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool);
}


svn_error_t *
svn_fs_fs__generate_lock_token(const char **token,
                               svn_fs_t *fs,
                               apr_pool_t *pool)
{
  SVN_ERR(svn_fs__check_fs(fs, TRUE));

  /* Notice that 'fs' is currently unused.  But perhaps someday, we'll
     want to use the fs UUID + some incremented number?  For now, we
     generate a URI that matches the DAV RFC.  We could change this to
     some other URI scheme someday, if we wish. */
  *token = apr_pstrcat(pool, "opaquelocktoken:",



( run in 0.763 second using v1.01-cache-2.11-cpan-e1769b4cff6 )