rep-cache.c revision 299742
1/* rep-sharing.c --- the rep-sharing cache for fsfs 2 * 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 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 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 19 * under the License. 20 * ==================================================================== 21 */ 22 23#include "svn_pools.h" 24 25#include "svn_private_config.h" 26 27#include "fs_fs.h" 28#include "fs.h" 29#include "rep-cache.h" 30#include "../libsvn_fs/fs-loader.h" 31 32#include "svn_path.h" 33 34#include "private/svn_sqlite.h" 35 36#include "rep-cache-db.h" 37 38/* A few magic values */ 39#define REP_CACHE_SCHEMA_FORMAT 1 40 41REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements); 42 43 44 45/** Helper functions. **/ 46static APR_INLINE const char * 47path_rep_cache_db(const char *fs_path, 48 apr_pool_t *result_pool) 49{ 50 return svn_dirent_join(fs_path, REP_CACHE_DB_NAME, result_pool); 51} 52 53 54/** Library-private API's. **/ 55 56/* Body of svn_fs_fs__open_rep_cache(). 57 Implements svn_atomic__init_once().init_func. 58 */ 59static svn_error_t * 60open_rep_cache(void *baton, 61 apr_pool_t *pool) 62{ 63 svn_fs_t *fs = baton; 64 fs_fs_data_t *ffd = fs->fsap_data; 65 svn_sqlite__db_t *sdb; 66 const char *db_path; 67 int version; 68 69 /* Open (or create) the sqlite database. It will be automatically 70 closed when fs->pool is destroyed. */ 71 db_path = path_rep_cache_db(fs->path, pool); 72#ifndef WIN32 73 { 74 /* We want to extend the permissions that apply to the repository 75 as a whole when creating a new rep cache and not simply default 76 to umask. */ 77 svn_boolean_t exists; 78 79 SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool)); 80 if (!exists) 81 { 82 const char *current = svn_fs_fs__path_current(fs, pool); 83 svn_error_t *err = svn_io_file_create_empty(db_path, pool); 84 85 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 86 /* A real error. */ 87 return svn_error_trace(err); 88 else if (err) 89 /* Some other thread/process created the file. */ 90 svn_error_clear(err); 91 else 92 /* We created the file. */ 93 SVN_ERR(svn_io_copy_perms(current, db_path, pool)); 94 } 95 } 96#endif 97 SVN_ERR(svn_sqlite__open(&sdb, db_path, 98 svn_sqlite__mode_rwcreate, statements, 99 0, NULL, 0, 100 fs->pool, pool)); 101 102 SVN_ERR(svn_sqlite__read_schema_version(&version, sdb, pool)); 103 if (version < REP_CACHE_SCHEMA_FORMAT) 104 { 105 /* Must be 0 -- an uninitialized (no schema) database. Create 106 the schema. Results in schema version of 1. */ 107 SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_SCHEMA)); 108 } 109 110 /* This is used as a flag that the database is available so don't 111 set it earlier. */ 112 ffd->rep_cache_db = sdb; 113 114 return SVN_NO_ERROR; 115} 116 117svn_error_t * 118svn_fs_fs__open_rep_cache(svn_fs_t *fs, 119 apr_pool_t *pool) 120{ 121 fs_fs_data_t *ffd = fs->fsap_data; 122 svn_error_t *err = svn_atomic__init_once(&ffd->rep_cache_db_opened, 123 open_rep_cache, fs, pool); 124 return svn_error_quick_wrap(err, _("Couldn't open rep-cache database")); 125} 126 127svn_error_t * 128svn_fs_fs__exists_rep_cache(svn_boolean_t *exists, 129 svn_fs_t *fs, apr_pool_t *pool) 130{ 131 svn_node_kind_t kind; 132 133 SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, pool), 134 &kind, pool)); 135 136 *exists = (kind != svn_node_none); 137 return SVN_NO_ERROR; 138} 139 140svn_error_t * 141svn_fs_fs__walk_rep_reference(svn_fs_t *fs, 142 svn_revnum_t start, 143 svn_revnum_t end, 144 svn_error_t *(*walker)(representation_t *, 145 void *, 146 svn_fs_t *, 147 apr_pool_t *), 148 void *walker_baton, 149 svn_cancel_func_t cancel_func, 150 void *cancel_baton, 151 apr_pool_t *pool) 152{ 153 fs_fs_data_t *ffd = fs->fsap_data; 154 svn_sqlite__stmt_t *stmt; 155 svn_boolean_t have_row; 156 int iterations = 0; 157 158 apr_pool_t *iterpool = svn_pool_create(pool); 159 160 /* Don't check ffd->rep_sharing_allowed. */ 161 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT); 162 163 if (! ffd->rep_cache_db) 164 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 165 166 /* Check global invariants. */ 167 if (start == 0) 168 { 169 svn_revnum_t max; 170 171 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, 172 STMT_GET_MAX_REV)); 173 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 174 max = svn_sqlite__column_revnum(stmt, 0); 175 SVN_ERR(svn_sqlite__reset(stmt)); 176 if (SVN_IS_VALID_REVNUM(max)) /* The rep-cache could be empty. */ 177 SVN_ERR(svn_fs_fs__ensure_revision_exists(max, fs, iterpool)); 178 } 179 180 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, 181 STMT_GET_REPS_FOR_RANGE)); 182 SVN_ERR(svn_sqlite__bindf(stmt, "rr", 183 start, end)); 184 185 /* Walk the cache entries. */ 186 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 187 while (have_row) 188 { 189 representation_t *rep; 190 const char *sha1_digest; 191 svn_error_t *err; 192 svn_checksum_t *checksum; 193 194 /* Clear ITERPOOL occasionally. */ 195 if (iterations++ % 16 == 0) 196 svn_pool_clear(iterpool); 197 198 /* Check for cancellation. */ 199 if (cancel_func) 200 { 201 err = cancel_func(cancel_baton); 202 if (err) 203 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 204 } 205 206 /* Construct a representation_t. */ 207 rep = apr_pcalloc(iterpool, sizeof(*rep)); 208 svn_fs_fs__id_txn_reset(&rep->txn_id); 209 sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool); 210 err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1, 211 sha1_digest, iterpool); 212 if (err) 213 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 214 215 rep->has_sha1 = TRUE; 216 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest)); 217 rep->revision = svn_sqlite__column_revnum(stmt, 1); 218 rep->item_index = svn_sqlite__column_int64(stmt, 2); 219 rep->size = svn_sqlite__column_int64(stmt, 3); 220 rep->expanded_size = svn_sqlite__column_int64(stmt, 4); 221 222 /* Walk. */ 223 err = walker(rep, walker_baton, fs, iterpool); 224 if (err) 225 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 226 227 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 228 } 229 230 SVN_ERR(svn_sqlite__reset(stmt)); 231 svn_pool_destroy(iterpool); 232 233 return SVN_NO_ERROR; 234} 235 236 237/* This function's caller ignores most errors it returns. 238 If you extend this function, check the callsite to see if you have 239 to make it not-ignore additional error codes. */ 240svn_error_t * 241svn_fs_fs__get_rep_reference(representation_t **rep, 242 svn_fs_t *fs, 243 svn_checksum_t *checksum, 244 apr_pool_t *pool) 245{ 246 fs_fs_data_t *ffd = fs->fsap_data; 247 svn_sqlite__stmt_t *stmt; 248 svn_boolean_t have_row; 249 250 SVN_ERR_ASSERT(ffd->rep_sharing_allowed); 251 if (! ffd->rep_cache_db) 252 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 253 254 /* We only allow SHA1 checksums in this table. */ 255 if (checksum->kind != svn_checksum_sha1) 256 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, 257 _("Only SHA1 checksums can be used as keys in the " 258 "rep_cache table.\n")); 259 260 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP)); 261 SVN_ERR(svn_sqlite__bindf(stmt, "s", 262 svn_checksum_to_cstring(checksum, pool))); 263 264 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 265 if (have_row) 266 { 267 *rep = apr_pcalloc(pool, sizeof(**rep)); 268 svn_fs_fs__id_txn_reset(&(*rep)->txn_id); 269 memcpy((*rep)->sha1_digest, checksum->digest, 270 sizeof((*rep)->sha1_digest)); 271 (*rep)->has_sha1 = TRUE; 272 (*rep)->revision = svn_sqlite__column_revnum(stmt, 0); 273 (*rep)->item_index = svn_sqlite__column_int64(stmt, 1); 274 (*rep)->size = svn_sqlite__column_int64(stmt, 2); 275 (*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3); 276 } 277 else 278 *rep = NULL; 279 280 SVN_ERR(svn_sqlite__reset(stmt)); 281 282 if (*rep) 283 { 284 /* Check that REP refers to a revision that exists in FS. */ 285 svn_error_t *err = svn_fs_fs__ensure_revision_exists((*rep)->revision, 286 fs, pool); 287 if (err) 288 return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 289 "Checksum '%s' in rep-cache is beyond HEAD", 290 svn_checksum_to_cstring_display(checksum, 291 pool)); 292 } 293 294 return SVN_NO_ERROR; 295} 296 297svn_error_t * 298svn_fs_fs__set_rep_reference(svn_fs_t *fs, 299 representation_t *rep, 300 apr_pool_t *pool) 301{ 302 fs_fs_data_t *ffd = fs->fsap_data; 303 svn_sqlite__stmt_t *stmt; 304 svn_error_t *err; 305 svn_checksum_t checksum; 306 checksum.kind = svn_checksum_sha1; 307 checksum.digest = rep->sha1_digest; 308 309 SVN_ERR_ASSERT(ffd->rep_sharing_allowed); 310 if (! ffd->rep_cache_db) 311 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 312 313 /* We only allow SHA1 checksums in this table. */ 314 if (! rep->has_sha1) 315 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, 316 _("Only SHA1 checksums can be used as keys in the " 317 "rep_cache table.\n")); 318 319 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_SET_REP)); 320 SVN_ERR(svn_sqlite__bindf(stmt, "siiii", 321 svn_checksum_to_cstring(&checksum, pool), 322 (apr_int64_t) rep->revision, 323 (apr_int64_t) rep->item_index, 324 (apr_int64_t) rep->size, 325 (apr_int64_t) rep->expanded_size)); 326 327 err = svn_sqlite__insert(NULL, stmt); 328 if (err) 329 { 330 representation_t *old_rep; 331 332 if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT) 333 return svn_error_trace(err); 334 335 svn_error_clear(err); 336 337 /* Constraint failed so the mapping for SHA1_CHECKSUM->REP 338 should exist. If so that's cool -- just do nothing. If not, 339 that's a red flag! */ 340 SVN_ERR(svn_fs_fs__get_rep_reference(&old_rep, fs, &checksum, pool)); 341 342 if (!old_rep) 343 { 344 /* Something really odd at this point, we failed to insert the 345 checksum AND failed to read an existing checksum. Do we need 346 to flag this? */ 347 } 348 } 349 350 return SVN_NO_ERROR; 351} 352 353 354svn_error_t * 355svn_fs_fs__del_rep_reference(svn_fs_t *fs, 356 svn_revnum_t youngest, 357 apr_pool_t *pool) 358{ 359 fs_fs_data_t *ffd = fs->fsap_data; 360 svn_sqlite__stmt_t *stmt; 361 362 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT); 363 if (! ffd->rep_cache_db) 364 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 365 366 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, 367 STMT_DEL_REPS_YOUNGER_THAN_REV)); 368 SVN_ERR(svn_sqlite__bindf(stmt, "r", youngest)); 369 SVN_ERR(svn_sqlite__step_done(stmt)); 370 371 return SVN_NO_ERROR; 372} 373 374/* Start a transaction to take an SQLite reserved lock that prevents 375 other writes. 376 377 See unlock_rep_cache(). */ 378static svn_error_t * 379lock_rep_cache(svn_fs_t *fs, 380 apr_pool_t *pool) 381{ 382 fs_fs_data_t *ffd = fs->fsap_data; 383 384 if (! ffd->rep_cache_db) 385 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 386 387 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP)); 388 389 return SVN_NO_ERROR; 390} 391 392/* End the transaction started by lock_rep_cache(). */ 393static svn_error_t * 394unlock_rep_cache(svn_fs_t *fs, 395 apr_pool_t *pool) 396{ 397 fs_fs_data_t *ffd = fs->fsap_data; 398 399 SVN_ERR_ASSERT(ffd->rep_cache_db); /* was opened by lock_rep_cache() */ 400 401 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_UNLOCK_REP)); 402 403 return SVN_NO_ERROR; 404} 405 406svn_error_t * 407svn_fs_fs__with_rep_cache_lock(svn_fs_t *fs, 408 svn_error_t *(*body)(void *, 409 apr_pool_t *), 410 void *baton, 411 apr_pool_t *pool) 412{ 413 svn_error_t *err; 414 415 SVN_ERR(lock_rep_cache(fs, pool)); 416 err = body(baton, pool); 417 return svn_error_compose_create(err, unlock_rep_cache(fs, pool)); 418} 419