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 /* Check that REP refers to a revision that exists in FS. */
55 rep_has_been_born(representation_t *rep,
61 SVN_ERR(svn_fs_fs__revision_exists(rep->revision, fs, pool));
68 /** Library-private API's. **/
70 /* Body of svn_fs_fs__open_rep_cache().
71 Implements svn_atomic__init_once().init_func.
74 open_rep_cache(void *baton,
78 fs_fs_data_t *ffd = fs->fsap_data;
79 svn_sqlite__db_t *sdb;
83 /* Open (or create) the sqlite database. It will be automatically
84 closed when fs->pool is destoyed. */
85 db_path = path_rep_cache_db(fs->path, pool);
88 /* We want to extend the permissions that apply to the repository
89 as a whole when creating a new rep cache and not simply default
93 SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
96 const char *current = svn_fs_fs__path_current(fs, pool);
97 svn_error_t *err = svn_io_file_create(db_path, "", pool);
99 if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
101 return svn_error_trace(err);
103 /* Some other thread/process created the file. */
104 svn_error_clear(err);
106 /* We created the file. */
107 SVN_ERR(svn_io_copy_perms(current, db_path, pool));
111 SVN_ERR(svn_sqlite__open(&sdb, db_path,
112 svn_sqlite__mode_rwcreate, statements,
116 SVN_ERR(svn_sqlite__read_schema_version(&version, sdb, pool));
117 if (version < REP_CACHE_SCHEMA_FORMAT)
119 /* Must be 0 -- an uninitialized (no schema) database. Create
120 the schema. Results in schema version of 1. */
121 SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_SCHEMA));
124 /* This is used as a flag that the database is available so don't
126 ffd->rep_cache_db = sdb;
132 svn_fs_fs__open_rep_cache(svn_fs_t *fs,
135 fs_fs_data_t *ffd = fs->fsap_data;
136 svn_error_t *err = svn_atomic__init_once(&ffd->rep_cache_db_opened,
137 open_rep_cache, fs, pool);
138 return svn_error_quick_wrap(err, _("Couldn't open rep-cache database"));
142 svn_fs_fs__exists_rep_cache(svn_boolean_t *exists,
143 svn_fs_t *fs, apr_pool_t *pool)
145 svn_node_kind_t kind;
147 SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, pool),
150 *exists = (kind != svn_node_none);
155 svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
158 svn_error_t *(*walker)(representation_t *,
163 svn_cancel_func_t cancel_func,
167 fs_fs_data_t *ffd = fs->fsap_data;
168 svn_sqlite__stmt_t *stmt;
169 svn_boolean_t have_row;
172 apr_pool_t *iterpool = svn_pool_create(pool);
174 /* Don't check ffd->rep_sharing_allowed. */
175 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
177 if (! ffd->rep_cache_db)
178 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
180 /* Check global invariants. */
185 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
187 SVN_ERR(svn_sqlite__step(&have_row, stmt));
188 max = svn_sqlite__column_revnum(stmt, 0);
189 SVN_ERR(svn_sqlite__reset(stmt));
190 if (SVN_IS_VALID_REVNUM(max)) /* The rep-cache could be empty. */
191 SVN_ERR(svn_fs_fs__revision_exists(max, fs, iterpool));
194 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
195 STMT_GET_REPS_FOR_RANGE));
196 SVN_ERR(svn_sqlite__bindf(stmt, "rr",
199 /* Walk the cache entries. */
200 SVN_ERR(svn_sqlite__step(&have_row, stmt));
203 representation_t *rep;
204 const char *sha1_digest;
207 /* Clear ITERPOOL occasionally. */
208 if (iterations++ % 16 == 0)
209 svn_pool_clear(iterpool);
211 /* Check for cancellation. */
214 err = cancel_func(cancel_baton);
216 return svn_error_compose_create(err, svn_sqlite__reset(stmt));
219 /* Construct a representation_t. */
220 rep = apr_pcalloc(iterpool, sizeof(*rep));
221 sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool);
222 err = svn_checksum_parse_hex(&rep->sha1_checksum,
223 svn_checksum_sha1, sha1_digest,
226 return svn_error_compose_create(err, svn_sqlite__reset(stmt));
227 rep->revision = svn_sqlite__column_revnum(stmt, 1);
228 rep->offset = svn_sqlite__column_int64(stmt, 2);
229 rep->size = svn_sqlite__column_int64(stmt, 3);
230 rep->expanded_size = svn_sqlite__column_int64(stmt, 4);
233 err = walker(rep, walker_baton, fs, iterpool);
235 return svn_error_compose_create(err, svn_sqlite__reset(stmt));
237 SVN_ERR(svn_sqlite__step(&have_row, stmt));
240 SVN_ERR(svn_sqlite__reset(stmt));
241 svn_pool_destroy(iterpool);
247 /* This function's caller ignores most errors it returns.
248 If you extend this function, check the callsite to see if you have
249 to make it not-ignore additional error codes. */
251 svn_fs_fs__get_rep_reference(representation_t **rep,
253 svn_checksum_t *checksum,
256 fs_fs_data_t *ffd = fs->fsap_data;
257 svn_sqlite__stmt_t *stmt;
258 svn_boolean_t have_row;
260 SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
261 if (! ffd->rep_cache_db)
262 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
264 /* We only allow SHA1 checksums in this table. */
265 if (checksum->kind != svn_checksum_sha1)
266 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
267 _("Only SHA1 checksums can be used as keys in the "
268 "rep_cache table.\n"));
270 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP));
271 SVN_ERR(svn_sqlite__bindf(stmt, "s",
272 svn_checksum_to_cstring(checksum, pool)));
274 SVN_ERR(svn_sqlite__step(&have_row, stmt));
277 *rep = apr_pcalloc(pool, sizeof(**rep));
278 (*rep)->sha1_checksum = svn_checksum_dup(checksum, pool);
279 (*rep)->revision = svn_sqlite__column_revnum(stmt, 0);
280 (*rep)->offset = svn_sqlite__column_int64(stmt, 1);
281 (*rep)->size = svn_sqlite__column_int64(stmt, 2);
282 (*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3);
287 SVN_ERR(svn_sqlite__reset(stmt));
290 SVN_ERR(rep_has_been_born(*rep, fs, pool));
296 svn_fs_fs__set_rep_reference(svn_fs_t *fs,
297 representation_t *rep,
298 svn_boolean_t reject_dup,
301 fs_fs_data_t *ffd = fs->fsap_data;
302 svn_sqlite__stmt_t *stmt;
305 SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
306 if (! ffd->rep_cache_db)
307 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
309 /* We only allow SHA1 checksums in this table. */
310 if (rep->sha1_checksum == NULL)
311 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
312 _("Only SHA1 checksums can be used as keys in the "
313 "rep_cache table.\n"));
315 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_SET_REP));
316 SVN_ERR(svn_sqlite__bindf(stmt, "siiii",
317 svn_checksum_to_cstring(rep->sha1_checksum, pool),
318 (apr_int64_t) rep->revision,
319 (apr_int64_t) rep->offset,
320 (apr_int64_t) rep->size,
321 (apr_int64_t) rep->expanded_size));
323 err = svn_sqlite__insert(NULL, stmt);
326 representation_t *old_rep;
328 if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT)
329 return svn_error_trace(err);
331 svn_error_clear(err);
333 /* Constraint failed so the mapping for SHA1_CHECKSUM->REP
334 should exist. If so, and the value is the same one we were
335 about to write, that's cool -- just do nothing. If, however,
336 the value is *different*, that's a red flag! */
337 SVN_ERR(svn_fs_fs__get_rep_reference(&old_rep, fs, rep->sha1_checksum,
342 if (reject_dup && ((old_rep->revision != rep->revision)
343 || (old_rep->offset != rep->offset)
344 || (old_rep->size != rep->size)
345 || (old_rep->expanded_size != rep->expanded_size)))
346 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
348 _("Representation key for checksum '%%s' exists "
349 "in filesystem '%%s' with a different value "
350 "(%%ld,%%%s,%%%s,%%%s) than what we were about "
351 "to store (%%ld,%%%s,%%%s,%%%s)"),
352 APR_OFF_T_FMT, SVN_FILESIZE_T_FMT,
353 SVN_FILESIZE_T_FMT, APR_OFF_T_FMT,
354 SVN_FILESIZE_T_FMT, SVN_FILESIZE_T_FMT),
355 svn_checksum_to_cstring_display(rep->sha1_checksum, pool),
356 fs->path, old_rep->revision, old_rep->offset, old_rep->size,
357 old_rep->expanded_size, rep->revision, rep->offset, rep->size,
364 /* Something really odd at this point, we failed to insert the
365 checksum AND failed to read an existing checksum. Do we need
375 svn_fs_fs__del_rep_reference(svn_fs_t *fs,
376 svn_revnum_t youngest,
379 fs_fs_data_t *ffd = fs->fsap_data;
380 svn_sqlite__stmt_t *stmt;
382 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
383 if (! ffd->rep_cache_db)
384 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
386 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
387 STMT_DEL_REPS_YOUNGER_THAN_REV));
388 SVN_ERR(svn_sqlite__bindf(stmt, "r", youngest));
389 SVN_ERR(svn_sqlite__step_done(stmt));
395 svn_fs_fs__lock_rep_cache(svn_fs_t *fs,
398 fs_fs_data_t *ffd = fs->fsap_data;
400 if (! ffd->rep_cache_db)
401 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
403 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP));