Beispiel #1
0
/* Get the current 'next-key' value and bump the record. */
static svn_error_t *
get_key_and_bump(svn_fs_t *fs,
                 const char **key,
                 trail_t *trail,
                 apr_pool_t *pool)
{
    base_fs_data_t *bfd = fs->fsap_data;
    DBC *cursor;
    char next_key[MAX_KEY_SIZE];
    apr_size_t key_len;
    int db_err;
    DBT query;
    DBT result;

    /* ### todo: see issue #409 for why bumping the key as part of this
       trail is problematic. */

    /* Open a cursor and move it to the 'next-key' value. We can then fetch
       the contents and use the cursor to overwrite those contents. Since
       this database allows duplicates, we can't do an arbitrary 'put' to
       write the new value -- that would append, not overwrite.  */

    svn_fs_base__trail_debug(trail, "strings", "cursor");
    SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
                     bfd->strings->cursor(bfd->strings, trail->db_txn,
                                          &cursor, 0)));

    /* Advance the cursor to 'next-key' and read it. */

    db_err = svn_bdb_dbc_get(cursor,
                             svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY),
                             svn_fs_base__result_dbt(&result),
                             DB_SET);
    if (db_err)
    {
        svn_bdb_dbc_close(cursor);
        return BDB_WRAP(fs, N_("getting next-key value"), db_err);
    }

    svn_fs_base__track_dbt(&result, pool);
    *key = apr_pstrmemdup(pool, result.data, result.size);

    /* Bump to future key. */
    key_len = result.size;
    svn_fs_base__next_key(result.data, &key_len, next_key);

    /* Shove the new key back into the database, at the cursor position. */
    db_err = svn_bdb_dbc_put(cursor, &query,
                             svn_fs_base__str_to_dbt(&result, next_key),
                             DB_CURRENT);
    if (db_err)
    {
        svn_bdb_dbc_close(cursor); /* ignore the error, the original is
                                    more important. */
        return BDB_WRAP(fs, N_("bumping next string key"), db_err);
    }

    return BDB_WRAP(fs, N_("closing string-reading cursor"),
                    svn_bdb_dbc_close(cursor));
}
Beispiel #2
0
/* Advance CURSOR by a single row in the set of rows whose keys match
   CURSOR's current location.  Set *LENGTH to the size of that next
   row.  If any error occurs, CURSOR will be destroyed.  */
static int
get_next_length(apr_size_t *length, DBC *cursor, DBT *query)
{
    DBT result;
    int db_err;

    /* Set up the DBT for reading the length of the record. */
    svn_fs_base__clear_dbt(&result);
    result.ulen = 0;
    result.flags |= DB_DBT_USERMEM;

    /* Note: this may change the QUERY DBT, but that's okay: we're going
       to be sticking with the same key anyways.  */
    db_err = svn_bdb_dbc_get(cursor, query, &result, DB_NEXT_DUP);

    /* Note that we exit on DB_NOTFOUND. The caller uses that to end a loop. */
    if (db_err)
    {
        DBT rerun;

        if (db_err != SVN_BDB_DB_BUFFER_SMALL)
        {
            svn_bdb_dbc_close(cursor);
            return db_err;
        }

        /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
           zero length buf), so we need to re-run the operation to make
           it happen. */
        svn_fs_base__clear_dbt(&rerun);
        rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
        db_err = svn_bdb_dbc_get(cursor, query, &rerun, DB_NEXT_DUP);
        if (db_err)
            svn_bdb_dbc_close(cursor);
    }

    /* ### this cast might not be safe? */
    *length = (apr_size_t) result.size;
    return db_err;
}
Beispiel #3
0
/* Allocate *CURSOR and advance it to first row in the set of rows
   whose key is defined by QUERY.  Set *LENGTH to the size of that
   first row.  */
