blob: 919ccc5360a64bd58134ca923d76d782643667f8 [file] [log] [blame]
/* rev-table.c : working with the `revisions' table
*
* ====================================================================
* Copyright (c) 2000-2002 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#include <db.h>
#include "svn_fs.h"
#include "fs.h"
#include "err.h"
#include "dbt.h"
#include "skel.h"
#include "proplist.h"
#include "validate.h"
#include "rev-table.h"
/* Opening/creating the `revisions' table. */
int svn_fs__open_revisions_table (DB **revisions_p,
DB_ENV *env,
int create)
{
DB *revisions;
DB_ERR (db_create (&revisions, env, 0));
DB_ERR (revisions->open (revisions, "revisions", 0, DB_RECNO,
create ? (DB_CREATE | DB_EXCL) : 0,
0666));
*revisions_p = revisions;
return 0;
}
/* Storing and retrieving filesystem revisions. */
static int
is_valid_filesystem_revision (skel_t *skel)
{
int len = svn_fs__list_length (skel);
if (len == 3)
{
if (svn_fs__matches_atom (skel->children, "revision")
&& skel->children->next != NULL
&& svn_fs__is_valid_proplist (skel->children->next->next))
{
skel_t *id = skel->children->next;
if (id->is_atom
&& 0 == (1 & svn_fs__count_id_components (id->data, id->len)))
return 1;
}
}
return 0;
}
svn_error_t *
svn_fs__get_rev (skel_t **skel_p,
svn_fs_t *fs,
svn_revnum_t rev,
trail_t *trail)
{
int db_err;
DBT key, value;
skel_t *skel;
/* Turn the revision number into a Berkeley DB record number.
Revisions are numbered starting with zero; Berkeley DB record
numbers begin with one. */
db_recno_t recno = rev + 1;
db_err = fs->revisions->get (fs->revisions, trail->db_txn,
svn_fs__set_dbt (&key, &recno, sizeof (recno)),
svn_fs__result_dbt (&value),
0);
svn_fs__track_dbt (&value, trail->pool);
/* If there's no such revision, return an appropriately specific error. */
if (db_err == DB_NOTFOUND)
return svn_fs__err_dangling_rev (fs, rev);
/* Handle any other error conditions. */
SVN_ERR (DB_WRAP (fs, "reading filesystem revision", db_err));
/* Parse and check the REVISION skel. */
skel = svn_fs__parse_skel (value.data, value.size, trail->pool);
if (! skel
|| ! is_valid_filesystem_revision (skel))
return svn_fs__err_corrupt_fs_revision (fs, rev);
*skel_p = skel;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs__put_rev (svn_revnum_t *rev,
svn_fs_t *fs,
skel_t *skel,
trail_t *trail)
{
int db_err;
DBT key, value;
db_recno_t recno = 0;
/* xbc FIXME: Need a useful revision number here. */
if (! is_valid_filesystem_revision (skel))
return svn_fs__err_corrupt_fs_revision (fs, -1);
db_err = fs->revisions->put (fs->revisions, trail->db_txn,
svn_fs__recno_dbt(&key, &recno),
svn_fs__skel_to_dbt (&value, skel, trail->pool),
DB_APPEND);
SVN_ERR (DB_WRAP (fs, "storing filesystem revision", db_err));
/* Turn the record number into a Subversion revision number.
Revisions are numbered starting with zero; Berkeley DB record
numbers begin with one. */
*rev = recno - 1;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs__rev_get_root (svn_fs_id_t **root_id_p,
svn_fs_t *fs,
svn_revnum_t rev,
trail_t *trail)
{
skel_t *skel;
svn_fs_id_t *id;
SVN_ERR (svn_fs__get_rev (&skel, fs, rev, trail));
id = svn_fs_parse_id (skel->children->next->data,
skel->children->next->len,
trail->pool);
/* The skel validator doesn't check the ID format. */
if (id == NULL)
return svn_fs__err_corrupt_fs_revision (fs, -1);
*root_id_p = id;
return SVN_NO_ERROR;
}
/* Getting the youngest revision. */
svn_error_t *
svn_fs__youngest_rev (svn_revnum_t *youngest_p,
svn_fs_t *fs,
trail_t *trail)
{
int db_err;
DBC *cursor = 0;
DBT key, value;
db_recno_t recno;
SVN_ERR (svn_fs__check_fs (fs));
/* Create a database cursor. */
SVN_ERR (DB_WRAP (fs, "getting youngest revision (creating cursor)",
fs->revisions->cursor (fs->revisions, trail->db_txn,
&cursor, 0)));
/* Find the last entry in the `revisions' table. */
db_err = cursor->c_get (cursor,
svn_fs__recno_dbt (&key, &recno),
svn_fs__nodata_dbt (&value),
DB_LAST);
if (db_err)
{
/* Free the cursor. Ignore any error value --- the error above
is more interesting. */
cursor->c_close (cursor);
if (db_err == DB_NOTFOUND)
/* The revision 0 should always be present, at least. */
return
svn_error_createf
(SVN_ERR_FS_CORRUPT, 0, 0, fs->pool,
"revision 0 missing from `revisions' table, in filesystem `%s'",
fs->path);
SVN_ERR (DB_WRAP (fs, "getting youngest revision (finding last entry)",
db_err));
}
/* You can't commit a transaction with open cursors, because:
1) key/value pairs don't get deleted until the cursors referring
to them are closed, so closing a cursor can fail for various
reasons, and txn_commit shouldn't fail that way, and
2) using a cursor after committing its transaction can cause
undetectable database corruption. */
SVN_ERR (DB_WRAP (fs, "getting youngest revision (closing cursor)",
cursor->c_close (cursor)));
/* Turn the record number into a Subversion revision number.
Revisions are numbered starting with zero; Berkeley DB record
numbers begin with one. */
*youngest_p = recno - 1;
return SVN_NO_ERROR;
}
struct youngest_rev_args {
svn_revnum_t youngest;
svn_fs_t *fs;
};
static svn_error_t *
txn_body_youngest_rev (void *baton,
trail_t *trail)
{
struct youngest_rev_args *args = baton;
SVN_ERR (svn_fs__youngest_rev (&(args->youngest), args->fs, trail));
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_youngest_rev (svn_revnum_t *youngest_p,
svn_fs_t *fs,
apr_pool_t *pool)
{
struct youngest_rev_args args;
SVN_ERR (svn_fs__check_fs (fs));
args.fs = fs;
SVN_ERR (svn_fs__retry_txn (fs, txn_body_youngest_rev, &args, pool));
*youngest_p = args.youngest;
return SVN_NO_ERROR;
}
/* Generic revision operations. */
struct revision_prop_args {
svn_string_t **value_p;
svn_fs_t *fs;
svn_revnum_t rev;
const char *propname;
};
static svn_error_t *
txn_body_revision_prop (void *baton,
trail_t *trail)
{
struct revision_prop_args *args = baton;
skel_t *skel;
skel_t *proplist;
SVN_ERR (svn_fs__get_rev (&skel, args->fs, args->rev, trail));
/* PROPLIST is the third element of revision skel. */
proplist = skel->children->next->next;
/* Return the results of the generic property getting function. */
return svn_fs__get_prop (args->value_p,
proplist,
args->propname,
trail->pool);
}
svn_error_t *
svn_fs_revision_prop (svn_string_t **value_p,
svn_fs_t *fs,
svn_revnum_t rev,
const char *propname,
apr_pool_t *pool)
{
struct revision_prop_args args;
svn_string_t *value;
SVN_ERR (svn_fs__check_fs (fs));
args.value_p = &value;
args.fs = fs;
args.rev = rev;
args.propname = propname;
SVN_ERR (svn_fs__retry_txn (fs, txn_body_revision_prop, &args, pool));
*value_p = value;
return SVN_NO_ERROR;
}
struct revision_proplist_args {
apr_hash_t **table_p;
svn_fs_t *fs;
svn_revnum_t rev;
};
static svn_error_t *
txn_body_revision_proplist (void *baton, trail_t *trail)
{
struct revision_proplist_args *args = baton;
skel_t *skel;
skel_t *proplist;
SVN_ERR (svn_fs__get_rev (&skel, args->fs, args->rev, trail));
/* PROPLIST is the third element of revision skel. */
proplist = skel->children->next->next;
/* Return the results of the generic property hash getting function. */
return svn_fs__make_prop_hash (args->table_p,
proplist,
trail->pool);
}
svn_error_t *
svn_fs_revision_proplist (apr_hash_t **table_p,
svn_fs_t *fs,
svn_revnum_t rev,
apr_pool_t *pool)
{
struct revision_proplist_args args;
apr_hash_t *table;
SVN_ERR (svn_fs__check_fs (fs));
args.table_p = &table;
args.fs = fs;
args.rev = rev;
SVN_ERR (svn_fs__retry_txn (fs, txn_body_revision_proplist, &args, pool));
*table_p = table;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs__set_rev_prop (svn_fs_t *fs,
svn_revnum_t rev,
const char *name,
const svn_string_t *value,
trail_t *trail)
{
skel_t *skel;
skel_t *proplist;
SVN_ERR (svn_fs__get_rev (&skel, fs, rev, trail));
/* PROPLIST is the third element of revision skel. */
proplist = skel->children->next->next;
/* Call the generic property setting function. */
SVN_ERR (svn_fs__set_prop (proplist, name, value, trail->pool));
{
int db_err;
DBT query, result;
db_recno_t recno = rev + 1;
/* Update the filesystem revision with the new skel that reflects
our property edits. */
db_err = fs->revisions->put
(fs->revisions, trail->db_txn,
svn_fs__set_dbt (&query, &recno, sizeof (recno)),
svn_fs__skel_to_dbt (&result, skel, trail->pool), 0);
SVN_ERR (DB_WRAP (fs, "updating filesystem revision", db_err));
}
return SVN_NO_ERROR;
}
struct change_rev_prop_args {
svn_fs_t *fs;
svn_revnum_t rev;
const char *name;
const svn_string_t *value;
};
static svn_error_t *
txn_body_change_rev_prop (void *baton, trail_t *trail)
{
struct change_rev_prop_args *args = baton;
SVN_ERR (svn_fs__set_rev_prop (args->fs, args->rev,
args->name, args->value, trail));
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_change_rev_prop (svn_fs_t *fs,
svn_revnum_t rev,
const char *name,
const svn_string_t *value,
apr_pool_t *pool)
{
struct change_rev_prop_args args;
SVN_ERR (svn_fs__check_fs (fs));
args.fs = fs;
args.rev = rev;
args.name = name;
args.value = value;
SVN_ERR (svn_fs__retry_txn (fs, txn_body_change_rev_prop, &args, pool));
return SVN_NO_ERROR;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/