blob: 3487e4a8a15caa42a6fc39efcc6d2f0eb2b3844b [file] [log] [blame]
/* fs.c --- creating, opening and closing filesystems
*
* ====================================================================
* 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h> /* for EINVAL */
#include "apr_general.h"
#include "apr_pools.h"
#include "apr_file_io.h"
#include "svn_pools.h"
#include "db.h"
#include "svn_fs.h"
#include "fs.h"
#include "err.h"
#include "nodes-table.h"
#include "rev-table.h"
#include "txn-table.h"
#include "reps-table.h"
#include "strings-table.h"
#include "dag.h"
#include "svn_private_config.h"
/* Checking for return values, and reporting errors. */
/* If FS is already open, then return an SVN_ERR_FS_ALREADY_OPEN
error. Otherwise, return zero. */
static svn_error_t *
check_already_open (svn_fs_t *fs)
{
int major, minor, patch;
/* ### check_already_open() doesn't truly have the right semantic for
### this, but it is called by both create_berkeley and open_berkeley,
### so it happens to be a low-cost point. probably should be
### refactored to go elsewhere. note that svn_fs_new() doesn't return
### an error, so it isn't quite suitable. */
db_version (&major, &minor, &patch);
if ((major < SVN_FS_WANT_DB_MAJOR)
|| (major == SVN_FS_WANT_DB_MAJOR && minor < SVN_FS_WANT_DB_MINOR)
|| (major == SVN_FS_WANT_DB_MAJOR && minor == SVN_FS_WANT_DB_MINOR
&& patch < SVN_FS_WANT_DB_PATCH))
return svn_error_createf (SVN_ERR_FS_GENERAL, 0, 0, fs->pool,
"bad database version: %d.%d.%d",
major, minor, patch);
if (fs->env)
return svn_error_create (SVN_ERR_FS_ALREADY_OPEN, 0, 0, fs->pool,
"filesystem object already open");
else
return SVN_NO_ERROR;
}
/* A default warning handling function. */
static void
default_warning_func (void *baton, const char *fmt, ...)
{
/* The one unforgiveable sin is to fail silently. Dumping to stderr
or /dev/tty is not acceptable default behavior for server
processes, since those may both be equivalent to /dev/null. */
abort ();
}
/* Cleanup functions. */
/* Close a database in the filesystem FS.
DB_PTR is a pointer to the DB pointer in *FS to close.
NAME is the name of the database, for use in error messages. */
static svn_error_t *
cleanup_fs_db (svn_fs_t *fs, DB **db_ptr, const char *name)
{
if (*db_ptr)
{
DB *db = *db_ptr;
char *msg = apr_psprintf (fs->pool, "closing `%s' database", name);
int db_err;
*db_ptr = 0;
db_err = db->close (db, 0);
/* We can ignore DB_INCOMPLETE on db->close and db->sync; it
* just means someone else was using the db at the same time
* we were. See the Berkeley documentation at:
* http://www.sleepycat.com/docs/ref/program/errorret.html#DB_INCOMPLETE
* http://www.sleepycat.com/docs/api_c/db_close.html
*/
if (db_err == DB_INCOMPLETE)
db_err = 0;
SVN_ERR (DB_WRAP (fs, msg, db_err));
}
return SVN_NO_ERROR;
}
/* Close whatever Berkeley DB resources are allocated to FS. */
static svn_error_t *
cleanup_fs (svn_fs_t *fs)
{
DB_ENV *env = fs->env;
if (! env)
return SVN_NO_ERROR;
/* Close the databases. */
SVN_ERR (cleanup_fs_db (fs, &fs->nodes, "nodes"));
SVN_ERR (cleanup_fs_db (fs, &fs->revisions, "revisions"));
SVN_ERR (cleanup_fs_db (fs, &fs->transactions, "transactions"));
SVN_ERR (cleanup_fs_db (fs, &fs->representations, "representations"));
SVN_ERR (cleanup_fs_db (fs, &fs->strings, "strings"));
/* Checkpoint any changes. */
{
int db_err = env->txn_checkpoint (env, 0, 0, 0);
while (db_err == DB_INCOMPLETE)
{
apr_sleep (1000000L); /* microseconds, so 1000000L == 1 second */
db_err = env->txn_checkpoint (env, 0, 0, 0);
}
/* If the environment was not (properly) opened, then txn_checkpoint
will typically return EINVAL. Ignore this case.
Note: we're passing awfully simple values to txn_checkpoint. Any
possible EINVAL result is caused entirely by issues internal
to the DB. We should be safe to ignore EINVAL even if
something other than open-failure causes the result code.
(especially because we're just trying to close it down)
*/
if (db_err != 0 && db_err != EINVAL)
{
SVN_ERR (DB_WRAP (fs, "checkpointing environment", db_err));
}
}
/* Finally, close the environment. */
fs->env = 0;
SVN_ERR (DB_WRAP (fs, "closing environment",
env->close (env, 0)));
return SVN_NO_ERROR;
}
/* An APR pool cleanup function for a filesystem. DATA must be a
pointer to the filesystem to clean up.
When the filesystem object's pool is freed, we want the resources
held by Berkeley DB to go away, just like everything else. So we
register this cleanup function with the filesystem's pool, and let
it take care of closing the databases, the environment, and any
other DB objects we might be using. APR calls this function before
actually freeing the pool's memory.
It's a pity that we can't return an svn_error_t object from an APR
cleanup function. For now, we return the rather generic
SVN_ERR_FS_CLEANUP, and store a pointer to the real svn_error_t
object in *(FS->cleanup_error), for someone else to discover, if
they like. */
static apr_status_t
cleanup_fs_apr (void *data)
{
svn_fs_t *fs = (svn_fs_t *) data;
svn_error_t *svn_err = cleanup_fs (fs);
if (! svn_err)
return APR_SUCCESS;
else
{
/* Try to pass the error back up to the caller, if they're
prepared to receive it. Don't overwrite a previously stored
error --- in a cascade, the first message is usually the most
helpful. */
if (fs->cleanup_error
&& ! *fs->cleanup_error)
*fs->cleanup_error = svn_err;
else
/* If we can't return this error, print it as a warning.
(Feel free to replace this with some more sensible
behavior. I just don't want to throw any information into
the bit bucket.) */
(*fs->warning) (fs->warning_baton, "%s", svn_err->message);
return SVN_ERR_FS_CLEANUP;
}
}
/* Allocating and freeing filesystem objects. */
svn_fs_t *
svn_fs_new (apr_pool_t *parent_pool)
{
svn_fs_t *new;
/* Allocate a new filesystem object in its own pool, which is a
subpool of POOL. */
{
apr_pool_t *pool = svn_pool_create (parent_pool);
new = apr_pcalloc (pool, sizeof (svn_fs_t));
new->pool = pool;
}
new->warning = default_warning_func;
apr_pool_cleanup_register (new->pool, (void *) new,
(apr_status_t (*) (void *)) cleanup_fs_apr,
apr_pool_cleanup_null);
return new;
}
void
svn_fs_set_warning_func (svn_fs_t *fs,
svn_fs_warning_callback_t warning,
void *warning_baton)
{
fs->warning = warning;
fs->warning_baton = warning_baton;
}
svn_error_t *
svn_fs_set_berkeley_errcall (svn_fs_t *fs,
void (*db_errcall_fcn) (const char *errpfx,
char *msg))
{
SVN_ERR (svn_fs__check_fs (fs));
fs->env->set_errcall(fs->env, db_errcall_fcn);
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_close_fs (svn_fs_t *fs)
{
svn_error_t *svn_err = 0;
#if 0 /* Set to 1 for instrumenting. */
{
DB_TXN_STAT *t;
DB_LOCK_STAT *l;
int db_err;
/* Print transaction statistics for this DB env. */
if ((db_err = fs->env->txn_stat (fs->env, &t, 0)) != 0)
fprintf (stderr, "Error running fs->env->txn_stat(): %s",
db_strerror (db_err));
else
{
printf ("*** DB txn stats, right before closing env:\n");
printf (" Number of txns currently active: %d\n",
t->st_nactive);
printf (" Max number of active txns at any one time: %d\n",
t->st_maxnactive);
printf (" Number of transactions that have begun: %d\n",
t->st_nbegins);
printf (" Number of transactions that have aborted: %d\n",
t->st_naborts);
printf (" Number of transactions that have committed: %d\n",
t->st_ncommits);
printf (" Number of times a thread was forced to wait: %d\n",
t->st_region_wait);
printf (" Number of times a thread didn't need to wait: %d\n",
t->st_region_nowait);
printf ("*** End DB txn stats.\n\n");
}
/* Print transaction statistics for this DB env. */
if ((db_err = fs->env->lock_stat (fs->env, &l, 0)) != 0)
fprintf (stderr, "Error running fs->env->lock_stat(): %s",
db_strerror (db_err));
else
{
printf ("*** DB lock stats, right before closing env:\n");
printf (" The number of current locks: %d\n",
l->st_nlocks);
printf (" Max number of locks at any one time: %d\n",
l->st_maxnlocks);
printf (" Number of current lockers: %d\n",
l->st_nlockers);
printf (" Max number of lockers at any one time: %d\n",
l->st_maxnlockers);
printf (" Number of current objects: %d\n",
l->st_nobjects);
printf (" Max number of objects at any one time: %d\n",
l->st_maxnobjects);
printf (" Total number of locks requested: %d\n",
l->st_nrequests);
printf (" Total number of locks released: %d\n",
l->st_nreleases);
printf (" Total number of lock reqs failed because "
"DB_LOCK_NOWAIT was set: %d\n", l->st_nnowaits);
printf (" Total number of locks not immediately available "
"due to conflicts: %d\n", l->st_nconflicts);
printf (" Number of deadlocks detected: %d\n", l->st_ndeadlocks);
printf (" Number of times a thread waited before "
"obtaining the region lock: %d\n", l->st_region_wait);
printf (" Number of times a thread didn't have to wait: %d\n",
l->st_region_nowait);
printf ("*** End DB lock stats.\n\n");
}
}
#endif /* 0/1 */
/* We've registered cleanup_fs_apr as a cleanup function for this
pool, so just freeing the pool should shut everything down
nicely. But do catch an error, if one occurs. */
fs->cleanup_error = &svn_err;
svn_pool_destroy (fs->pool);
return svn_err;
}
/* Allocating an appropriate Berkeley DB environment object. */
/* Allocate a Berkeley DB environment object for the filesystem FS,
and set up its default parameters appropriately. */
static svn_error_t *
allocate_env (svn_fs_t *fs)
{
/* Allocate a Berkeley DB environment object. */
SVN_ERR (DB_WRAP (fs, "allocating environment object",
db_env_create (&fs->env, 0)));
/* If we detect a deadlock, select a transaction to abort at random
from those participating in the deadlock. */
SVN_ERR (DB_WRAP (fs, "setting deadlock detection policy",
fs->env->set_lk_detect (fs->env, DB_LOCK_RANDOM)));
/* Berkeley defaults to a 32k log buffer, which is too small for our
purposes; see this post from Daniel Berlin <dan@dberlin.org>:
http://subversion.tigris.org/servlets/ReadMsg?msgId=56325&listName=dev
for details. Below, we increase it to 256k for better
throughput. Note that the size of a logfile must be at least 4
times this amount; they default to 10 megs, so we're still fine,
but if you increase the 256 drastically, you'll want to look at
DB_ENV->set_lg_max(). */
SVN_ERR (DB_WRAP (fs, "setting in-memory log buffer size",
fs->env->set_lg_bsize (fs->env, 256 * 1024)));
return SVN_NO_ERROR;
}
/* Filesystem creation/opening. */
const char *
svn_fs_berkeley_path (svn_fs_t *fs, apr_pool_t *pool)
{
return apr_pstrdup (pool, fs->path);
}
svn_error_t *
svn_fs_create_berkeley (svn_fs_t *fs, const char *path)
{
apr_status_t apr_err;
svn_error_t *svn_err;
SVN_ERR (check_already_open (fs));
/* Initialize the fs's path. */
fs->path = apr_pstrdup (fs->pool, path);
/* Create the directory for the new Berkeley DB environment. */
apr_err = apr_dir_make (fs->path, APR_OS_DEFAULT, fs->pool);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf (apr_err, 0, 0, fs->pool,
"creating Berkeley DB environment dir `%s'",
fs->path);
/* Write the DB_CONFIG file. */
{
apr_file_t *dbconfig_file = NULL;
const char *dbconfig_file_name = apr_psprintf (fs->pool,
"%s/DB_CONFIG", path);
static const char * const dbconfig_contents =
"# This is the configuration file for the Berkeley DB environment\n"
"# used by your Subversion repository.\n"
"\n"
"### Lock subsystem\n"
"#\n"
"# Make sure you read the documentation at:\n"
"#\n"
"# http://www.sleepycat.com/docs/ref/lock/max.html\n"
"#\n"
"# before tweaking these values.\n"
"set_lk_max_locks 1000\n"
"set_lk_max_lockers 1000\n"
"set_lk_max_objects 1000\n";
apr_err = apr_file_open (&dbconfig_file, dbconfig_file_name,
APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
fs->pool);
if (apr_err != APR_SUCCESS)
return svn_error_createf (apr_err, 0, 0, fs->pool,
"opening `%s' for writing", dbconfig_file_name);
apr_err = apr_file_write_full (dbconfig_file, dbconfig_contents,
strlen (dbconfig_contents), NULL);
if (apr_err != APR_SUCCESS)
return svn_error_createf (apr_err, 0, 0, fs->pool,
"writing to `%s'", dbconfig_file_name);
apr_err = apr_file_close (dbconfig_file);
if (apr_err != APR_SUCCESS)
return svn_error_createf (apr_err, 0, 0, fs->pool,
"closing `%s'", dbconfig_file_name);
}
svn_err = allocate_env (fs);
if (svn_err) goto error;
/* Create the Berkeley DB environment. */
svn_err = DB_WRAP (fs, "creating environment",
fs->env->open (fs->env, fs->path,
(DB_CREATE
| DB_INIT_LOCK
| DB_INIT_LOG
| DB_INIT_MPOOL
| DB_INIT_TXN),
0666));
if (svn_err) goto error;
/* Create the databases in the environment. */
svn_err = DB_WRAP (fs, "creating `nodes' table",
svn_fs__open_nodes_table (&fs->nodes, fs->env, 1));
if (svn_err) goto error;
svn_err = DB_WRAP (fs, "creating `revisions' table",
svn_fs__open_revisions_table (&fs->revisions,
fs->env, 1));
if (svn_err) goto error;
svn_err = DB_WRAP (fs, "creating `transactions' table",
svn_fs__open_transactions_table (&fs->transactions,
fs->env, 1));
if (svn_err) goto error;
svn_err = DB_WRAP (fs, "creating `representations' table",
svn_fs__open_reps_table (&fs->representations,
fs->env, 1));
if (svn_err) goto error;
svn_err = DB_WRAP (fs, "creating `strings' table",
svn_fs__open_strings_table (&fs->strings,
fs->env, 1));
if (svn_err) goto error;
/* Initialize the DAG subsystem. */
svn_err = svn_fs__dag_init_fs (fs);
if (svn_err) goto error;
return SVN_NO_ERROR;
error:
(void) cleanup_fs (fs);
return svn_err;
}
/* Gaining access to an existing Berkeley DB-based filesystem. */
svn_error_t *
svn_fs_open_berkeley (svn_fs_t *fs, const char *path)
{
svn_error_t *svn_err;
SVN_ERR (check_already_open (fs));
/* Initialize paths. */
fs->path = apr_pstrdup (fs->pool, path);
svn_err = allocate_env (fs);
if (svn_err) goto error;
/* Open the Berkeley DB environment. */
svn_err = DB_WRAP (fs, "opening environment",
fs->env->open (fs->env, fs->path,
(DB_INIT_LOCK
| DB_INIT_LOG
| DB_INIT_MPOOL
| DB_INIT_TXN),
0666));
if (svn_err) goto error;
/* Open the various databases. */
svn_err = DB_WRAP (fs, "opening `nodes' table",
svn_fs__open_nodes_table (&fs->nodes, fs->env, 0));
if (svn_err) goto error;
svn_err = DB_WRAP (fs, "opening `revisions' table",
svn_fs__open_revisions_table (&fs->revisions,
fs->env, 0));
if (svn_err) goto error;
svn_err = DB_WRAP (fs, "opening `transactions' table",
svn_fs__open_transactions_table (&fs->transactions,
fs->env, 0));
if (svn_err) goto error;
svn_err = DB_WRAP (fs, "creating `representations' table",
svn_fs__open_reps_table (&fs->representations,
fs->env, 0));
if (svn_err) goto error;
svn_err = DB_WRAP (fs, "creating `strings' table",
svn_fs__open_strings_table (&fs->strings,
fs->env, 0));
if (svn_err) goto error;
return SVN_NO_ERROR;
error:
cleanup_fs (fs);
return svn_err;
}
/* Running recovery on a Berkeley DB-based filesystem. */
svn_error_t *
svn_fs_berkeley_recover (const char *path,
apr_pool_t *pool)
{
int db_err;
DB_ENV *env;
db_err = db_env_create (&env, 0);
if (db_err)
return svn_fs__dberr (pool, db_err);
/* Here's the comment copied from db_recover.c:
Initialize the environment -- we don't actually do anything
else, that all that's needed to run recovery.
Note that we specify a private environment, as we're about to
create a region, and we don't want to to leave it around. If
we leave the region around, the application that should create
it will simply join it instead, and will then be running with
incorrectly sized (and probably terribly small) caches. */
db_err = env->open (env, path, (DB_RECOVER | DB_CREATE
| DB_INIT_LOCK | DB_INIT_LOG
| DB_INIT_MPOOL | DB_INIT_TXN
| DB_PRIVATE),
0666);
if (db_err)
return svn_fs__dberr (pool, db_err);
db_err = env->close (env, 0);
if (db_err)
return svn_fs__dberr (pool, db_err);
return SVN_NO_ERROR;
}
/* Deleting a Berkeley DB-based filesystem. */
svn_error_t *
svn_fs_delete_berkeley (const char *path,
apr_pool_t *pool)
{
apr_status_t apr_err;
int db_err;
DB_ENV *env;
/* First, use the Berkeley DB library function to remove any shared
memory segments. */
db_err = db_env_create (&env, 0);
if (db_err)
return svn_fs__dberr (pool, db_err);
db_err = env->remove (env, path, DB_FORCE);
if (db_err)
return svn_fs__dberr (pool, db_err);
/* Remove the environment directory. */
apr_err = apr_dir_remove_recursively (path, pool);
if (! APR_STATUS_IS_SUCCESS (apr_err))
return svn_error_createf (apr_err, 0, 0, pool,
"recursively removing `%s'", path);
return SVN_NO_ERROR;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/