blob: 7d6fb4102ca62152530f0113060618c5f60ce02d [file] [log] [blame]
/* rep-sharing.c --- the rep-sharing cache for fsfs
*
* ====================================================================
* 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 "cached_data.h"
#include "fs_fs.h"
#include "fs.h"
#include "rep-cache.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_fs__open_rep_cache().
Implements svn_atomic__init_once().init_func.
*/
static svn_error_t *
open_rep_cache(void *baton,
apr_pool_t *pool)
{
svn_fs_t *fs = baton;
fs_fs_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, 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_fs__exists_rep_cache(&exists, fs, pool));
if (!exists)
{
const char *current = svn_fs_fs__path_current(fs, pool);
svn_error_t *err = svn_io_file_create_empty(db_path, 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, pool));
}
}
#endif
SVN_ERR(svn_sqlite__open(&sdb, db_path,
svn_sqlite__mode_rwcreate, statements,
0, NULL, 0,
fs->pool, pool));
SVN_SQLITE__ERR_CLOSE(svn_sqlite__read_schema_version(&version, sdb, pool),
sdb);
if (version < REP_CACHE_SCHEMA_FORMAT)
{
/* Must be 0 -- an uninitialized (no schema) database. Create
the schema. Results in schema version of 1. */
SVN_SQLITE__ERR_CLOSE(svn_sqlite__exec_statements(sdb,
STMT_CREATE_SCHEMA),
sdb);
}
/* 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_fs__open_rep_cache(svn_fs_t *fs,
apr_pool_t *pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
svn_error_t *err = svn_atomic__init_once(&ffd->rep_cache_db_opened,
open_rep_cache, fs, pool);
return svn_error_quick_wrap(err, _("Couldn't open rep-cache database"));
}
svn_error_t *
svn_fs_fs__close_rep_cache(svn_fs_t *fs)
{
fs_fs_data_t *ffd = fs->fsap_data;
if (ffd->rep_cache_db)
{
SVN_ERR(svn_sqlite__close(ffd->rep_cache_db));
ffd->rep_cache_db = NULL;
ffd->rep_cache_db_opened = 0;
}
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__exists_rep_cache(svn_boolean_t *exists,
svn_fs_t *fs, apr_pool_t *pool)
{
svn_node_kind_t kind;
SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, pool),
&kind, pool));
*exists = (kind != svn_node_none);
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
svn_revnum_t start,
svn_revnum_t end,
svn_error_t *(*walker)(representation_t *,
void *,
svn_fs_t *,
apr_pool_t *),
void *walker_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
{
fs_fs_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(pool);
/* Don't check ffd->rep_sharing_allowed. */
SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
if (! ffd->rep_cache_db)
SVN_ERR(svn_fs_fs__open_rep_cache(fs, 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_fs__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)
{
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 representation_t. */
rep = apr_pcalloc(iterpool, sizeof(*rep));
svn_fs_fs__id_txn_reset(&rep->txn_id);
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->revision = svn_sqlite__column_revnum(stmt, 1);
rep->item_index = 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_fs__get_rep_reference(representation_t **rep_p,
svn_fs_t *fs,
svn_checksum_t *checksum,
apr_pool_t *pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
svn_sqlite__stmt_t *stmt;
svn_boolean_t have_row;
representation_t *rep;
SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
if (! ffd->rep_cache_db)
SVN_ERR(svn_fs_fs__open_rep_cache(fs, 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, pool)));
SVN_ERR(svn_sqlite__step(&have_row, stmt));
if (have_row)
{
rep = apr_pcalloc(pool, sizeof(*rep));
svn_fs_fs__id_txn_reset(&(rep->txn_id));
memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
rep->has_sha1 = TRUE;
rep->revision = svn_sqlite__column_revnum(stmt, 0);
rep->item_index = 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)
{
svn_error_t *err;
SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, rep, pool));
/* Check that REP refers to a revision that exists in FS. */
err = svn_fs_fs__ensure_revision_exists(rep->revision, fs, 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,
pool));
}
*rep_p = rep;
return SVN_NO_ERROR;
}
svn_error_t *
svn_fs_fs__set_rep_reference(svn_fs_t *fs,
representation_t *rep,
apr_pool_t *pool)
{
fs_fs_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_fs__open_rep_cache(fs, 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, pool),
(apr_int64_t) rep->revision,
(apr_int64_t) rep->item_index,
(apr_int64_t) rep->size,
(apr_int64_t) rep->expanded_size));
err = svn_sqlite__insert(NULL, stmt);
if (err)
{
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_fs__get_rep_reference(&old_rep, fs, &checksum, 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_fs__del_rep_reference(svn_fs_t *fs,
svn_revnum_t youngest,
apr_pool_t *pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
svn_sqlite__stmt_t *stmt;
SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
if (! ffd->rep_cache_db)
SVN_ERR(svn_fs_fs__open_rep_cache(fs, 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)
{
fs_fs_data_t *ffd = fs->fsap_data;
if (! ffd->rep_cache_db)
SVN_ERR(svn_fs_fs__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)
{
fs_fs_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_fs__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));
}