rep-cache.c revision 362181
1/* rep-sharing.c --- the rep-sharing cache for fsx 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_x.h" 28#include "fs.h" 29#include "rep-cache.h" 30#include "util.h" 31#include "../libsvn_fs/fs-loader.h" 32 33#include "svn_path.h" 34 35#include "private/svn_sqlite.h" 36 37#include "rep-cache-db.h" 38 39/* A few magic values */ 40#define REP_CACHE_SCHEMA_FORMAT 1 41 42REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements); 43 44 45 46/** Helper functions. **/ 47static APR_INLINE const char * 48path_rep_cache_db(const char *fs_path, 49 apr_pool_t *result_pool) 50{ 51 return svn_dirent_join(fs_path, REP_CACHE_DB_NAME, result_pool); 52} 53 54 55/** Library-private API's. **/ 56 57/* Body of svn_fs_x__open_rep_cache(). 58 Implements svn_atomic__init_once().init_func. 59 */ 60static svn_error_t * 61open_rep_cache(void *baton, 62 apr_pool_t *scratch_pool) 63{ 64 svn_fs_t *fs = baton; 65 svn_fs_x__data_t *ffd = fs->fsap_data; 66 svn_sqlite__db_t *sdb; 67 const char *db_path; 68 int version; 69 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); 73#ifndef WIN32 74 { 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 77 to umask. */ 78 svn_boolean_t exists; 79 80 SVN_ERR(svn_fs_x__exists_rep_cache(&exists, fs, scratch_pool)); 81 if (!exists) 82 { 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); 85 86 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 87 /* A real error. */ 88 return svn_error_trace(err); 89 else if (err) 90 /* Some other thread/process created the file. */ 91 svn_error_clear(err); 92 else 93 /* We created the file. */ 94 SVN_ERR(svn_io_copy_perms(current, db_path, scratch_pool)); 95 } 96 } 97#endif 98 SVN_ERR(svn_sqlite__open(&sdb, db_path, 99 svn_sqlite__mode_rwcreate, statements, 100 0, NULL, 0, 101 fs->pool, scratch_pool)); 102 103 SVN_SQLITE__ERR_CLOSE(svn_sqlite__read_schema_version(&version, sdb, 104 scratch_pool), 105 sdb); 106 if (version < REP_CACHE_SCHEMA_FORMAT) 107 { 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, 111 STMT_CREATE_SCHEMA), 112 sdb); 113 } 114 115 /* This is used as a flag that the database is available so don't 116 set it earlier. */ 117 ffd->rep_cache_db = sdb; 118 119 return SVN_NO_ERROR; 120} 121 122svn_error_t * 123svn_fs_x__open_rep_cache(svn_fs_t *fs, 124 apr_pool_t *scratch_pool) 125{ 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), 133 scratch_pool)); 134} 135 136svn_error_t * 137svn_fs_x__close_rep_cache(svn_fs_t *fs) 138{ 139 svn_fs_x__data_t *ffd = fs->fsap_data; 140 141 if (ffd->rep_cache_db) 142 { 143 SVN_ERR(svn_sqlite__close(ffd->rep_cache_db)); 144 ffd->rep_cache_db = NULL; 145 ffd->rep_cache_db_opened = 0; 146 } 147 148 return SVN_NO_ERROR; 149} 150 151svn_error_t * 152svn_fs_x__exists_rep_cache(svn_boolean_t *exists, 153 svn_fs_t *fs, 154 apr_pool_t *scratch_pool) 155{ 156 svn_node_kind_t kind; 157 158 SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, scratch_pool), 159 &kind, scratch_pool)); 160 161 *exists = (kind != svn_node_none); 162 return SVN_NO_ERROR; 163} 164 165svn_error_t * 166svn_fs_x__walk_rep_reference(svn_fs_t *fs, 167 svn_revnum_t start, 168 svn_revnum_t end, 169 svn_error_t *(*walker)(svn_fs_x__representation_t *, 170 void *, 171 svn_fs_t *, 172 apr_pool_t *), 173 void *walker_baton, 174 svn_cancel_func_t cancel_func, 175 void *cancel_baton, 176 apr_pool_t *scratch_pool) 177{ 178 svn_fs_x__data_t *ffd = fs->fsap_data; 179 svn_sqlite__stmt_t *stmt; 180 svn_boolean_t have_row; 181 int iterations = 0; 182 183 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 184 185 if (! ffd->rep_cache_db) 186 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool)); 187 188 /* Check global invariants. */ 189 if (start == 0) 190 { 191 svn_revnum_t max; 192 193 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, 194 STMT_GET_MAX_REV)); 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)); 200 } 201 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", 205 start, end)); 206 207 /* Walk the cache entries. */ 208 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 209 while (have_row) 210 { 211 svn_fs_x__representation_t *rep; 212 const char *sha1_digest; 213 svn_error_t *err; 214 svn_checksum_t *checksum; 215 216 /* Clear ITERPOOL occasionally. */ 217 if (iterations++ % 16 == 0) 218 svn_pool_clear(iterpool); 219 220 /* Check for cancellation. */ 221 if (cancel_func) 222 { 223 err = cancel_func(cancel_baton); 224 if (err) 225 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 226 } 227 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); 233 if (err) 234 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 235 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); 242 243 /* Walk. */ 244 err = walker(rep, walker_baton, fs, iterpool); 245 if (err) 246 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 247 248 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 249 } 250 251 SVN_ERR(svn_sqlite__reset(stmt)); 252 svn_pool_destroy(iterpool); 253 254 return SVN_NO_ERROR; 255} 256 257 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. */ 261svn_error_t * 262svn_fs_x__get_rep_reference(svn_fs_x__representation_t **rep_p, 263 svn_fs_t *fs, 264 svn_checksum_t *checksum, 265 apr_pool_t *result_pool, 266 apr_pool_t *scratch_pool) 267{ 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; 272 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)); 276 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")); 282 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))); 286 287 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 288 if (have_row) 289 { 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); 297 } 298 else 299 rep = NULL; 300 301 SVN_ERR(svn_sqlite__reset(stmt)); 302 303 if (rep) 304 { 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, 308 scratch_pool); 309 if (err) 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)); 313 } 314 315 *rep_p = rep; 316 return SVN_NO_ERROR; 317} 318 319svn_error_t * 320svn_fs_x__set_rep_reference(svn_fs_t *fs, 321 svn_fs_x__representation_t *rep, 322 apr_pool_t *scratch_pool) 323{ 324 svn_fs_x__data_t *ffd = fs->fsap_data; 325 svn_sqlite__stmt_t *stmt; 326 svn_error_t *err; 327 svn_checksum_t checksum; 328 checksum.kind = svn_checksum_sha1; 329 checksum.digest = rep->sha1_digest; 330 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)); 334 335 /* We only allow SHA1 checksums in this table. */ 336 if (! rep->has_sha1) 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")); 340 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)); 348 349 err = svn_sqlite__insert(NULL, stmt); 350 if (err) 351 { 352 svn_fs_x__representation_t *old_rep; 353 354 if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT) 355 return svn_error_trace(err); 356 357 svn_error_clear(err); 358 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)); 364 365 if (!old_rep) 366 { 367 /* Something really odd at this point, we failed to insert the 368 checksum AND failed to read an existing checksum. Do we need 369 to flag this? */ 370 } 371 } 372 373 return SVN_NO_ERROR; 374} 375 376 377svn_error_t * 378svn_fs_x__del_rep_reference(svn_fs_t *fs, 379 svn_revnum_t youngest, 380 apr_pool_t *scratch_pool) 381{ 382 svn_fs_x__data_t *ffd = fs->fsap_data; 383 svn_sqlite__stmt_t *stmt; 384 385 if (! ffd->rep_cache_db) 386 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool)); 387 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)); 392 393 return SVN_NO_ERROR; 394} 395 396/* Start a transaction to take an SQLite reserved lock that prevents 397 other writes. 398 399 See unlock_rep_cache(). */ 400static svn_error_t * 401lock_rep_cache(svn_fs_t *fs, 402 apr_pool_t *pool) 403{ 404 svn_fs_x__data_t *ffd = fs->fsap_data; 405 406 if (! ffd->rep_cache_db) 407 SVN_ERR(svn_fs_x__open_rep_cache(fs, pool)); 408 409 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP)); 410 411 return SVN_NO_ERROR; 412} 413 414/* End the transaction started by lock_rep_cache(). */ 415static svn_error_t * 416unlock_rep_cache(svn_fs_t *fs, 417 apr_pool_t *pool) 418{ 419 svn_fs_x__data_t *ffd = fs->fsap_data; 420 421 SVN_ERR_ASSERT(ffd->rep_cache_db); /* was opened by lock_rep_cache() */ 422 423 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_UNLOCK_REP)); 424 425 return SVN_NO_ERROR; 426} 427 428svn_error_t * 429svn_fs_x__with_rep_cache_lock(svn_fs_t *fs, 430 svn_error_t *(*body)(void *, 431 apr_pool_t *), 432 void *baton, 433 apr_pool_t *pool) 434{ 435 svn_error_t *err; 436 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)); 440} 441