1 /* rep-sharing.c --- the rep-sharing cache for fsx
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
23 #include "svn_pools.h"
25 #include "svn_private_config.h"
29 #include "rep-cache.h"
31 #include "../libsvn_fs/fs-loader.h"
35 #include "private/svn_sqlite.h"
37 #include "rep-cache-db.h"
39 /* A few magic values */
40 #define REP_CACHE_SCHEMA_FORMAT 1
42 REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements);
46 /** Helper functions. **/
47 static APR_INLINE const char *
48 path_rep_cache_db(const char *fs_path,
49 apr_pool_t *result_pool)
51 return svn_dirent_join(fs_path, REP_CACHE_DB_NAME, result_pool);
55 /** Library-private API's. **/
57 /* Body of svn_fs_x__open_rep_cache().
58 Implements svn_atomic__init_once().init_func.
61 open_rep_cache(void *baton,
62 apr_pool_t *scratch_pool)
65 svn_fs_x__data_t *ffd = fs->fsap_data;
66 svn_sqlite__db_t *sdb;
70 /* Open (or create) the sqlite database. It will be automatically
71 closed when fs->pool is destroyed. */
72 db_path = path_rep_cache_db(fs->path, scratch_pool);
75 /* We want to extend the permissions that apply to the repository
76 as a whole when creating a new rep cache and not simply default
80 SVN_ERR(svn_fs_x__exists_rep_cache(&exists, fs, scratch_pool));
83 const char *current = svn_fs_x__path_current(fs, scratch_pool);
84 svn_error_t *err = svn_io_file_create_empty(db_path, scratch_pool);
86 if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
88 return svn_error_trace(err);
90 /* Some other thread/process created the file. */
93 /* We created the file. */
94 SVN_ERR(svn_io_copy_perms(current, db_path, scratch_pool));
98 SVN_ERR(svn_sqlite__open(&sdb, db_path,
99 svn_sqlite__mode_rwcreate, statements,
101 fs->pool, scratch_pool));
103 SVN_SQLITE__ERR_CLOSE(svn_sqlite__read_schema_version(&version, sdb,
106 if (version < REP_CACHE_SCHEMA_FORMAT)
108 /* Must be 0 -- an uninitialized (no schema) database. Create
109 the schema. Results in schema version of 1. */
110 SVN_SQLITE__ERR_CLOSE(svn_sqlite__exec_statements(sdb,
115 /* This is used as a flag that the database is available so don't
117 ffd->rep_cache_db = sdb;
123 svn_fs_x__open_rep_cache(svn_fs_t *fs,
124 apr_pool_t *scratch_pool)
126 svn_fs_x__data_t *ffd = fs->fsap_data;
127 svn_error_t *err = svn_atomic__init_once(&ffd->rep_cache_db_opened,
128 open_rep_cache, fs, scratch_pool);
129 return svn_error_quick_wrapf(err,
130 _("Couldn't open rep-cache database '%s'"),
131 svn_dirent_local_style(
132 path_rep_cache_db(fs->path, scratch_pool),
137 svn_fs_x__close_rep_cache(svn_fs_t *fs)
139 svn_fs_x__data_t *ffd = fs->fsap_data;
141 if (ffd->rep_cache_db)
143 SVN_ERR(svn_sqlite__close(ffd->rep_cache_db));
144 ffd->rep_cache_db = NULL;
145 ffd->rep_cache_db_opened = 0;
152 svn_fs_x__exists_rep_cache(svn_boolean_t *exists,
154 apr_pool_t *scratch_pool)
156 svn_node_kind_t kind;
158 SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, scratch_pool),
159 &kind, scratch_pool));
161 *exists = (kind != svn_node_none);
166 svn_fs_x__walk_rep_reference(svn_fs_t *fs,
169 svn_error_t *(*walker)(svn_fs_x__representation_t *,
174 svn_cancel_func_t cancel_func,
176 apr_pool_t *scratch_pool)
178 svn_fs_x__data_t *ffd = fs->fsap_data;
179 svn_sqlite__stmt_t *stmt;
180 svn_boolean_t have_row;
183 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
185 if (! ffd->rep_cache_db)
186 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
188 /* Check global invariants. */
193 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
195 SVN_ERR(svn_sqlite__step(&have_row, stmt));
196 max = svn_sqlite__column_revnum(stmt, 0);
197 SVN_ERR(svn_sqlite__reset(stmt));
198 if (SVN_IS_VALID_REVNUM(max)) /* The rep-cache could be empty. */
199 SVN_ERR(svn_fs_x__ensure_revision_exists(max, fs, iterpool));
202 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
203 STMT_GET_REPS_FOR_RANGE));
204 SVN_ERR(svn_sqlite__bindf(stmt, "rr",
207 /* Walk the cache entries. */
208 SVN_ERR(svn_sqlite__step(&have_row, stmt));
211 svn_fs_x__representation_t *rep;
212 const char *sha1_digest;
214 svn_checksum_t *checksum;
216 /* Clear ITERPOOL occasionally. */
217 if (iterations++ % 16 == 0)
218 svn_pool_clear(iterpool);
220 /* Check for cancellation. */
223 err = cancel_func(cancel_baton);
225 return svn_error_compose_create(err, svn_sqlite__reset(stmt));
228 /* Construct a svn_fs_x__representation_t. */
229 rep = apr_pcalloc(iterpool, sizeof(*rep));
230 sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool);
231 err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
232 sha1_digest, iterpool);
234 return svn_error_compose_create(err, svn_sqlite__reset(stmt));
236 rep->has_sha1 = TRUE;
237 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
238 rep->id.change_set = svn_sqlite__column_revnum(stmt, 1);
239 rep->id.number = svn_sqlite__column_int64(stmt, 2);
240 rep->size = svn_sqlite__column_int64(stmt, 3);
241 rep->expanded_size = svn_sqlite__column_int64(stmt, 4);
244 err = walker(rep, walker_baton, fs, iterpool);
246 return svn_error_compose_create(err, svn_sqlite__reset(stmt));
248 SVN_ERR(svn_sqlite__step(&have_row, stmt));
251 SVN_ERR(svn_sqlite__reset(stmt));
252 svn_pool_destroy(iterpool);
258 /* This function's caller ignores most errors it returns.
259 If you extend this function, check the callsite to see if you have
260 to make it not-ignore additional error codes. */
262 svn_fs_x__get_rep_reference(svn_fs_x__representation_t **rep_p,
264 svn_checksum_t *checksum,
265 apr_pool_t *result_pool,
266 apr_pool_t *scratch_pool)
268 svn_fs_x__data_t *ffd = fs->fsap_data;
269 svn_sqlite__stmt_t *stmt;
270 svn_boolean_t have_row;
271 svn_fs_x__representation_t *rep;
273 SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
274 if (! ffd->rep_cache_db)
275 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
277 /* We only allow SHA1 checksums in this table. */
278 if (checksum->kind != svn_checksum_sha1)
279 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
280 _("Only SHA1 checksums can be used as keys in the "
281 "rep_cache table.\n"));
283 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP));
284 SVN_ERR(svn_sqlite__bindf(stmt, "s",
285 svn_checksum_to_cstring(checksum, scratch_pool)));
287 SVN_ERR(svn_sqlite__step(&have_row, stmt));
290 rep = apr_pcalloc(result_pool, sizeof(*rep));
291 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
292 rep->has_sha1 = TRUE;
293 rep->id.change_set = svn_sqlite__column_revnum(stmt, 0);
294 rep->id.number = svn_sqlite__column_int64(stmt, 1);
295 rep->size = svn_sqlite__column_int64(stmt, 2);
296 rep->expanded_size = svn_sqlite__column_int64(stmt, 3);
301 SVN_ERR(svn_sqlite__reset(stmt));
305 /* Check that REP refers to a revision that exists in FS. */
306 svn_revnum_t revision = svn_fs_x__get_revnum(rep->id.change_set);
307 svn_error_t *err = svn_fs_x__ensure_revision_exists(revision, fs,
310 return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
311 "Checksum '%s' in rep-cache is beyond HEAD",
312 svn_checksum_to_cstring_display(checksum, scratch_pool));
320 svn_fs_x__set_rep_reference(svn_fs_t *fs,
321 svn_fs_x__representation_t *rep,
322 apr_pool_t *scratch_pool)
324 svn_fs_x__data_t *ffd = fs->fsap_data;
325 svn_sqlite__stmt_t *stmt;
327 svn_checksum_t checksum;
328 checksum.kind = svn_checksum_sha1;
329 checksum.digest = rep->sha1_digest;
331 SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
332 if (! ffd->rep_cache_db)
333 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
335 /* We only allow SHA1 checksums in this table. */
337 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
338 _("Only SHA1 checksums can be used as keys in the "
339 "rep_cache table.\n"));
341 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_SET_REP));
342 SVN_ERR(svn_sqlite__bindf(stmt, "siiii",
343 svn_checksum_to_cstring(&checksum, scratch_pool),
344 (apr_int64_t) rep->id.change_set,
345 (apr_int64_t) rep->id.number,
346 (apr_int64_t) rep->size,
347 (apr_int64_t) rep->expanded_size));
349 err = svn_sqlite__insert(NULL, stmt);
352 svn_fs_x__representation_t *old_rep;
354 if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT)
355 return svn_error_trace(err);
357 svn_error_clear(err);
359 /* Constraint failed so the mapping for SHA1_CHECKSUM->REP
360 should exist. If so that's cool -- just do nothing. If not,
361 that's a red flag! */
362 SVN_ERR(svn_fs_x__get_rep_reference(&old_rep, fs, &checksum,
363 scratch_pool, scratch_pool));
367 /* Something really odd at this point, we failed to insert the
368 checksum AND failed to read an existing checksum. Do we need
378 svn_fs_x__del_rep_reference(svn_fs_t *fs,
379 svn_revnum_t youngest,
380 apr_pool_t *scratch_pool)
382 svn_fs_x__data_t *ffd = fs->fsap_data;
383 svn_sqlite__stmt_t *stmt;
385 if (! ffd->rep_cache_db)
386 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
388 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
389 STMT_DEL_REPS_YOUNGER_THAN_REV));
390 SVN_ERR(svn_sqlite__bindf(stmt, "r", youngest));
391 SVN_ERR(svn_sqlite__step_done(stmt));
396 /* Start a transaction to take an SQLite reserved lock that prevents
399 See unlock_rep_cache(). */
401 lock_rep_cache(svn_fs_t *fs,
404 svn_fs_x__data_t *ffd = fs->fsap_data;
406 if (! ffd->rep_cache_db)
407 SVN_ERR(svn_fs_x__open_rep_cache(fs, pool));
409 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP));
414 /* End the transaction started by lock_rep_cache(). */
416 unlock_rep_cache(svn_fs_t *fs,
419 svn_fs_x__data_t *ffd = fs->fsap_data;
421 SVN_ERR_ASSERT(ffd->rep_cache_db); /* was opened by lock_rep_cache() */
423 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_UNLOCK_REP));
429 svn_fs_x__with_rep_cache_lock(svn_fs_t *fs,
430 svn_error_t *(*body)(void *,
437 SVN_ERR(lock_rep_cache(fs, pool));
438 err = body(baton, pool);
439 return svn_error_compose_create(err, unlock_rep_cache(fs, pool));