summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_x/rep-cache.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_x/rep-cache.c')
-rw-r--r--subversion/libsvn_fs_x/rep-cache.c416
1 files changed, 416 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_x/rep-cache.c b/subversion/libsvn_fs_x/rep-cache.c
new file mode 100644
index 0000000..85e62a4
--- /dev/null
+++ b/subversion/libsvn_fs_x/rep-cache.c
@@ -0,0 +1,416 @@
+/* rep-sharing.c --- the rep-sharing cache for fsx
+ *
+ * ====================================================================
+ * 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 "svn_pools.h"
+
+#include "svn_private_config.h"
+
+#include "fs_x.h"
+#include "fs.h"
+#include "rep-cache.h"
+#include "util.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_path.h"
+
+#include "private/svn_sqlite.h"
+
+#include "rep-cache-db.h"
+
+/* A few magic values */
+#define REP_CACHE_SCHEMA_FORMAT 1
+
+REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements);
+
+
+
+/** Helper functions. **/
+static APR_INLINE const char *
+path_rep_cache_db(const char *fs_path,
+ apr_pool_t *result_pool)
+{
+ return svn_dirent_join(fs_path, REP_CACHE_DB_NAME, result_pool);
+}
+
+
+/** Library-private API's. **/
+
+/* Body of svn_fs_x__open_rep_cache().
+ Implements svn_atomic__init_once().init_func.
+ */
+static svn_error_t *
+open_rep_cache(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_t *fs = baton;
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ svn_sqlite__db_t *sdb;
+ const char *db_path;
+ int version;
+
+ /* Open (or create) the sqlite database. It will be automatically
+ closed when fs->pool is destroyed. */
+ db_path = path_rep_cache_db(fs->path, scratch_pool);
+#ifndef WIN32
+ {
+ /* We want to extend the permissions that apply to the repository
+ as a whole when creating a new rep cache and not simply default
+ to umask. */
+ svn_boolean_t exists;
+
+ SVN_ERR(svn_fs_x__exists_rep_cache(&exists, fs, scratch_pool));
+ if (!exists)
+ {
+ const char *current = svn_fs_x__path_current(fs, scratch_pool);
+ svn_error_t *err = svn_io_file_create_empty(db_path, scratch_pool);
+
+ if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
+ /* A real error. */
+ return svn_error_trace(err);
+ else if (err)
+ /* Some other thread/process created the file. */
+ svn_error_clear(err);
+ else
+ /* We created the file. */
+ SVN_ERR(svn_io_copy_perms(current, db_path, scratch_pool));
+ }
+ }
+#endif
+ SVN_ERR(svn_sqlite__open(&sdb, db_path,
+ svn_sqlite__mode_rwcreate, statements,
+ 0, NULL, 0,
+ fs->pool, scratch_pool));
+
+ SVN_ERR(svn_sqlite__read_schema_version(&version, sdb, scratch_pool));
+ if (version < REP_CACHE_SCHEMA_FORMAT)
+ {
+ /* Must be 0 -- an uninitialized (no schema) database. Create
+ the schema. Results in schema version of 1. */
+ SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_SCHEMA));
+ }
+
+ /* This is used as a flag that the database is available so don't
+ set it earlier. */
+ ffd->rep_cache_db = sdb;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__open_rep_cache(svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ svn_error_t *err = svn_atomic__init_once(&ffd->rep_cache_db_opened,
+ open_rep_cache, fs, scratch_pool);
+ return svn_error_quick_wrap(err, _("Couldn't open rep-cache database"));
+}
+
+svn_error_t *
+svn_fs_x__exists_rep_cache(svn_boolean_t *exists,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, scratch_pool),
+ &kind, scratch_pool));
+
+ *exists = (kind != svn_node_none);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__walk_rep_reference(svn_fs_t *fs,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_error_t *(*walker)(svn_fs_x__representation_t *,
+ void *,
+ svn_fs_t *,
+ apr_pool_t *),
+ void *walker_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+ int iterations = 0;
+
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ if (! ffd->rep_cache_db)
+ SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
+
+ /* Check global invariants. */
+ if (start == 0)
+ {
+ svn_revnum_t max;
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
+ STMT_GET_MAX_REV));
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ max = svn_sqlite__column_revnum(stmt, 0);
+ SVN_ERR(svn_sqlite__reset(stmt));
+ if (SVN_IS_VALID_REVNUM(max)) /* The rep-cache could be empty. */
+ SVN_ERR(svn_fs_x__ensure_revision_exists(max, fs, iterpool));
+ }
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
+ STMT_GET_REPS_FOR_RANGE));
+ SVN_ERR(svn_sqlite__bindf(stmt, "rr",
+ start, end));
+
+ /* Walk the cache entries. */
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ while (have_row)
+ {
+ svn_fs_x__representation_t *rep;
+ const char *sha1_digest;
+ svn_error_t *err;
+ svn_checksum_t *checksum;
+
+ /* Clear ITERPOOL occasionally. */
+ if (iterations++ % 16 == 0)
+ svn_pool_clear(iterpool);
+
+ /* Check for cancellation. */
+ if (cancel_func)
+ {
+ err = cancel_func(cancel_baton);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+ }
+
+ /* Construct a svn_fs_x__representation_t. */
+ rep = apr_pcalloc(iterpool, sizeof(*rep));
+ sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool);
+ err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
+ sha1_digest, iterpool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ rep->has_sha1 = TRUE;
+ memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
+ rep->id.change_set = svn_sqlite__column_revnum(stmt, 1);
+ rep->id.number = svn_sqlite__column_int64(stmt, 2);
+ rep->size = svn_sqlite__column_int64(stmt, 3);
+ rep->expanded_size = svn_sqlite__column_int64(stmt, 4);
+
+ /* Walk. */
+ err = walker(rep, walker_baton, fs, iterpool);
+ if (err)
+ return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ }
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This function's caller ignores most errors it returns.
+ If you extend this function, check the callsite to see if you have
+ to make it not-ignore additional error codes. */
+svn_error_t *
+svn_fs_x__get_rep_reference(svn_fs_x__representation_t **rep,
+ svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ svn_sqlite__stmt_t *stmt;
+ svn_boolean_t have_row;
+
+ SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
+ if (! ffd->rep_cache_db)
+ SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
+
+ /* We only allow SHA1 checksums in this table. */
+ if (checksum->kind != svn_checksum_sha1)
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
+ _("Only SHA1 checksums can be used as keys in the "
+ "rep_cache table.\n"));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP));
+ SVN_ERR(svn_sqlite__bindf(stmt, "s",
+ svn_checksum_to_cstring(checksum, scratch_pool)));
+
+ SVN_ERR(svn_sqlite__step(&have_row, stmt));
+ if (have_row)
+ {
+ *rep = apr_pcalloc(result_pool, sizeof(**rep));
+ memcpy((*rep)->sha1_digest, checksum->digest,
+ sizeof((*rep)->sha1_digest));
+ (*rep)->has_sha1 = TRUE;
+ (*rep)->id.change_set = svn_sqlite__column_revnum(stmt, 0);
+ (*rep)->id.number = svn_sqlite__column_int64(stmt, 1);
+ (*rep)->size = svn_sqlite__column_int64(stmt, 2);
+ (*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3);
+ }
+ else
+ *rep = NULL;
+
+ SVN_ERR(svn_sqlite__reset(stmt));
+
+ if (*rep)
+ {
+ /* Check that REP refers to a revision that exists in FS. */
+ svn_revnum_t revision = svn_fs_x__get_revnum((*rep)->id.change_set);
+ svn_error_t *err = svn_fs_x__ensure_revision_exists(revision, fs,
+ scratch_pool);
+ if (err)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
+ "Checksum '%s' in rep-cache is beyond HEAD",
+ svn_checksum_to_cstring_display(checksum, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__set_rep_reference(svn_fs_t *fs,
+ svn_fs_x__representation_t *rep,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ svn_sqlite__stmt_t *stmt;
+ svn_error_t *err;
+ svn_checksum_t checksum;
+ checksum.kind = svn_checksum_sha1;
+ checksum.digest = rep->sha1_digest;
+
+ SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
+ if (! ffd->rep_cache_db)
+ SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
+
+ /* We only allow SHA1 checksums in this table. */
+ if (! rep->has_sha1)
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
+ _("Only SHA1 checksums can be used as keys in the "
+ "rep_cache table.\n"));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_SET_REP));
+ SVN_ERR(svn_sqlite__bindf(stmt, "siiii",
+ svn_checksum_to_cstring(&checksum, scratch_pool),
+ (apr_int64_t) rep->id.change_set,
+ (apr_int64_t) rep->id.number,
+ (apr_int64_t) rep->size,
+ (apr_int64_t) rep->expanded_size));
+
+ err = svn_sqlite__insert(NULL, stmt);
+ if (err)
+ {
+ svn_fs_x__representation_t *old_rep;
+
+ if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+
+ /* Constraint failed so the mapping for SHA1_CHECKSUM->REP
+ should exist. If so that's cool -- just do nothing. If not,
+ that's a red flag! */
+ SVN_ERR(svn_fs_x__get_rep_reference(&old_rep, fs, &checksum,
+ scratch_pool, scratch_pool));
+
+ if (!old_rep)
+ {
+ /* Something really odd at this point, we failed to insert the
+ checksum AND failed to read an existing checksum. Do we need
+ to flag this? */
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_x__del_rep_reference(svn_fs_t *fs,
+ svn_revnum_t youngest,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ svn_sqlite__stmt_t *stmt;
+
+ if (! ffd->rep_cache_db)
+ SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
+
+ SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
+ STMT_DEL_REPS_YOUNGER_THAN_REV));
+ SVN_ERR(svn_sqlite__bindf(stmt, "r", youngest));
+ SVN_ERR(svn_sqlite__step_done(stmt));
+
+ return SVN_NO_ERROR;
+}
+
+/* Start a transaction to take an SQLite reserved lock that prevents
+ other writes.
+
+ See unlock_rep_cache(). */
+static svn_error_t *
+lock_rep_cache(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+
+ if (! ffd->rep_cache_db)
+ SVN_ERR(svn_fs_x__open_rep_cache(fs, pool));
+
+ SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP));
+
+ return SVN_NO_ERROR;
+}
+
+/* End the transaction started by lock_rep_cache(). */
+static svn_error_t *
+unlock_rep_cache(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+
+ SVN_ERR_ASSERT(ffd->rep_cache_db); /* was opened by lock_rep_cache() */
+
+ SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_UNLOCK_REP));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__with_rep_cache_lock(svn_fs_t *fs,
+ svn_error_t *(*body)(void *,
+ apr_pool_t *),
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+
+ SVN_ERR(lock_rep_cache(fs, pool));
+ err = body(baton, pool);
+ return svn_error_compose_create(err, unlock_rep_cache(fs, pool));
+}