1 /* rep-sharing.c --- the rep-sharing cache for fsfs
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"
30 #include "../libsvn_fs/fs-loader.h"
34 #include "private/svn_sqlite.h"
36 #include "rep-cache-db.h"
38 /* A few magic values */
39 #define REP_CACHE_SCHEMA_FORMAT 1
41 REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements);
45 /** Helper functions. **/
46 static APR_INLINE const char *
47 path_rep_cache_db(const char *fs_path,
48 apr_pool_t *result_pool)
50 return svn_dirent_join(fs_path, REP_CACHE_DB_NAME, result_pool);
53 #define SVN_ERR_CLOSE(x, db) do \
55 svn_error_t *svn__err = (x); \
57 return svn_error_compose_create(svn__err, svn_sqlite__close(db)); \
61 /** Library-private API's. **/
63 /* Body of svn_fs_fs__open_rep_cache().
64 Implements svn_atomic__init_once().init_func.
67 open_rep_cache(void *baton,
71 fs_fs_data_t *ffd = fs->fsap_data;
72 svn_sqlite__db_t *sdb;
76 /* Open (or create) the sqlite database. It will be automatically
77 closed when fs->pool is destroyed. */
78 db_path = path_rep_cache_db(fs->path, pool);
81 /* We want to extend the permissions that apply to the repository
82 as a whole when creating a new rep cache and not simply default
86 SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
89 const char *current = svn_fs_fs__path_current(fs, pool);
90 svn_error_t *err = svn_io_file_create_empty(db_path, pool);
92 if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
94 return svn_error_trace(err);
96 /* Some other thread/process created the file. */
99 /* We created the file. */
100 SVN_ERR(svn_io_copy_perms(current, db_path, pool));
104 SVN_ERR(svn_sqlite__open(&sdb, db_path,
105 svn_sqlite__mode_rwcreate, statements,
109 SVN_ERR_CLOSE(svn_sqlite__read_schema_version(&version, sdb, pool), sdb);
110 if (version < REP_CACHE_SCHEMA_FORMAT)
112 /* Must be 0 -- an uninitialized (no schema) database. Create
113 the schema. Results in schema version of 1. */
114 SVN_ERR_CLOSE(svn_sqlite__exec_statements(sdb, STMT_CREATE_SCHEMA), sdb);
117 /* This is used as a flag that the database is available so don't
119 ffd->rep_cache_db = sdb;
125 svn_fs_fs__open_rep_cache(svn_fs_t *fs,
128 fs_fs_data_t *ffd = fs->fsap_data;
129 svn_error_t *err = svn_atomic__init_once(&ffd->rep_cache_db_opened,
130 open_rep_cache, fs, pool);
131 return svn_error_quick_wrapf(err,
132 _("Couldn't open rep-cache database '%s'"),
133 svn_dirent_local_style(
134 path_rep_cache_db(fs->path, pool), pool));
138 svn_fs_fs__close_rep_cache(svn_fs_t *fs)
140 fs_fs_data_t *ffd = fs->fsap_data;
142 if (ffd->rep_cache_db)
144 SVN_ERR(svn_sqlite__close(ffd->rep_cache_db));
145 ffd->rep_cache_db = NULL;
146 ffd->rep_cache_db_opened = 0;
153 svn_fs_fs__exists_rep_cache(svn_boolean_t *exists,
154 svn_fs_t *fs, apr_pool_t *pool)
156 svn_node_kind_t kind;
158 SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, pool),
161 *exists = (kind != svn_node_none);
166 svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
169 svn_error_t *(*walker)(representation_t *,
174 svn_cancel_func_t cancel_func,
178 fs_fs_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(pool);
185 /* Don't check ffd->rep_sharing_allowed. */
186 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
188 if (! ffd->rep_cache_db)
189 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
191 /* Check global invariants. */
196 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
198 SVN_ERR(svn_sqlite__step(&have_row, stmt));
199 max = svn_sqlite__column_revnum(stmt, 0);
200 SVN_ERR(svn_sqlite__reset(stmt));
201 if (SVN_IS_VALID_REVNUM(max)) /* The rep-cache could be empty. */
202 SVN_ERR(svn_fs_fs__ensure_revision_exists(max, fs, iterpool));
205 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
206 STMT_GET_REPS_FOR_RANGE));
207 SVN_ERR(svn_sqlite__bindf(stmt, "rr",
210 /* Walk the cache entries. */
211 SVN_ERR(svn_sqlite__step(&have_row, stmt));
214 representation_t *rep;
215 const char *sha1_digest;
217 svn_checksum_t *checksum;
219 /* Clear ITERPOOL occasionally. */
220 if (iterations++ % 16 == 0)
221 svn_pool_clear(iterpool);
223 /* Check for cancellation. */
226 err = cancel_func(cancel_baton);
228 return svn_error_compose_create(err, svn_sqlite__reset(stmt));
231 /* Construct a representation_t. */
232 rep = apr_pcalloc(iterpool, sizeof(*rep));
233 svn_fs_fs__id_txn_reset(&rep->txn_id);
234 sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool);
235 err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
236 sha1_digest, iterpool);
238 return svn_error_compose_create(err, svn_sqlite__reset(stmt));
240 rep->has_sha1 = TRUE;
241 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
242 rep->revision = svn_sqlite__column_revnum(stmt, 1);
243 rep->item_index = svn_sqlite__column_int64(stmt, 2);
244 rep->size = svn_sqlite__column_int64(stmt, 3);
245 rep->expanded_size = svn_sqlite__column_int64(stmt, 4);
248 err = walker(rep, walker_baton, fs, iterpool);
250 return svn_error_compose_create(err, svn_sqlite__reset(stmt));
252 SVN_ERR(svn_sqlite__step(&have_row, stmt));
255 SVN_ERR(svn_sqlite__reset(stmt));
256 svn_pool_destroy(iterpool);
262 /* This function's caller ignores most errors it returns.
263 If you extend this function, check the callsite to see if you have
264 to make it not-ignore additional error codes. */
266 svn_fs_fs__get_rep_reference(representation_t **rep,
268 svn_checksum_t *checksum,
271 fs_fs_data_t *ffd = fs->fsap_data;
272 svn_sqlite__stmt_t *stmt;
273 svn_boolean_t have_row;
275 SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
276 if (! ffd->rep_cache_db)
277 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
279 /* We only allow SHA1 checksums in this table. */
280 if (checksum->kind != svn_checksum_sha1)
281 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
282 _("Only SHA1 checksums can be used as keys in the "
283 "rep_cache table.\n"));
285 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP));
286 SVN_ERR(svn_sqlite__bindf(stmt, "s",
287 svn_checksum_to_cstring(checksum, pool)));
289 SVN_ERR(svn_sqlite__step(&have_row, stmt));
292 *rep = apr_pcalloc(pool, sizeof(**rep));
293 svn_fs_fs__id_txn_reset(&(*rep)->txn_id);
294 memcpy((*rep)->sha1_digest, checksum->digest,
295 sizeof((*rep)->sha1_digest));
296 (*rep)->has_sha1 = TRUE;
297 (*rep)->revision = svn_sqlite__column_revnum(stmt, 0);
298 (*rep)->item_index = svn_sqlite__column_int64(stmt, 1);
299 (*rep)->size = svn_sqlite__column_int64(stmt, 2);
300 (*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3);
305 SVN_ERR(svn_sqlite__reset(stmt));
309 /* Check that REP refers to a revision that exists in FS. */
310 svn_error_t *err = svn_fs_fs__ensure_revision_exists((*rep)->revision,
313 return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
314 "Checksum '%s' in rep-cache is beyond HEAD",
315 svn_checksum_to_cstring_display(checksum,
323 svn_fs_fs__set_rep_reference(svn_fs_t *fs,
324 representation_t *rep,
327 fs_fs_data_t *ffd = fs->fsap_data;
328 svn_sqlite__stmt_t *stmt;
330 svn_checksum_t checksum;
331 checksum.kind = svn_checksum_sha1;
332 checksum.digest = rep->sha1_digest;
334 SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
335 if (! ffd->rep_cache_db)
336 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
338 /* We only allow SHA1 checksums in this table. */
340 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
341 _("Only SHA1 checksums can be used as keys in the "
342 "rep_cache table.\n"));
344 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_SET_REP));
345 SVN_ERR(svn_sqlite__bindf(stmt, "siiii",
346 svn_checksum_to_cstring(&checksum, pool),
347 (apr_int64_t) rep->revision,
348 (apr_int64_t) rep->item_index,
349 (apr_int64_t) rep->size,
350 (apr_int64_t) rep->expanded_size));
352 err = svn_sqlite__insert(NULL, stmt);
355 representation_t *old_rep;
357 if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT)
358 return svn_error_trace(err);
360 svn_error_clear(err);
362 /* Constraint failed so the mapping for SHA1_CHECKSUM->REP
363 should exist. If so that's cool -- just do nothing. If not,
364 that's a red flag! */
365 SVN_ERR(svn_fs_fs__get_rep_reference(&old_rep, fs, &checksum, pool));
369 /* Something really odd at this point, we failed to insert the
370 checksum AND failed to read an existing checksum. Do we need
380 svn_fs_fs__del_rep_reference(svn_fs_t *fs,
381 svn_revnum_t youngest,
384 fs_fs_data_t *ffd = fs->fsap_data;
385 svn_sqlite__stmt_t *stmt;
387 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
388 if (! ffd->rep_cache_db)
389 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
391 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
392 STMT_DEL_REPS_YOUNGER_THAN_REV));
393 SVN_ERR(svn_sqlite__bindf(stmt, "r", youngest));
394 SVN_ERR(svn_sqlite__step_done(stmt));
399 /* Start a transaction to take an SQLite reserved lock that prevents
402 See unlock_rep_cache(). */
404 lock_rep_cache(svn_fs_t *fs,
407 fs_fs_data_t *ffd = fs->fsap_data;
409 if (! ffd->rep_cache_db)
410 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
412 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP));
417 /* End the transaction started by lock_rep_cache(). */
419 unlock_rep_cache(svn_fs_t *fs,
422 fs_fs_data_t *ffd = fs->fsap_data;
424 SVN_ERR_ASSERT(ffd->rep_cache_db); /* was opened by lock_rep_cache() */
426 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_UNLOCK_REP));
432 svn_fs_fs__with_rep_cache_lock(svn_fs_t *fs,
433 svn_error_t *(*body)(void *,
440 SVN_ERR(lock_rep_cache(fs, pool));
441 err = body(baton, pool);
442 return svn_error_compose_create(err, unlock_rep_cache(fs, pool));