| /* fs.c --- creating, opening and closing filesystems |
| * |
| * ==================================================================== |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * ==================================================================== |
| */ |
| |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <apr_general.h> |
| #include <apr_pools.h> |
| #include <apr_file_io.h> |
| |
| #include "svn_hash.h" |
| #include "svn_pools.h" |
| #include "svn_fs.h" |
| #include "svn_path.h" |
| #include "svn_utf.h" |
| #include "svn_delta.h" |
| #include "svn_version.h" |
| #include "fs.h" |
| #include "err.h" |
| #include "dag.h" |
| #include "revs-txns.h" |
| #include "uuid.h" |
| #include "tree.h" |
| #include "id.h" |
| #include "lock.h" |
| #define SVN_WANT_BDB |
| #include "svn_private_config.h" |
| |
| #include "bdb/bdb-err.h" |
| #include "bdb/bdb_compat.h" |
| #include "bdb/env.h" |
| #include "bdb/nodes-table.h" |
| #include "bdb/rev-table.h" |
| #include "bdb/txn-table.h" |
| #include "bdb/copies-table.h" |
| #include "bdb/changes-table.h" |
| #include "bdb/reps-table.h" |
| #include "bdb/strings-table.h" |
| #include "bdb/uuids-table.h" |
| #include "bdb/locks-table.h" |
| #include "bdb/lock-tokens-table.h" |
| #include "bdb/node-origins-table.h" |
| #include "bdb/miscellaneous-table.h" |
| #include "bdb/checksum-reps-table.h" |
| |
| #include "../libsvn_fs/fs-loader.h" |
| #include "private/svn_fs_util.h" |
| |
| |
| /* Checking for return values, and reporting errors. */ |
| |
| /* Check that we're using the right Berkeley DB version. */ |
| /* FIXME: This check should be abstracted into the DB back-end layer. */ |
| static svn_error_t * |
| check_bdb_version(void) |
| { |
| int major, minor, patch; |
| |
| db_version(&major, &minor, &patch); |
| |
| /* First, check that we're using a reasonably correct of Berkeley DB. */ |
| 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, |
| _("Bad database version: got %d.%d.%d," |
| " should be at least %d.%d.%d"), |
| major, minor, patch, |
| SVN_FS_WANT_DB_MAJOR, |
| SVN_FS_WANT_DB_MINOR, |
| SVN_FS_WANT_DB_PATCH); |
| |
| /* Now, check that the version we're running against is the same as |
| the one we compiled with. */ |
| if (major != DB_VERSION_MAJOR || minor != DB_VERSION_MINOR) |
| return svn_error_createf(SVN_ERR_FS_GENERAL, 0, |
| _("Bad database version:" |
| " compiled with %d.%d.%d," |
| " running against %d.%d.%d"), |
| DB_VERSION_MAJOR, |
| DB_VERSION_MINOR, |
| DB_VERSION_PATCH, |
| major, minor, patch); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* 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 the BDB environment is panicked, don't do anything, since |
| attempting to close the database will fail anyway. */ |
| base_fs_data_t *bfd = fs->fsap_data; |
| if (*db_ptr && !svn_fs_bdb__get_panic(bfd->bdb)) |
| { |
| 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); |
| if (db_err == DB_RUNRECOVERY) |
| { |
| /* We can ignore DB_RUNRECOVERY errors from DB->close, but |
| must set the panic flag in the environment baton. The |
| error will be propagated appropriately from |
| svn_fs_bdb__close. */ |
| svn_fs_bdb__set_panic(bfd->bdb); |
| db_err = 0; |
| } |
| |
| #if SVN_BDB_HAS_DB_INCOMPLETE |
| /* 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; |
| #endif /* SVN_BDB_HAS_DB_INCOMPLETE */ |
| |
| SVN_ERR(BDB_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) |
| { |
| base_fs_data_t *bfd = fs->fsap_data; |
| bdb_env_baton_t *bdb = (bfd ? bfd->bdb : NULL); |
| |
| if (!bdb) |
| return SVN_NO_ERROR; |
| |
| /* Close the databases. */ |
| SVN_ERR(cleanup_fs_db(fs, &bfd->nodes, "nodes")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->revisions, "revisions")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->transactions, "transactions")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->copies, "copies")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->changes, "changes")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->representations, "representations")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->strings, "strings")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->uuids, "uuids")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->locks, "locks")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->lock_tokens, "lock-tokens")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->node_origins, "node-origins")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->checksum_reps, "checksum-reps")); |
| SVN_ERR(cleanup_fs_db(fs, &bfd->miscellaneous, "miscellaneous")); |
| |
| /* Finally, close the environment. */ |
| bfd->bdb = 0; |
| { |
| svn_error_t *err = svn_fs_bdb__close(bdb); |
| if (err) |
| return svn_error_createf |
| (err->apr_err, err, |
| _("Berkeley DB error for filesystem '%s'" |
| " while closing environment:\n"), |
| fs->path); |
| } |
| return SVN_NO_ERROR; |
| } |
| |
| #if 0 /* Set to 1 for instrumenting. */ |
| static void print_fs_stats(svn_fs_t *fs) |
| { |
| base_fs_data_t *bfd = fs->fsap_data; |
| DB_TXN_STAT *t; |
| DB_LOCK_STAT *l; |
| int db_err; |
| |
| /* Print transaction statistics for this DB env. */ |
| if ((db_err = bfd->bdb->env->txn_stat(bfd->bdb->env, &t, 0)) != 0) |
| fprintf(stderr, "Error running bfd->bdb->env->txn_stat(): %s", |
| db_strerror(db_err)); |
| else |
| { |
| printf("*** DB transaction stats, right before closing env:\n"); |
| printf(" Number of transactions currently active: %d\n", |
| t->st_nactive); |
| printf(" Max number of active transactions 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 transaction stats.\n\n"); |
| } |
| |
| /* Print transaction statistics for this DB env. */ |
| if ((db_err = bfd->bdb->env->lock_stat(bfd->bdb->env, &l, 0)) != 0) |
| fprintf(stderr, "Error running bfd->bdb->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"); |
| } |
| |
| } |
| #else |
| # define print_fs_stats(fs) |
| #endif /* 0/1 */ |
| |
| /* 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 pass the real svn_error_t to the registered |
| warning callback. */ |
| |
| static apr_status_t |
| cleanup_fs_apr(void *data) |
| { |
| svn_fs_t *fs = data; |
| svn_error_t *err; |
| |
| print_fs_stats(fs); |
| |
| err = cleanup_fs(fs); |
| if (! err) |
| return APR_SUCCESS; |
| |
| /* Darn. An error during cleanup. Call the warning handler to |
| try and do something "right" with this error. Note that |
| the default will simply abort(). */ |
| (*fs->warning)(fs->warning_baton, err); |
| |
| svn_error_clear(err); |
| |
| return SVN_ERR_FS_CLEANUP; |
| } |
| |
| |
| static svn_error_t * |
| base_bdb_set_errcall(svn_fs_t *fs, |
| void (*db_errcall_fcn)(const char *errpfx, char *msg)) |
| { |
| base_fs_data_t *bfd = fs->fsap_data; |
| |
| SVN_ERR(svn_fs__check_fs(fs, TRUE)); |
| bfd->bdb->error_info->user_callback = db_errcall_fcn; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Write the DB_CONFIG file. */ |
| static svn_error_t * |
| bdb_write_config(svn_fs_t *fs) |
| { |
| const char *dbconfig_file_name = |
| svn_dirent_join(fs->path, BDB_CONFIG_FILE, fs->pool); |
| apr_file_t *dbconfig_file = NULL; |
| int i; |
| |
| static const char dbconfig_contents[] = |
| "# This is the configuration file for the Berkeley DB environment\n" |
| "# used by your Subversion repository.\n" |
| "# You must run 'svnadmin recover' whenever you modify this file,\n" |
| "# for your changes to take effect.\n" |
| "\n" |
| "### Lock subsystem\n" |
| "#\n" |
| "# Make sure you read the documentation at:\n" |
| "#\n" |
| "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/lock_max.html\n" |
| "#\n" |
| "# before tweaking these values.\n" |
| "#\n" |
| "set_lk_max_locks 2000\n" |
| "set_lk_max_lockers 2000\n" |
| "set_lk_max_objects 2000\n" |
| "\n" |
| "### Log file subsystem\n" |
| "#\n" |
| "# Make sure you read the documentation at:\n" |
| "#\n" |
| "# http://docs.oracle.com/cd/E17076_02/html/api_reference/C/envset_lg_bsize.html\n" |
| "# http://docs.oracle.com/cd/E17076_02/html/api_reference/C/envset_lg_max.html\n" |
| "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_limits.html\n" |
| "#\n" |
| "# Increase the size of the in-memory log buffer from the default\n" |
| "# of 32 Kbytes to 256 Kbytes. Decrease the log file size from\n" |
| "# 10 Mbytes to 1 Mbyte. This will help reduce the amount of disk\n" |
| "# space required for hot backups. The size of the log file must be\n" |
| "# at least four times the size of the in-memory log buffer.\n" |
| "#\n" |
| "# Note: Decreasing the in-memory buffer size below 256 Kbytes will hurt\n" |
| "# hurt commit performance. For details, see:\n" |
| "#\n" |
| "# http://svn.haxx.se/dev/archive-2002-02/0141.shtml\n" |
| "#\n" |
| "set_lg_bsize 262144\n" |
| "set_lg_max 1048576\n" |
| "#\n" |
| "# If you see \"log region out of memory\" errors, bump lg_regionmax.\n" |
| "# For more information, see:\n" |
| "#\n" |
| "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n" |
| "# http://svn.haxx.se/users/archive-2004-10/1000.shtml\n" |
| "#\n" |
| "set_lg_regionmax 131072\n" |
| "#\n" |
| /* ### Configure this with "svnadmin create --bdb-cache-size" */ |
| "# The default cache size in BDB is only 256k. As explained in\n" |
| "# http://svn.haxx.se/dev/archive-2004-12/0368.shtml, this is too\n" |
| "# small for most applications. Bump this number if \"db_stat -m\"\n" |
| "# shows too many cache misses.\n" |
| "#\n" |
| "set_cachesize 0 1048576 1\n"; |
| |
| /* Run-time configurable options. |
| Each option set consists of a minimum required BDB version, a |
| config hash key, a header, an inactive form and an active |
| form. We always write the header; then, depending on the |
| run-time configuration and the BDB version we're compiling |
| against, we write either the active or inactive form of the |
| value. */ |
| static const struct |
| { |
| int bdb_major; |
| int bdb_minor; |
| const char *config_key; |
| const char *header; |
| const char *inactive; |
| const char *active; |
| } dbconfig_options[] = { |
| /* Controlled by "svnadmin create --bdb-txn-nosync" */ |
| { 4, 0, SVN_FS_CONFIG_BDB_TXN_NOSYNC, |
| /* header */ |
| "#\n" |
| "# Disable fsync of log files on transaction commit. Read the\n" |
| "# documentation about DB_TXN_NOSYNC at:\n" |
| "#\n" |
| "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n" |
| "#\n" |
| "# [requires Berkeley DB 4.0]\n" |
| "#\n", |
| /* inactive */ |
| "#set_flags DB_TXN_NOSYNC\n", |
| /* active */ |
| "set_flags DB_TXN_NOSYNC\n" }, |
| /* Controlled by "svnadmin create --bdb-log-keep" */ |
| { 4, 2, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE, |
| /* header */ |
| "#\n" |
| "# Enable automatic removal of unused transaction log files.\n" |
| "# Read the documentation about DB_LOG_AUTOREMOVE at:\n" |
| "#\n" |
| "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n" |
| "#\n" |
| "# [requires Berkeley DB 4.2]\n" |
| "#\n", |
| /* inactive */ |
| "#set_flags DB_LOG_AUTOREMOVE\n", |
| /* active */ |
| "set_flags DB_LOG_AUTOREMOVE\n" }, |
| }; |
| static const int dbconfig_options_length = |
| sizeof(dbconfig_options)/sizeof(*dbconfig_options); |
| |
| |
| SVN_ERR(svn_io_file_open(&dbconfig_file, dbconfig_file_name, |
| APR_WRITE | APR_CREATE, APR_OS_DEFAULT, |
| fs->pool)); |
| |
| SVN_ERR(svn_io_file_write_full(dbconfig_file, dbconfig_contents, |
| sizeof(dbconfig_contents) - 1, NULL, |
| fs->pool)); |
| |
| /* Write the variable DB_CONFIG flags. */ |
| for (i = 0; i < dbconfig_options_length; ++i) |
| { |
| void *value = NULL; |
| const char *choice; |
| |
| if (fs->config) |
| { |
| value = svn_hash_gets(fs->config, dbconfig_options[i].config_key); |
| } |
| |
| SVN_ERR(svn_io_file_write_full(dbconfig_file, |
| dbconfig_options[i].header, |
| strlen(dbconfig_options[i].header), |
| NULL, fs->pool)); |
| |
| if (((DB_VERSION_MAJOR == dbconfig_options[i].bdb_major |
| && DB_VERSION_MINOR >= dbconfig_options[i].bdb_minor) |
| || DB_VERSION_MAJOR > dbconfig_options[i].bdb_major) |
| && value != NULL && strcmp(value, "0") != 0) |
| choice = dbconfig_options[i].active; |
| else |
| choice = dbconfig_options[i].inactive; |
| |
| SVN_ERR(svn_io_file_write_full(dbconfig_file, choice, strlen(choice), |
| NULL, fs->pool)); |
| } |
| |
| return svn_io_file_close(dbconfig_file, fs->pool); |
| } |
| |
| static svn_error_t * |
| base_bdb_refresh_revision(svn_fs_t *fs, |
| apr_pool_t *scratch_pool) |
| { |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| base_bdb_info_format(int *fs_format, |
| svn_version_t **supports_version, |
| svn_fs_t *fs, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| base_fs_data_t *bfd = fs->fsap_data; |
| |
| *fs_format = bfd->format; |
| *supports_version = apr_palloc(result_pool, sizeof(svn_version_t)); |
| |
| (*supports_version)->major = SVN_VER_MAJOR; |
| (*supports_version)->minor = 0; |
| (*supports_version)->patch = 0; |
| (*supports_version)->tag = ""; |
| |
| switch (bfd->format) |
| { |
| case 1: |
| break; |
| case 2: |
| (*supports_version)->minor = 4; |
| break; |
| case 3: |
| (*supports_version)->minor = 5; |
| break; |
| case 4: |
| (*supports_version)->minor = 6; |
| break; |
| #ifdef SVN_DEBUG |
| # if SVN_FS_BASE__FORMAT_NUMBER != 4 |
| # error "Need to add a 'case' statement here" |
| # endif |
| #endif |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| base_bdb_info_config_files(apr_array_header_t **files, |
| svn_fs_t *fs, |
| apr_pool_t *result_pool, |
| apr_pool_t *scratch_pool) |
| { |
| *files = apr_array_make(result_pool, 1, sizeof(const char *)); |
| APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path, |
| BDB_CONFIG_FILE, |
| result_pool); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| base_bdb_verify_root(svn_fs_root_t *root, |
| apr_pool_t *scratch_pool) |
| { |
| /* Verifying is currently a no op for BDB. */ |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| base_bdb_freeze(svn_fs_t *fs, |
| svn_fs_freeze_func_t freeze_func, |
| void *freeze_baton, |
| apr_pool_t *pool) |
| { |
| SVN__NOT_IMPLEMENTED(); |
| } |
| |
| |
| /* Creating a new filesystem */ |
| |
| static fs_vtable_t fs_vtable = { |
| svn_fs_base__youngest_rev, |
| base_bdb_refresh_revision, |
| svn_fs_base__revision_prop, |
| svn_fs_base__revision_proplist, |
| svn_fs_base__change_rev_prop, |
| svn_fs_base__set_uuid, |
| svn_fs_base__revision_root, |
| svn_fs_base__begin_txn, |
| svn_fs_base__open_txn, |
| svn_fs_base__purge_txn, |
| svn_fs_base__list_transactions, |
| svn_fs_base__deltify, |
| svn_fs_base__lock, |
| svn_fs_base__generate_lock_token, |
| svn_fs_base__unlock, |
| svn_fs_base__get_lock, |
| svn_fs_base__get_locks, |
| base_bdb_info_format, |
| base_bdb_info_config_files, |
| NULL /* info_fsap */, |
| base_bdb_verify_root, |
| base_bdb_freeze, |
| base_bdb_set_errcall, |
| NULL /* ioctl */ |
| }; |
| |
| /* Where the format number is stored. */ |
| #define FORMAT_FILE "format" |
| |
| /* Depending on CREATE, create or open the environment and databases |
| for filesystem FS in PATH. */ |
| static svn_error_t * |
| open_databases(svn_fs_t *fs, |
| svn_boolean_t create, |
| int format, |
| const char *path) |
| { |
| base_fs_data_t *bfd; |
| |
| SVN_ERR(svn_fs__check_fs(fs, FALSE)); |
| |
| bfd = apr_pcalloc(fs->pool, sizeof(*bfd)); |
| fs->vtable = &fs_vtable; |
| fs->fsap_data = bfd; |
| |
| /* Initialize the fs's path. */ |
| fs->path = apr_pstrdup(fs->pool, path); |
| |
| if (create) |
| SVN_ERR(bdb_write_config(fs)); |
| |
| /* Create the Berkeley DB environment. */ |
| { |
| svn_error_t *err = svn_fs_bdb__open(&(bfd->bdb), path, |
| SVN_BDB_STANDARD_ENV_FLAGS, |
| 0666, fs->pool); |
| if (err) |
| { |
| if (create) |
| return svn_error_createf |
| (err->apr_err, err, |
| _("Berkeley DB error for filesystem '%s'" |
| " while creating environment:\n"), |
| fs->path); |
| else |
| return svn_error_createf |
| (err->apr_err, err, |
| _("Berkeley DB error for filesystem '%s'" |
| " while opening environment:\n"), |
| fs->path); |
| } |
| } |
| |
| /* We must register the FS cleanup function *after* opening the |
| environment, so that it's run before the environment baton |
| cleanup. */ |
| apr_pool_cleanup_register(fs->pool, fs, cleanup_fs_apr, |
| apr_pool_cleanup_null); |
| |
| |
| /* Create the databases in the environment. */ |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'nodes' table") |
| : N_("opening 'nodes' table")), |
| svn_fs_bdb__open_nodes_table(&bfd->nodes, |
| bfd->bdb->env, |
| create))); |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'revisions' table") |
| : N_("opening 'revisions' table")), |
| svn_fs_bdb__open_revisions_table(&bfd->revisions, |
| bfd->bdb->env, |
| create))); |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'transactions' table") |
| : N_("opening 'transactions' table")), |
| svn_fs_bdb__open_transactions_table(&bfd->transactions, |
| bfd->bdb->env, |
| create))); |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'copies' table") |
| : N_("opening 'copies' table")), |
| svn_fs_bdb__open_copies_table(&bfd->copies, |
| bfd->bdb->env, |
| create))); |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'changes' table") |
| : N_("opening 'changes' table")), |
| svn_fs_bdb__open_changes_table(&bfd->changes, |
| bfd->bdb->env, |
| create))); |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'representations' table") |
| : N_("opening 'representations' table")), |
| svn_fs_bdb__open_reps_table(&bfd->representations, |
| bfd->bdb->env, |
| create))); |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'strings' table") |
| : N_("opening 'strings' table")), |
| svn_fs_bdb__open_strings_table(&bfd->strings, |
| bfd->bdb->env, |
| create))); |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'uuids' table") |
| : N_("opening 'uuids' table")), |
| svn_fs_bdb__open_uuids_table(&bfd->uuids, |
| bfd->bdb->env, |
| create))); |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'locks' table") |
| : N_("opening 'locks' table")), |
| svn_fs_bdb__open_locks_table(&bfd->locks, |
| bfd->bdb->env, |
| create))); |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'lock-tokens' table") |
| : N_("opening 'lock-tokens' table")), |
| svn_fs_bdb__open_lock_tokens_table(&bfd->lock_tokens, |
| bfd->bdb->env, |
| create))); |
| |
| if (format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT) |
| { |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'node-origins' table") |
| : N_("opening 'node-origins' table")), |
| svn_fs_bdb__open_node_origins_table(&bfd->node_origins, |
| bfd->bdb->env, |
| create))); |
| } |
| |
| if (format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT) |
| { |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'miscellaneous' table") |
| : N_("opening 'miscellaneous' table")), |
| svn_fs_bdb__open_miscellaneous_table(&bfd->miscellaneous, |
| bfd->bdb->env, |
| create))); |
| } |
| |
| if (format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT) |
| { |
| SVN_ERR(BDB_WRAP(fs, (create |
| ? N_("creating 'checksum-reps' table") |
| : N_("opening 'checksum-reps' table")), |
| svn_fs_bdb__open_checksum_reps_table(&bfd->checksum_reps, |
| bfd->bdb->env, |
| create))); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Called by functions that initialize an svn_fs_t struct, after that |
| initialization is done, to populate svn_fs_t->uuid. */ |
| static svn_error_t * |
| populate_opened_fs(svn_fs_t *fs, apr_pool_t *scratch_pool) |
| { |
| SVN_ERR(svn_fs_base__populate_uuid(fs, scratch_pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| base_create(svn_fs_t *fs, |
| const char *path, |
| svn_mutex__t *common_pool_lock, |
| apr_pool_t *scratch_pool, |
| apr_pool_t *common_pool) |
| { |
| int format = SVN_FS_BASE__FORMAT_NUMBER; |
| svn_error_t *svn_err; |
| |
| /* See if compatibility with older versions was explicitly requested. */ |
| if (fs->config) |
| { |
| svn_version_t *compatible_version; |
| SVN_ERR(svn_fs__compatible_version(&compatible_version, fs->config, |
| scratch_pool)); |
| |
| /* select format number */ |
| switch(compatible_version->minor) |
| { |
| case 0: |
| case 1: |
| case 2: |
| case 3: format = 1; |
| break; |
| |
| case 4: format = 2; |
| break; |
| |
| case 5: format = 3; |
| break; |
| |
| default:format = SVN_FS_BASE__FORMAT_NUMBER; |
| } |
| } |
| |
| /* Create the environment and databases. */ |
| svn_err = open_databases(fs, TRUE, format, path); |
| if (svn_err) goto error; |
| |
| /* Initialize the DAG subsystem. */ |
| svn_err = svn_fs_base__dag_init_fs(fs); |
| if (svn_err) goto error; |
| |
| /* This filesystem is ready. Stamp it with a format number. */ |
| svn_err = svn_io_write_version_file(svn_dirent_join(fs->path, FORMAT_FILE, |
| scratch_pool), |
| format, scratch_pool); |
| if (svn_err) goto error; |
| |
| ((base_fs_data_t *) fs->fsap_data)->format = format; |
| |
| SVN_ERR(populate_opened_fs(fs, scratch_pool)); |
| return SVN_NO_ERROR; |
| |
| error: |
| return svn_error_compose_create(svn_err, |
| svn_error_trace(cleanup_fs(fs))); |
| } |
| |
| |
| /* Gaining access to an existing Berkeley DB-based filesystem. */ |
| |
| svn_error_t * |
| svn_fs_base__test_required_feature_format(svn_fs_t *fs, |
| const char *feature, |
| int requires) |
| { |
| base_fs_data_t *bfd = fs->fsap_data; |
| if (bfd->format < requires) |
| return svn_error_createf |
| (SVN_ERR_UNSUPPORTED_FEATURE, NULL, |
| _("The '%s' feature requires version %d of the filesystem schema; " |
| "filesystem '%s' uses only version %d"), |
| feature, requires, fs->path, bfd->format); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format |
| number is not the same as the format number supported by this |
| Subversion. */ |
| static svn_error_t * |
| check_format(int format) |
| { |
| /* We currently support any format less than the compiled format number |
| simultaneously. */ |
| if (format <= SVN_FS_BASE__FORMAT_NUMBER) |
| return SVN_NO_ERROR; |
| |
| return svn_error_createf( |
| SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL, |
| _("Expected FS format '%d'; found format '%d'"), |
| SVN_FS_BASE__FORMAT_NUMBER, format); |
| } |
| |
| static svn_error_t * |
| base_open(svn_fs_t *fs, |
| const char *path, |
| svn_mutex__t *common_pool_lock, |
| apr_pool_t *scratch_pool, |
| apr_pool_t *common_pool) |
| { |
| int format; |
| svn_error_t *svn_err; |
| svn_boolean_t write_format_file = FALSE; |
| |
| /* Read the FS format number. */ |
| svn_err = svn_io_read_version_file(&format, |
| svn_dirent_join(path, FORMAT_FILE, |
| scratch_pool), |
| scratch_pool); |
| if (svn_err && APR_STATUS_IS_ENOENT(svn_err->apr_err)) |
| { |
| /* Pre-1.2 filesystems did not have a format file (you could say |
| they were format "0"), so they get upgraded on the fly. |
| However, we stopped "upgrading on the fly" in 1.5, so older |
| filesystems should only be bumped to 1.3, which is format "1". */ |
| svn_error_clear(svn_err); |
| svn_err = SVN_NO_ERROR; |
| format = 1; |
| write_format_file = TRUE; |
| } |
| else if (svn_err) |
| goto error; |
| |
| /* Create the environment and databases. */ |
| svn_err = open_databases(fs, FALSE, format, path); |
| if (svn_err) goto error; |
| |
| ((base_fs_data_t *) fs->fsap_data)->format = format; |
| SVN_ERR(check_format(format)); |
| |
| /* If we lack a format file, write one. */ |
| if (write_format_file) |
| { |
| svn_err = svn_io_write_version_file(svn_dirent_join(path, FORMAT_FILE, |
| scratch_pool), |
| format, scratch_pool); |
| if (svn_err) goto error; |
| } |
| |
| SVN_ERR(populate_opened_fs(fs, scratch_pool)); |
| return SVN_NO_ERROR; |
| |
| error: |
| return svn_error_compose_create(svn_err, |
| svn_error_trace(cleanup_fs(fs))); |
| } |
| |
| |
| /* Running recovery on a Berkeley DB-based filesystem. */ |
| |
| |
| /* Recover a database at PATH. Perform catastrophic recovery if FATAL |
| is TRUE. Use POOL for temporary allocation. */ |
| static svn_error_t * |
| bdb_recover(const char *path, svn_boolean_t fatal, apr_pool_t *pool) |
| { |
| bdb_env_baton_t *bdb; |
| |
| /* 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 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. */ |
| |
| /* Note that since we're using a private environment, we should |
| /not/ initialize locking. We want the environment files to go |
| away. */ |
| |
| SVN_ERR(svn_fs_bdb__open(&bdb, path, |
| ((fatal ? DB_RECOVER_FATAL : DB_RECOVER) |
| | SVN_BDB_PRIVATE_ENV_FLAGS), |
| 0666, pool)); |
| return svn_fs_bdb__close(bdb); |
| } |
| |
| static svn_error_t * |
| base_open_for_recovery(svn_fs_t *fs, |
| const char *path, |
| svn_mutex__t *common_pool_lock, |
| apr_pool_t *pool, |
| apr_pool_t *common_pool) |
| { |
| /* Just stash the path in the fs pointer - it's all we really need. */ |
| fs->path = apr_pstrdup(fs->pool, path); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| base_upgrade(svn_fs_t *fs, |
| const char *path, |
| svn_fs_upgrade_notify_t notify_func, |
| void *notify_baton, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_mutex__t *common_pool_lock, |
| apr_pool_t *pool, |
| apr_pool_t *common_pool) |
| { |
| const char *version_file_path; |
| int old_format_number; |
| svn_error_t *err; |
| |
| version_file_path = svn_dirent_join(path, FORMAT_FILE, pool); |
| |
| /* Read the old number so we've got it on hand later on. */ |
| err = svn_io_read_version_file(&old_format_number, version_file_path, pool); |
| if (err && APR_STATUS_IS_ENOENT(err->apr_err)) |
| { |
| /* Pre-1.2 filesystems do not have a 'format' file. */ |
| old_format_number = 0; |
| svn_error_clear(err); |
| err = SVN_NO_ERROR; |
| } |
| SVN_ERR(err); |
| SVN_ERR(check_format(old_format_number)); |
| |
| /* Bump the format file's stored version number. */ |
| SVN_ERR(svn_io_write_version_file(version_file_path, |
| SVN_FS_BASE__FORMAT_NUMBER, pool)); |
| if (notify_func) |
| SVN_ERR(notify_func(notify_baton, SVN_FS_BASE__FORMAT_NUMBER, |
| svn_fs_upgrade_format_bumped, pool)); |
| |
| /* Check and see if we need to record the "bump" revision. */ |
| if (old_format_number < SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT) |
| { |
| apr_pool_t *subpool = svn_pool_create(pool); |
| svn_revnum_t youngest_rev; |
| const char *value; |
| |
| /* Open the filesystem in a subpool (so we can control its |
| closure) and do our fiddling. |
| |
| NOTE: By using base_open() here instead of open_databases(), |
| we will end up re-reading the format file that we just wrote. |
| But it's better to use the existing encapsulation of "opening |
| the filesystem" rather than duplicating (or worse, partially |
| duplicating) that logic here. */ |
| SVN_ERR(base_open(fs, path, common_pool_lock, subpool, common_pool)); |
| |
| /* Fetch the youngest rev, and record it */ |
| SVN_ERR(svn_fs_base__youngest_rev(&youngest_rev, fs, subpool)); |
| value = apr_psprintf(subpool, "%ld", youngest_rev); |
| SVN_ERR(svn_fs_base__miscellaneous_set |
| (fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE, |
| value, subpool)); |
| svn_pool_destroy(subpool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| base_verify(svn_fs_t *fs, const char *path, |
| svn_revnum_t start, |
| svn_revnum_t end, |
| svn_fs_progress_notify_func_t notify_func, |
| void *notify_baton, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_mutex__t *common_pool_lock, |
| apr_pool_t *pool, |
| apr_pool_t *common_pool) |
| { |
| /* Verifying is currently a no op for BDB. */ |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| base_bdb_recover(svn_fs_t *fs, |
| svn_cancel_func_t cancel_func, void *cancel_baton, |
| apr_pool_t *pool) |
| { |
| /* The fs pointer is a fake created in base_open_for_recovery above. |
| We only care about the path. */ |
| return bdb_recover(fs->path, FALSE, pool); |
| } |
| |
| static svn_error_t * |
| base_bdb_pack(svn_fs_t *fs, |
| const char *path, |
| svn_fs_pack_notify_t notify_func, |
| void *notify_baton, |
| svn_cancel_func_t cancel, |
| void *cancel_baton, |
| svn_mutex__t *common_pool_lock, |
| apr_pool_t *pool, |
| apr_pool_t *common_pool) |
| { |
| /* Packing is currently a no op for BDB. */ |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* Running the 'archive' command on a Berkeley DB-based filesystem. */ |
| |
| |
| static svn_error_t * |
| base_bdb_logfiles(apr_array_header_t **logfiles, |
| const char *path, |
| svn_boolean_t only_unused, |
| apr_pool_t *pool) |
| { |
| bdb_env_baton_t *bdb; |
| char **filelist; |
| char **filename; |
| u_int32_t flags = only_unused ? 0 : DB_ARCH_LOG; |
| |
| *logfiles = apr_array_make(pool, 4, sizeof(const char *)); |
| |
| SVN_ERR(svn_fs_bdb__open(&bdb, path, |
| SVN_BDB_STANDARD_ENV_FLAGS, |
| 0666, pool)); |
| SVN_BDB_ERR(bdb, bdb->env->log_archive(bdb->env, &filelist, flags)); |
| |
| if (filelist == NULL) |
| return svn_fs_bdb__close(bdb); |
| |
| for (filename = filelist; *filename != NULL; ++filename) |
| { |
| APR_ARRAY_PUSH(*logfiles, const char *) = apr_pstrdup(pool, *filename); |
| } |
| |
| free(filelist); |
| |
| return svn_fs_bdb__close(bdb); |
| } |
| |
| |
| |
| /* Copying a live Berkeley DB-base filesystem. */ |
| |
| /** |
| * Delete all unused log files from DBD environment at @a live_path that exist |
| * in @a backup_path. |
| */ |
| static svn_error_t * |
| svn_fs_base__clean_logs(const char *live_path, |
| const char *backup_path, |
| apr_pool_t *pool) |
| { |
| apr_array_header_t *logfiles; |
| |
| SVN_ERR(base_bdb_logfiles(&logfiles, |
| live_path, |
| TRUE, /* Only unused logs */ |
| pool)); |
| |
| { /* Process unused logs from live area */ |
| int idx; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| |
| /* Process log files. */ |
| for (idx = 0; idx < logfiles->nelts; idx++) |
| { |
| const char *log_file = APR_ARRAY_IDX(logfiles, idx, const char *); |
| const char *live_log_path; |
| const char *backup_log_path; |
| |
| svn_pool_clear(subpool); |
| live_log_path = svn_dirent_join(live_path, log_file, subpool); |
| backup_log_path = svn_dirent_join(backup_path, log_file, subpool); |
| |
| { /* Compare files. No point in using MD5 and wasting CPU cycles as we |
| got full copies of both logs */ |
| |
| svn_boolean_t files_match = FALSE; |
| svn_node_kind_t kind; |
| |
| /* Check to see if there is a corresponding log file in the backup |
| directory */ |
| SVN_ERR(svn_io_check_path(backup_log_path, &kind, pool)); |
| |
| /* If the copy of the log exists, compare them */ |
| if (kind == svn_node_file) |
| SVN_ERR(svn_io_files_contents_same_p(&files_match, |
| live_log_path, |
| backup_log_path, |
| subpool)); |
| |
| /* If log files do not match, go to the next log file. */ |
| if (!files_match) |
| continue; |
| } |
| |
| SVN_ERR(svn_io_remove_file2(live_log_path, FALSE, subpool)); |
| } |
| |
| svn_pool_destroy(subpool); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* DB_ENV->get_flags() and DB->get_pagesize() don't exist prior to |
| Berkeley DB 4.2. */ |
| #if SVN_BDB_VERSION_AT_LEAST(4, 2) |
| |
| /* Open the BDB environment at PATH and compare its configuration |
| flags with FLAGS. If every flag in FLAGS is set in the |
| environment, then set *MATCH to true. Else set *MATCH to false. */ |
| static svn_error_t * |
| check_env_flags(svn_boolean_t *match, |
| u_int32_t flags, |
| const char *path, |
| apr_pool_t *pool) |
| { |
| bdb_env_baton_t *bdb; |
| #if SVN_BDB_VERSION_AT_LEAST(4, 7) |
| int flag_state; |
| #else |
| u_int32_t envflags; |
| #endif |
| |
| SVN_ERR(svn_fs_bdb__open(&bdb, path, |
| SVN_BDB_STANDARD_ENV_FLAGS, |
| 0666, pool)); |
| #if SVN_BDB_VERSION_AT_LEAST(4, 7) |
| SVN_BDB_ERR(bdb, bdb->env->log_get_config(bdb->env, flags, &flag_state)); |
| #else |
| SVN_BDB_ERR(bdb, bdb->env->get_flags(bdb->env, &envflags)); |
| #endif |
| |
| SVN_ERR(svn_fs_bdb__close(bdb)); |
| |
| #if SVN_BDB_VERSION_AT_LEAST(4, 7) |
| if (flag_state == 0) |
| #else |
| if (flags & envflags) |
| #endif |
| *match = TRUE; |
| else |
| *match = FALSE; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Set *PAGESIZE to the size of pages used to hold items in the |
| database environment located at PATH. |
| */ |
| static svn_error_t * |
| get_db_pagesize(u_int32_t *pagesize, |
| const char *path, |
| apr_pool_t *pool) |
| { |
| bdb_env_baton_t *bdb; |
| DB *nodes_table; |
| |
| SVN_ERR(svn_fs_bdb__open(&bdb, path, |
| SVN_BDB_STANDARD_ENV_FLAGS, |
| 0666, pool)); |
| |
| /* ### We're only asking for the pagesize on the 'nodes' table. |
| Is this enough? We never call DB->set_pagesize() on any of |
| our tables, so presumably BDB is using the same default |
| pagesize for all our databases, right? */ |
| SVN_BDB_ERR(bdb, svn_fs_bdb__open_nodes_table(&nodes_table, bdb->env, |
| FALSE)); |
| SVN_BDB_ERR(bdb, nodes_table->get_pagesize(nodes_table, pagesize)); |
| SVN_BDB_ERR(bdb, nodes_table->close(nodes_table, 0)); |
| |
| return svn_fs_bdb__close(bdb); |
| } |
| #endif /* SVN_BDB_VERSION_AT_LEAST(4, 2) */ |
| |
| |
| /* Copy FILENAME from SRC_DIR to DST_DIR in byte increments of size |
| CHUNKSIZE. The read/write buffer of size CHUNKSIZE will be |
| allocated in POOL. If ALLOW_MISSING is set, we won't make a fuss |
| if FILENAME isn't found in SRC_DIR; otherwise, we will. */ |
| static svn_error_t * |
| copy_db_file_safely(const char *src_dir, |
| const char *dst_dir, |
| const char *filename, |
| u_int32_t chunksize, |
| svn_boolean_t allow_missing, |
| apr_pool_t *pool) |
| { |
| apr_file_t *s = NULL, *d = NULL; /* init to null important for APR */ |
| const char *file_src_path = svn_dirent_join(src_dir, filename, pool); |
| const char *file_dst_path = svn_dirent_join(dst_dir, filename, pool); |
| svn_error_t *err; |
| char *buf; |
| |
| /* Open source file. If it's missing and that's allowed, there's |
| nothing more to do here. */ |
| err = svn_io_file_open(&s, file_src_path, |
| (APR_READ | APR_LARGEFILE), |
| APR_OS_DEFAULT, pool); |
| if (err && APR_STATUS_IS_ENOENT(err->apr_err) && allow_missing) |
| { |
| svn_error_clear(err); |
| return SVN_NO_ERROR; |
| } |
| SVN_ERR(err); |
| |
| /* Open destination file. */ |
| SVN_ERR(svn_io_file_open(&d, file_dst_path, (APR_WRITE | APR_CREATE | |
| APR_LARGEFILE), |
| APR_OS_DEFAULT, pool)); |
| |
| /* Allocate our read/write buffer. */ |
| buf = apr_palloc(pool, chunksize); |
| |
| /* Copy bytes till the cows come home. */ |
| while (1) |
| { |
| apr_size_t bytes_this_time = chunksize; |
| svn_error_t *read_err, *write_err; |
| |
| /* Read 'em. */ |
| if ((read_err = svn_io_file_read(s, buf, &bytes_this_time, pool))) |
| { |
| if (APR_STATUS_IS_EOF(read_err->apr_err)) |
| svn_error_clear(read_err); |
| else |
| { |
| svn_error_clear(svn_io_file_close(s, pool)); |
| svn_error_clear(svn_io_file_close(d, pool)); |
| return read_err; |
| } |
| } |
| |
| /* Write 'em. */ |
| if ((write_err = svn_io_file_write_full(d, buf, bytes_this_time, NULL, |
| pool))) |
| { |
| svn_error_clear(svn_io_file_close(s, pool)); |
| svn_error_clear(svn_io_file_close(d, pool)); |
| return write_err; |
| } |
| |
| /* read_err is either NULL, or a dangling pointer - but it is only a |
| dangling pointer if it used to be an EOF error. */ |
| if (read_err) |
| { |
| SVN_ERR(svn_io_file_close(s, pool)); |
| SVN_ERR(svn_io_file_close(d, pool)); |
| break; /* got EOF on read, all files closed, all done. */ |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| |
| static svn_error_t * |
| base_hotcopy(svn_fs_t *src_fs, |
| svn_fs_t *dst_fs, |
| const char *src_path, |
| const char *dest_path, |
| svn_boolean_t clean_logs, |
| svn_boolean_t incremental, |
| svn_fs_hotcopy_notify_t notify_func, |
| void *notify_baton, |
| svn_cancel_func_t cancel_func, |
| void *cancel_baton, |
| svn_mutex__t *common_pool_lock, |
| apr_pool_t *pool, |
| apr_pool_t *common_pool) |
| { |
| svn_error_t *err; |
| u_int32_t pagesize; |
| svn_boolean_t log_autoremove = FALSE; |
| int format; |
| |
| if (incremental) |
| return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, |
| _("BDB repositories do not support incremental " |
| "hotcopy")); |
| |
| /* Check the FS format number to be certain that we know how to |
| hotcopy this FS. Pre-1.2 filesystems did not have a format file (you |
| could say they were format "0"), so we will error here. This is not |
| optimal, but since this has been the case since 1.2.0, and no one has |
| complained, it apparently isn't much of a concern. (We did not check |
| the 'format' file in 1.2.x, but we did blindly try to copy 'locks', |
| which would have errored just the same.) */ |
| SVN_ERR(svn_io_read_version_file( |
| &format, svn_dirent_join(src_path, FORMAT_FILE, pool), pool)); |
| SVN_ERR(check_format(format)); |
| |
| /* If using Berkeley DB 4.2 or later, note whether the DB_LOG_AUTO_REMOVE |
| feature is on. If it is, we have a potential race condition: |
| another process might delete a logfile while we're in the middle |
| of copying all the logfiles. (This is not a huge deal; at worst, |
| the hotcopy fails with a file-not-found error.) */ |
| #if SVN_BDB_VERSION_AT_LEAST(4, 2) |
| err = check_env_flags(&log_autoremove, |
| #if SVN_BDB_VERSION_AT_LEAST(4, 7) |
| DB_LOG_AUTO_REMOVE, |
| /* DB_LOG_AUTO_REMOVE was named DB_LOG_AUTOREMOVE before Berkeley DB 4.7. */ |
| #else |
| DB_LOG_AUTOREMOVE, |
| #endif |
| src_path, pool); |
| #endif |
| SVN_ERR(err); |
| |
| /* Copy the DB_CONFIG file. */ |
| SVN_ERR(svn_io_dir_file_copy(src_path, dest_path, "DB_CONFIG", pool)); |
| |
| /* In order to copy the database files safely and atomically, we |
| must copy them in chunks which are multiples of the page-size |
| used by BDB. See sleepycat docs for details, or svn issue #1818. */ |
| #if SVN_BDB_VERSION_AT_LEAST(4, 2) |
| SVN_ERR(get_db_pagesize(&pagesize, src_path, pool)); |
| if (pagesize < SVN__STREAM_CHUNK_SIZE) |
| { |
| /* use the largest multiple of BDB pagesize we can. */ |
| int multiple = SVN__STREAM_CHUNK_SIZE / pagesize; |
| pagesize *= multiple; |
| } |
| #else |
| /* default to 128K chunks, which should be safe. |
| BDB almost certainly uses a power-of-2 pagesize. */ |
| pagesize = (4096 * 32); |
| #endif |
| |
| /* Copy the databases. */ |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "nodes", pagesize, FALSE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "transactions", pagesize, FALSE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "revisions", pagesize, FALSE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "copies", pagesize, FALSE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "changes", pagesize, FALSE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "representations", pagesize, FALSE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "strings", pagesize, FALSE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "uuids", pagesize, TRUE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "locks", pagesize, TRUE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "lock-tokens", pagesize, TRUE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "node-origins", pagesize, TRUE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "checksum-reps", pagesize, TRUE, pool)); |
| SVN_ERR(copy_db_file_safely(src_path, dest_path, |
| "miscellaneous", pagesize, TRUE, pool)); |
| |
| { |
| apr_array_header_t *logfiles; |
| int idx; |
| apr_pool_t *subpool; |
| |
| SVN_ERR(base_bdb_logfiles(&logfiles, |
| src_path, |
| FALSE, /* All logs */ |
| pool)); |
| |
| /* Process log files. */ |
| subpool = svn_pool_create(pool); |
| for (idx = 0; idx < logfiles->nelts; idx++) |
| { |
| svn_pool_clear(subpool); |
| err = svn_io_dir_file_copy(src_path, dest_path, |
| APR_ARRAY_IDX(logfiles, idx, |
| const char *), |
| subpool); |
| if (err) |
| { |
| if (log_autoremove) |
| return |
| svn_error_quick_wrap |
| (err, |
| _("Error copying logfile; the DB_LOG_AUTOREMOVE feature\n" |
| "may be interfering with the hotcopy algorithm. If\n" |
| "the problem persists, try deactivating this feature\n" |
| "in DB_CONFIG")); |
| else |
| return svn_error_trace(err); |
| } |
| } |
| svn_pool_destroy(subpool); |
| } |
| |
| /* Since this is a copy we will have exclusive access to the repository. */ |
| err = bdb_recover(dest_path, TRUE, pool); |
| if (err) |
| { |
| if (log_autoremove) |
| return |
| svn_error_quick_wrap |
| (err, |
| _("Error running catastrophic recovery on hotcopy; the\n" |
| "DB_LOG_AUTOREMOVE feature may be interfering with the\n" |
| "hotcopy algorithm. If the problem persists, try deactivating\n" |
| "this feature in DB_CONFIG")); |
| else |
| return svn_error_trace(err); |
| } |
| |
| /* Only now that the hotcopied filesystem is complete, |
| stamp it with a format file. */ |
| SVN_ERR(svn_io_write_version_file( |
| svn_dirent_join(dest_path, FORMAT_FILE, pool), format, pool)); |
| |
| if (clean_logs) |
| SVN_ERR(svn_fs_base__clean_logs(src_path, dest_path, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* Deleting a Berkeley DB-based filesystem. */ |
| |
| |
| static svn_error_t * |
| base_delete_fs(const char *path, |
| apr_pool_t *pool) |
| { |
| /* First, use the Berkeley DB library function to remove any shared |
| memory segments. */ |
| SVN_ERR(svn_fs_bdb__remove(path, pool)); |
| |
| /* Remove the environment directory. */ |
| return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool); |
| } |
| |
| static const svn_version_t * |
| base_version(void) |
| { |
| SVN_VERSION_BODY; |
| } |
| |
| static const char * |
| base_get_description(void) |
| { |
| return _("Module for working with a Berkeley DB repository."); |
| } |
| |
| static svn_error_t * |
| base_set_svn_fs_open(svn_fs_t *fs, |
| svn_error_t *(*svn_fs_open_)(svn_fs_t **, |
| const char *, |
| apr_hash_t *, |
| apr_pool_t *, |
| apr_pool_t *)) |
| { |
| return SVN_NO_ERROR; |
| } |
| |
| |
| /* Base FS library vtable, used by the FS loader library. */ |
| static fs_library_vtable_t library_vtable = { |
| base_version, |
| base_create, |
| base_open, |
| base_open_for_recovery, |
| base_upgrade, |
| base_verify, |
| base_delete_fs, |
| base_hotcopy, |
| base_get_description, |
| base_bdb_recover, |
| base_bdb_pack, |
| base_bdb_logfiles, |
| svn_fs_base__id_parse, |
| base_set_svn_fs_open, |
| NULL /* info_fsap_dup */, |
| NULL /* ioctl */ |
| }; |
| |
| svn_error_t * |
| svn_fs_base__init(const svn_version_t *loader_version, |
| fs_library_vtable_t **vtable, apr_pool_t* common_pool) |
| { |
| static const svn_version_checklist_t checklist[] = |
| { |
| { "svn_subr", svn_subr_version }, |
| { "svn_delta", svn_delta_version }, |
| { "svn_fs_util", svn_fs_util__version }, |
| { NULL, NULL } |
| }; |
| |
| /* Simplified version check to make sure we can safely use the |
| VTABLE parameter. The FS loader does a more exhaustive check. */ |
| if (loader_version->major != SVN_VER_MAJOR) |
| return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL, |
| _("Unsupported FS loader version (%d) for bdb"), |
| loader_version->major); |
| SVN_ERR(svn_ver_check_list2(base_version(), checklist, svn_ver_equal)); |
| SVN_ERR(check_bdb_version()); |
| SVN_ERR(svn_fs_bdb__init(common_pool)); |
| |
| *vtable = &library_vtable; |
| return SVN_NO_ERROR; |
| } |