static svn_error_t *
locate_key(apr_size_t *length,
           DBC **cursor,
           DBT *query,
           svn_fs_t *fs,
           trail_t *trail,
           apr_pool_t *pool)
{
    base_fs_data_t *bfd = fs->fsap_data;
    int db_err;
    DBT result;

    svn_fs_base__trail_debug(trail, "strings", "cursor");
    SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
                     bfd->strings->cursor(bfd->strings, trail->db_txn,
                                          cursor, 0)));

    /* Set up the DBT for reading the length of the record. */
    svn_fs_base__clear_dbt(&result);
    result.ulen = 0;
    result.flags |= DB_DBT_USERMEM;

    /* Advance the cursor to the key that we're looking for. */
    db_err = svn_bdb_dbc_get(*cursor, query, &result, DB_SET);

    /* We don't need to svn_fs_base__track_dbt() the result, because nothing
       was allocated in it. */

    /* If there's no such node, return an appropriately specific error.  */
    if (db_err == DB_NOTFOUND)
    {
        svn_bdb_dbc_close(*cursor);
        return svn_error_createf
               (SVN_ERR_FS_NO_SUCH_STRING, 0,
                "No such string '%s'", (const char *)query->data);
    }
    if (db_err)
    {
        DBT rerun;

        if (db_err != SVN_BDB_DB_BUFFER_SMALL)
        {
            svn_bdb_dbc_close(*cursor);
            return BDB_WRAP(fs, N_("moving cursor"), db_err);
        }

        /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
           zero length buf), so we need to re-run the operation to make
           it happen. */
        svn_fs_base__clear_dbt(&rerun);
        rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
        db_err = svn_bdb_dbc_get(*cursor, query, &rerun, DB_SET);
        if (db_err)
        {
            svn_bdb_dbc_close(*cursor);
            return BDB_WRAP(fs, N_("rerunning cursor move"), db_err);
        }
    }

    /* ### this cast might not be safe? */
    *length = (apr_size_t) result.size;

    return SVN_NO_ERROR;
}
Beispiel #4
0
svn_error_t *
svn_fs_bdb__string_copy(svn_fs_t *fs,
                        const char **new_key,
                        const char *key,
                        trail_t *trail,
                        apr_pool_t *pool)
{
    base_fs_data_t *bfd = fs->fsap_data;
    DBT query;
    DBT result;
    DBT copykey;
    DBC *cursor;
    int db_err;

    /* Copy off the old key in case the caller is sharing storage
       between the old and new keys. */
    const char *old_key = apr_pstrdup(pool, key);

    SVN_ERR(get_key_and_bump(fs, new_key, trail, pool));

    svn_fs_base__trail_debug(trail, "strings", "cursor");
    SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
                     bfd->strings->cursor(bfd->strings, trail->db_txn,
                                          &cursor, 0)));

    svn_fs_base__str_to_dbt(&query, old_key);
    svn_fs_base__str_to_dbt(&copykey, *new_key);

    svn_fs_base__clear_dbt(&result);

    /* Move to the first record and fetch its data (under BDB's mem mgmt). */
    db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
    if (db_err)
    {
        svn_bdb_dbc_close(cursor);
        return BDB_WRAP(fs, N_("getting next-key value"), db_err);
    }

    while (1)
    {
        /* ### can we pass a BDB-provided buffer to another BDB function?
           ### they are supposed to have a duration up to certain points
           ### of calling back into BDB, but I'm not sure what the exact
           ### rules are. it is definitely nicer to use BDB buffers here
           ### to simplify things and reduce copies, but... hrm.
        */

        /* Write the data to the database */
        svn_fs_base__trail_debug(trail, "strings", "put");
        db_err = bfd->strings->put(bfd->strings, trail->db_txn,
                                   &copykey, &result, 0);
        if (db_err)
        {
            svn_bdb_dbc_close(cursor);
            return BDB_WRAP(fs, N_("writing copied data"), db_err);
        }

        /* Read the next chunk. Terminate loop if we're done. */
        svn_fs_base__clear_dbt(&result);
        db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
        if (db_err == DB_NOTFOUND)
            break;
        if (db_err)
        {
            svn_bdb_dbc_close(cursor);
            return BDB_WRAP(fs, N_("fetching string data for a copy"), db_err);
        }
    }

    return BDB_WRAP(fs, N_("closing string-reading cursor"),
                    svn_bdb_dbc_close(cursor));
}
Beispiel #5
0
svn_error_t *
svn_fs_bdb__string_read(svn_fs_t *fs,
                        const char *key,
                        char *buf,
                        svn_filesize_t offset,
                        apr_size_t *len,
                        trail_t *trail,
                        apr_pool_t *pool)
{
    int db_err;
    DBT query, result;
    DBC *cursor;
    apr_size_t length, bytes_read = 0;

    svn_fs_base__str_to_dbt(&query, key);

    SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool));

    /* Seek through the records for this key, trying to find the record that
       includes OFFSET. Note that we don't require reading from more than
       one record since we're allowed to return partial reads.  */
    while (length <= offset)
    {
        offset -= length;

        /* Remember, if any error happens, our cursor has been closed
           for us. */
        db_err = get_next_length(&length, cursor, &query);

        /* No more records? They tried to read past the end. */
        if (db_err == DB_NOTFOUND)
        {
            *len = 0;
            return SVN_NO_ERROR;
        }
        if (db_err)
            return BDB_WRAP(fs, N_("reading string"), db_err);
    }

    /* The current record contains OFFSET. Fetch the contents now. Note that
       OFFSET has been moved to be relative to this record. The length could
       quite easily extend past this record, so we use DB_DBT_PARTIAL and
       read successive records until we've filled the request.  */
    while (1)
    {
        svn_fs_base__clear_dbt(&result);
        result.data = buf + bytes_read;
        result.ulen = *len - bytes_read;
        result.doff = (u_int32_t)offset;
        result.dlen = *len - bytes_read;
        result.flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL);
        db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_CURRENT);
        if (db_err)
        {
            svn_bdb_dbc_close(cursor);
            return BDB_WRAP(fs, N_("reading string"), db_err);
        }

        bytes_read += result.size;
        if (bytes_read == *len)
        {
            /* Done with the cursor. */
            SVN_ERR(BDB_WRAP(fs, N_("closing string-reading cursor"),
                             svn_bdb_dbc_close(cursor)));
            break;
        }

        /* Remember, if any error happens, our cursor has been closed
           for us. */
        db_err = get_next_length(&length, cursor, &query);
        if (db_err == DB_NOTFOUND)
            break;
        if (db_err)
            return BDB_WRAP(fs, N_("reading string"), db_err);

        /* We'll be reading from the beginning of the next record */
        offset = 0;
    }

    *len = bytes_read;
    return SVN_NO_ERROR;
}
Beispiel #6
0
svn_error_t *
svn_fs_bdb__changes_fetch_raw(apr_array_header_t **changes_p,
                              svn_fs_t *fs,
                              const char *key,
                              trail_t *trail,
                              apr_pool_t *pool)
{
  base_fs_data_t *bfd = fs->fsap_data;
  DBC *cursor;
  DBT query, result;
  int db_err = 0, db_c_err = 0;
  svn_error_t *err = SVN_NO_ERROR;
  change_t *change;
  apr_array_header_t *changes = apr_array_make(pool, 4, sizeof(change));

  /* Get a cursor on the first record matching KEY, and then loop over
     the records, adding them to the return array. */
  svn_fs_base__trail_debug(trail, "changes", "cursor");
  SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"),
                   bfd->changes->cursor(bfd->changes, trail->db_txn,
                                        &cursor, 0)));

  /* Advance the cursor to the key that we're looking for. */
  svn_fs_base__str_to_dbt(&query, key);
  svn_fs_base__result_dbt(&result);
  db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
  if (! db_err)
    svn_fs_base__track_dbt(&result, pool);

  while (! db_err)
    {
      svn_skel_t *result_skel;

      /* RESULT now contains a change record associated with KEY.  We
         need to parse that skel into an change_t structure ...  */
      result_skel = svn_skel__parse(result.data, result.size, pool);
      if (! result_skel)
        {
          err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
                                  _("Error reading changes for key '%s'"),
                                  key);
          goto cleanup;
        }
      err = svn_fs_base__parse_change_skel(&change, result_skel, pool);
      if (err)
        goto cleanup;

      /* ... and add it to our return array.  */
      APR_ARRAY_PUSH(changes, change_t *) = change;

      /* Advance the cursor to the next record with this same KEY, and
         fetch that record. */
      svn_fs_base__result_dbt(&result);
      db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
      if (! db_err)
        svn_fs_base__track_dbt(&result, pool);
    }

  /* If there are no (more) change records for this KEY, we're
     finished.  Just return the (possibly empty) array.  Any other
     error, however, needs to get handled appropriately.  */
  if (db_err && (db_err != DB_NOTFOUND))
    err = BDB_WRAP(fs, N_("fetching changes"), db_err);

 cleanup:
  /* Close the cursor. */
  db_c_err = svn_bdb_dbc_close(cursor);

  /* If we had an error prior to closing the cursor, return the error. */
  if (err)
    return svn_error_trace(err);

  /* If our only error thus far was when we closed the cursor, return
     that error. */
  if (db_c_err)
    SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err));

  /* Finally, set our return variable and get outta here. */
  *changes_p = changes;
  return SVN_NO_ERROR;
}
Beispiel #7
0
svn_error_t *
svn_fs_bdb__changes_fetch(apr_hash_t **changes_p,
                          svn_fs_t *fs,
                          const char *key,
                          trail_t *trail,
                          apr_pool_t *pool)
{
  base_fs_data_t *bfd = fs->fsap_data;
  DBC *cursor;
  DBT query, result;
  int db_err = 0, db_c_err = 0;
  svn_error_t *err = SVN_NO_ERROR;
  apr_hash_t *changes = apr_hash_make(pool);
  apr_pool_t *subpool = svn_pool_create(pool);

  /* Get a cursor on the first record matching KEY, and then loop over
     the records, adding them to the return array. */
  svn_fs_base__trail_debug(trail, "changes", "cursor");
  SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"),
                   bfd->changes->cursor(bfd->changes, trail->db_txn,
                                        &cursor, 0)));

  /* Advance the cursor to the key that we're looking for. */
  svn_fs_base__str_to_dbt(&query, key);
  svn_fs_base__result_dbt(&result);
  db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
  if (! db_err)
    svn_fs_base__track_dbt(&result, pool);

  while (! db_err)
    {
      change_t *change;
      svn_skel_t *result_skel;

      /* Clear the per-iteration subpool. */
      svn_pool_clear(subpool);

      /* RESULT now contains a change record associated with KEY.  We
         need to parse that skel into an change_t structure ...  */
      result_skel = svn_skel__parse(result.data, result.size, subpool);
      if (! result_skel)
        {
          err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
                                  _("Error reading changes for key '%s'"),
                                  key);
          goto cleanup;
        }
      err = svn_fs_base__parse_change_skel(&change, result_skel, subpool);
      if (err)
        goto cleanup;

      /* ... and merge it with our return hash.  */
      err = fold_change(changes, change);
      if (err)
        goto cleanup;

      /* Now, if our change was a deletion or replacement, we have to
         blow away any changes thus far on paths that are (or, were)
         children of this path.
         ### i won't bother with another iteration pool here -- at
             most we talking about a few extra dups of paths into what
             is already a temporary subpool.
      */
      if ((change->kind == svn_fs_path_change_delete)
          || (change->kind == svn_fs_path_change_replace))
        {
          apr_hash_index_t *hi;

          for (hi = apr_hash_first(subpool, changes);
               hi;
               hi = apr_hash_next(hi))
            {
              /* KEY is the path. */
              const void *hashkey;
              apr_ssize_t klen;
              const char *child_relpath;

              apr_hash_this(hi, &hashkey, &klen, NULL);

              /* If we come across our own path, ignore it.
                 If we come across a child of our path, remove it. */
              child_relpath = svn_fspath__skip_ancestor(change->path, hashkey);
              if (child_relpath && *child_relpath)
                apr_hash_set(changes, hashkey, klen, NULL);
            }
        }

      /* Advance the cursor to the next record with this same KEY, and
         fetch that record. */
      svn_fs_base__result_dbt(&result);
      db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
      if (! db_err)
        svn_fs_base__track_dbt(&result, pool);
    }

  /* Destroy the per-iteration subpool. */
  svn_pool_destroy(subpool);

  /* If there are no (more) change records for this KEY, we're
     finished.  Just return the (possibly empty) array.  Any other
     error, however, needs to get handled appropriately.  */
  if (db_err && (db_err != DB_NOTFOUND))
    err = BDB_WRAP(fs, N_("fetching changes"), db_err);

 cleanup:
  /* Close the cursor. */
  db_c_err = svn_bdb_dbc_close(cursor);

  /* If we had an error prior to closing the cursor, return the error. */
  if (err)
    return svn_error_trace(err);

  /* If our only error thus far was when we closed the cursor, return
     that error. */
  if (db_c_err)
    SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err));

  /* Finally, set our return variable and get outta here. */
  *changes_p = changes;
  return SVN_NO_ERROR;
}
Beispiel #8
0
svn_error_t *
svn_fs_bdb__locks_get(svn_fs_t *fs,
                      const char *path,
                      svn_fs_get_locks_callback_t get_locks_func,
                      void *get_locks_baton,
                      trail_t *trail,
                      apr_pool_t *pool)
{
  base_fs_data_t *bfd = fs->fsap_data;
  DBC *cursor;
  DBT key, value;
  int db_err, db_c_err;
  apr_pool_t *subpool = svn_pool_create(pool);
  const char *lock_token;
  svn_lock_t *lock;
  svn_error_t *err;
  const char *lookup_path = path;

  /* First, try to lookup PATH itself. */
  err = svn_fs_bdb__lock_token_get(&lock_token, fs, path, trail, pool);
  if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
              || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)
              || (err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)))
    {
      svn_error_clear(err);
    }
  else if (err)
    {
      return err;
    }
  else
    {
      SVN_ERR(get_lock(&lock, fs, path, lock_token, trail, pool));
      if (lock && get_locks_func)
        SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
    }

  /* Now go hunt for possible children of PATH. */
  if (strcmp(path, "/") != 0)
    lookup_path = apr_pstrcat(pool, path, "/", NULL);

  svn_fs_base__trail_debug(trail, "lock-tokens", "cursor");
  db_err = bfd->lock_tokens->cursor(bfd->lock_tokens, trail->db_txn,
                                    &cursor, 0);
  SVN_ERR(BDB_WRAP(fs, "creating cursor for reading lock tokens", db_err));

  /* Since the key is going to be returned as well as the value make
     sure BDB malloc's the returned key.  */
  svn_fs_base__str_to_dbt(&key, lookup_path);
  key.flags |= DB_DBT_MALLOC;

  /* Get the first matching key that is either equal or greater than
     the one passed in, by passing in the DB_RANGE_SET flag.  */
  db_err = svn_bdb_dbc_get(cursor, &key, svn_fs_base__result_dbt(&value),
                           DB_SET_RANGE);

  /* As long as the prefix of the returned KEY matches LOOKUP_PATH we
     know it is either LOOKUP_PATH or a decendant thereof.  */
  while ((! db_err)
         && strncmp(lookup_path, key.data, strlen(lookup_path)) == 0)
    {
      const char *child_path;

      svn_pool_clear(subpool);

      svn_fs_base__track_dbt(&key, subpool);
      svn_fs_base__track_dbt(&value, subpool);

      /* Create a usable path and token in temporary memory. */
      child_path = apr_pstrmemdup(subpool, key.data, key.size);
      lock_token = apr_pstrmemdup(subpool, value.data, value.size);

      /* Get the lock for CHILD_PATH.  */
      err = get_lock(&lock, fs, child_path, lock_token, trail, subpool);
      if (err)
        {
          svn_bdb_dbc_close(cursor);
          return err;
        }

      /* Lock is verified, hand it off to our callback. */
      if (lock && get_locks_func)
        {
          err = get_locks_func(get_locks_baton, lock, subpool);
          if (err)
            {
              svn_bdb_dbc_close(cursor);
              return err;
            }
        }

      svn_fs_base__result_dbt(&key);
      svn_fs_base__result_dbt(&value);
      db_err = svn_bdb_dbc_get(cursor, &key, &value, DB_NEXT);
    }

  svn_pool_destroy(subpool);
  db_c_err = svn_bdb_dbc_close(cursor);

  if (db_err && (db_err != DB_NOTFOUND))
    SVN_ERR(BDB_WRAP(fs, "fetching lock tokens", db_err));
  if (db_c_err)
    SVN_ERR(BDB_WRAP(fs, "fetching lock tokens (closing cursor)", db_c_err));

  return SVN_NO_ERROR;
}
Beispiel #9
0
svn_error_t *
svn_fs_bdb__get_txn_list(apr_array_header_t **names_p,
                         svn_fs_t *fs,
                         trail_t *trail,
                         apr_pool_t *pool)
{
  base_fs_data_t *bfd = fs->fsap_data;
  apr_size_t const next_key_key_len = strlen(NEXT_KEY_KEY);
  apr_pool_t *subpool = svn_pool_create(pool);
  apr_array_header_t *names;
  DBC *cursor;
  DBT key, value;
  int db_err, db_c_err;

  /* Allocate the initial names array */
  names = apr_array_make(pool, 4, sizeof(const char *));

  /* Create a database cursor to list the transaction names. */
  svn_fs_base__trail_debug(trail, "transactions", "cursor");
  SVN_ERR(BDB_WRAP(fs, "reading transaction list (opening cursor)",
                   bfd->transactions->cursor(bfd->transactions,
                                             trail->db_txn, &cursor, 0)));

  /* Build a null-terminated array of keys in the transactions table. */
  for (db_err = svn_bdb_dbc_get(cursor,
                                svn_fs_base__result_dbt(&key),
                                svn_fs_base__result_dbt(&value),
                                DB_FIRST);
       db_err == 0;
       db_err = svn_bdb_dbc_get(cursor,
                                svn_fs_base__result_dbt(&key),
                                svn_fs_base__result_dbt(&value),
                                DB_NEXT))
    {
      transaction_t *txn;
      svn_skel_t *txn_skel;
      svn_error_t *err;

      /* Clear the per-iteration subpool */
      svn_pool_clear(subpool);

      /* Track the memory alloc'd for fetching the key and value here
         so that when the containing pool is cleared, this memory is
         freed. */
      svn_fs_base__track_dbt(&key, subpool);
      svn_fs_base__track_dbt(&value, subpool);

      /* Ignore the "next-key" key. */
      if (key.size == next_key_key_len
          && 0 == memcmp(key.data, NEXT_KEY_KEY, next_key_key_len))
        continue;

      /* Parse TRANSACTION skel */
      txn_skel = svn_skel__parse(value.data, value.size, subpool);
      if (! txn_skel)
        {
          svn_bdb_dbc_close(cursor);
          return svn_fs_base__err_corrupt_txn
            (fs, apr_pstrmemdup(pool, key.data, key.size));
        }

      /* Convert skel to native type. */
      if ((err = svn_fs_base__parse_transaction_skel(&txn, txn_skel,
                                                     subpool)))
        {
          svn_bdb_dbc_close(cursor);
          return err;
        }

      /* If this is an immutable "committed" transaction, ignore it. */
      if (is_committed(txn))
        continue;

      /* Add the transaction name to the NAMES array, duping it into POOL. */
      APR_ARRAY_PUSH(names, const char *) = apr_pstrmemdup(pool, key.data,
                                                           key.size);
    }

  /* Check for errors, but close the cursor first. */
  db_c_err = svn_bdb_dbc_close(cursor);
  if (db_err != DB_NOTFOUND)
    {
      SVN_ERR(BDB_WRAP(fs, "reading transaction list (listing keys)",
                       db_err));
    }
  SVN_ERR(BDB_WRAP(fs, "reading transaction list (closing cursor)",
                   db_c_err));

  /* Destroy the per-iteration subpool */
  svn_pool_destroy(subpool);

  *names_p = names;
  return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_bdb__locks_get(svn_fs_t *fs,
                      const char *path,
                      svn_depth_t depth,
                      svn_fs_get_locks_callback_t get_locks_func,
                      void *get_locks_baton,
                      trail_t *trail,
                      apr_pool_t *pool)
{
  base_fs_data_t *bfd = fs->fsap_data;
  DBC *cursor;
  DBT key, value;
  int db_err, db_c_err;
  apr_pool_t *subpool = svn_pool_create(pool);
  const char *lock_token;
  svn_lock_t *lock;
  svn_error_t *err;
  const char *lookup_path = path;

  /* First, try to lookup PATH itself. */
  err = svn_fs_bdb__lock_token_get(&lock_token, fs, path, trail, pool);
  if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
              || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)
              || (err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)))
    {
      svn_error_clear(err);
    }
  else if (err)
    {
      return svn_error_trace(err);
    }
  else
    {
      SVN_ERR(get_lock(&lock, fs, path, lock_token, trail, pool));
      if (lock && get_locks_func)
        SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
    }

  /* If we're only looking at PATH itself (depth = empty), stop here. */
  if (depth == svn_depth_empty)
    return SVN_NO_ERROR;

  /* Now go hunt for possible children of PATH. */

  svn_fs_base__trail_debug(trail, "lock-tokens", "cursor");
  db_err = bfd->lock_tokens->cursor(bfd->lock_tokens, trail->db_txn,
                                    &cursor, 0);
  SVN_ERR(BDB_WRAP(fs, "creating cursor for reading lock tokens", db_err));

  /* Since the key is going to be returned as well as the value make
     sure BDB malloc's the returned key.  */
  svn_fs_base__str_to_dbt(&key, lookup_path);
  key.flags |= DB_DBT_MALLOC;

  /* Get the first matching key that is either equal or greater than
     the one passed in, by passing in the DB_RANGE_SET flag.  */
  db_err = svn_bdb_dbc_get(cursor, &key, svn_fs_base__result_dbt(&value),
                           DB_SET_RANGE);

  if (!svn_fspath__is_root(path, strlen(path)))
    lookup_path = apr_pstrcat(pool, path, "/", (char *)NULL);

  /* As long as the prefix of the returned KEY matches LOOKUP_PATH we
     know it is either LOOKUP_PATH or a decendant thereof.  */
  while ((! db_err)
         && strncmp(lookup_path, key.data, strlen(lookup_path)) == 0)
    {
      const char *child_path;

      svn_pool_clear(subpool);

      svn_fs_base__track_dbt(&key, subpool);
      svn_fs_base__track_dbt(&value, subpool);

      /* Create a usable path and token in temporary memory. */
      child_path = apr_pstrmemdup(subpool, key.data, key.size);
      lock_token = apr_pstrmemdup(subpool, value.data, value.size);

      if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
        {
          /* On the assumption that we only store locks for files,
             depth=files and depth=immediates should boil down to the
             same set of results.  So just see if CHILD_PATH is an
             immediate child of PATH.  If not, we don't care about
             this item.   */
          const char *rel_path = svn_fspath__is_child(path, child_path, subpool);
          if (!rel_path || (svn_path_component_count(rel_path) != 1))
            goto loop_it;
        }

      /* Get the lock for CHILD_PATH.  */
      err = get_lock(&lock, fs, child_path, lock_token, trail, subpool);
      if (err)
        {
          svn_bdb_dbc_close(cursor);
          return svn_error_trace(err);
        }

      /* Lock is verified, hand it off to our callback. */
      if (lock && get_locks_func)
        {
          err = get_locks_func(get_locks_baton, lock, subpool);
          if (err)
            {
              svn_bdb_dbc_close(cursor);
              return svn_error_trace(err);
            }
        }

    loop_it:
      svn_fs_base__result_dbt(&key);
      svn_fs_base__result_dbt(&value);
      db_err = svn_bdb_dbc_get(cursor, &key, &value, DB_NEXT);
    }

  svn_pool_destroy(subpool);
  db_c_err = svn_bdb_dbc_close(cursor);

  if (db_err && (db_err != DB_NOTFOUND))
    SVN_ERR(BDB_WRAP(fs, "fetching lock tokens", db_err));
  if (db_c_err)
    SVN_ERR(BDB_WRAP(fs, "fetching lock tokens (closing cursor)", db_c_err));

  return SVN_NO_ERROR;
}