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_ERR(svn_sqlite__read_schema_version(&version, sdb, scratch_pool)); 104 if (version < REP_CACHE_SCHEMA_FORMAT) 105 { 106 /* Must be 0 -- an uninitialized (no schema) database. Create 107 the schema. Results in schema version of 1. */ 108 SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_SCHEMA)); 109 } 110 111 /* This is used as a flag that the database is available so don't 112 set it earlier. */ 113 ffd->rep_cache_db = sdb; 114 115 return SVN_NO_ERROR; 116} 117 118svn_error_t * 119svn_fs_x__open_rep_cache(svn_fs_t *fs, 120 apr_pool_t *scratch_pool) 121{ 122 svn_fs_x__data_t *ffd = fs->fsap_data; 123 svn_error_t *err = svn_atomic__init_once(&ffd->rep_cache_db_opened, 124 open_rep_cache, fs, scratch_pool); 125 return svn_error_quick_wrap(err, _("Couldn't open rep-cache database")); 126} 127 128svn_error_t * 129svn_fs_x__exists_rep_cache(svn_boolean_t *exists, 130 svn_fs_t *fs, 131 apr_pool_t *scratch_pool) 132{ 133 svn_node_kind_t kind; 134 135 SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, scratch_pool), 136 &kind, scratch_pool)); 137 138 *exists = (kind != svn_node_none); 139 return SVN_NO_ERROR; 140} 141 142svn_error_t * 143svn_fs_x__walk_rep_reference(svn_fs_t *fs, 144 svn_revnum_t start, 145 svn_revnum_t end, 146 svn_error_t *(*walker)(svn_fs_x__representation_t *, 147 void *, 148 svn_fs_t *, 149 apr_pool_t *), 150 void *walker_baton, 151 svn_cancel_func_t cancel_func, 152 void *cancel_baton, 153 apr_pool_t *scratch_pool) 154{ 155 svn_fs_x__data_t *ffd = fs->fsap_data; 156 svn_sqlite__stmt_t *stmt; 157 svn_boolean_t have_row; 158 int iterations = 0; 159 160 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 161 162 if (! ffd->rep_cache_db) 163 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool)); 164 165 /* Check global invariants. */ 166 if (start == 0) 167 { 168 svn_revnum_t max; 169 170 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, 171 STMT_GET_MAX_REV)); 172 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 173 max = svn_sqlite__column_revnum(stmt, 0); 174 SVN_ERR(svn_sqlite__reset(stmt)); 175 if (SVN_IS_VALID_REVNUM(max)) /* The rep-cache could be empty. */ 176 SVN_ERR(svn_fs_x__ensure_revision_exists(max, fs, iterpool)); 177 } 178 179 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, 180 STMT_GET_REPS_FOR_RANGE)); 181 SVN_ERR(svn_sqlite__bindf(stmt, "rr", 182 start, end)); 183 184 /* Walk the cache entries. */ 185 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 186 while (have_row) 187 { 188 svn_fs_x__representation_t *rep; 189 const char *sha1_digest; 190 svn_error_t *err; 191 svn_checksum_t *checksum; 192 193 /* Clear ITERPOOL occasionally. */ 194 if (iterations++ % 16 == 0) 195 svn_pool_clear(iterpool); 196 197 /* Check for cancellation. */ 198 if (cancel_func) 199 { 200 err = cancel_func(cancel_baton); 201 if (err) 202 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 203 } 204 205 /* Construct a svn_fs_x__representation_t. */ 206 rep = apr_pcalloc(iterpool, sizeof(*rep)); 207 sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool); 208 err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1, 209 sha1_digest, iterpool); 210 if (err) 211 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 212 213 rep->has_sha1 = TRUE; 214 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest)); 215 rep->id.change_set = svn_sqlite__column_revnum(stmt, 1); 216 rep->id.number = svn_sqlite__column_int64(stmt, 2); 217 rep->size = svn_sqlite__column_int64(stmt, 3); 218 rep->expanded_size = svn_sqlite__column_int64(stmt, 4); 219 220 /* Walk. */ 221 err = walker(rep, walker_baton, fs, iterpool); 222 if (err) 223 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 224 225 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 226 } 227 228 SVN_ERR(svn_sqlite__reset(stmt)); 229 svn_pool_destroy(iterpool); 230 231 return SVN_NO_ERROR; 232} 233 234 235/* This function's caller ignores most errors it returns. 236 If you extend this function, check the callsite to see if you have 237 to make it not-ignore additional error codes. */ 238svn_error_t * 239svn_fs_x__get_rep_reference(svn_fs_x__representation_t **rep, 240 svn_fs_t *fs, 241 svn_checksum_t *checksum, 242 apr_pool_t *result_pool, 243 apr_pool_t *scratch_pool) 244{ 245 svn_fs_x__data_t *ffd = fs->fsap_data; 246 svn_sqlite__stmt_t *stmt; 247 svn_boolean_t have_row; 248 249 SVN_ERR_ASSERT(ffd->rep_sharing_allowed); 250 if (! ffd->rep_cache_db) 251 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool)); 252 253 /* We only allow SHA1 checksums in this table. */ 254 if (checksum->kind != svn_checksum_sha1) 255 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, 256 _("Only SHA1 checksums can be used as keys in the " 257 "rep_cache table.\n")); 258 259 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP)); 260 SVN_ERR(svn_sqlite__bindf(stmt, "s", 261 svn_checksum_to_cstring(checksum, scratch_pool))); 262 263 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 264 if (have_row) 265 { 266 *rep = apr_pcalloc(result_pool, sizeof(**rep)); 267 memcpy((*rep)->sha1_digest, checksum->digest, 268 sizeof((*rep)->sha1_digest)); 269 (*rep)->has_sha1 = TRUE; 270 (*rep)->id.change_set = svn_sqlite__column_revnum(stmt, 0); 271 (*rep)->id.number = svn_sqlite__column_int64(stmt, 1); 272 (*rep)->size = svn_sqlite__column_int64(stmt, 2); 273 (*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3); 274 } 275 else 276 *rep = NULL; 277 278 SVN_ERR(svn_sqlite__reset(stmt)); 279 280 if (*rep) 281 { 282 /* Check that REP refers to a revision that exists in FS. */ 283 svn_revnum_t revision = svn_fs_x__get_revnum((*rep)->id.change_set); 284 svn_error_t *err = svn_fs_x__ensure_revision_exists(revision, fs, 285 scratch_pool); 286 if (err) 287 return svn_error_createf(SVN_ERR_FS_CORRUPT, err, 288 "Checksum '%s' in rep-cache is beyond HEAD", 289 svn_checksum_to_cstring_display(checksum, scratch_pool)); 290 } 291 292 return SVN_NO_ERROR; 293} 294 295svn_error_t * 296svn_fs_x__set_rep_reference(svn_fs_t *fs, 297 svn_fs_x__representation_t *rep, 298 apr_pool_t *scratch_pool) 299{ 300 svn_fs_x__data_t *ffd = fs->fsap_data; 301 svn_sqlite__stmt_t *stmt; 302 svn_error_t *err; 303 svn_checksum_t checksum; 304 checksum.kind = svn_checksum_sha1; 305 checksum.digest = rep->sha1_digest; 306 307 SVN_ERR_ASSERT(ffd->rep_sharing_allowed); 308 if (! ffd->rep_cache_db) 309 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool)); 310 311 /* We only allow SHA1 checksums in this table. */ 312 if (! rep->has_sha1) 313 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, 314 _("Only SHA1 checksums can be used as keys in the " 315 "rep_cache table.\n")); 316 317 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_SET_REP)); 318 SVN_ERR(svn_sqlite__bindf(stmt, "siiii", 319 svn_checksum_to_cstring(&checksum, scratch_pool), 320 (apr_int64_t) rep->id.change_set, 321 (apr_int64_t) rep->id.number, 322 (apr_int64_t) rep->size, 323 (apr_int64_t) rep->expanded_size)); 324 325 err = svn_sqlite__insert(NULL, stmt); 326 if (err) 327 { 328 svn_fs_x__representation_t *old_rep; 329 330 if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT) 331 return svn_error_trace(err); 332 333 svn_error_clear(err); 334 335 /* Constraint failed so the mapping for SHA1_CHECKSUM->REP 336 should exist. If so that's cool -- just do nothing. If not, 337 that's a red flag! */ 338 SVN_ERR(svn_fs_x__get_rep_reference(&old_rep, fs, &checksum, 339 scratch_pool, scratch_pool)); 340 341 if (!old_rep) 342 { 343 /* Something really odd at this point, we failed to insert the 344 checksum AND failed to read an existing checksum. Do we need 345 to flag this? */ 346 } 347 } 348 349 return SVN_NO_ERROR; 350} 351 352 353svn_error_t * 354svn_fs_x__del_rep_reference(svn_fs_t *fs, 355 svn_revnum_t youngest, 356 apr_pool_t *scratch_pool) 357{ 358 svn_fs_x__data_t *ffd = fs->fsap_data; 359 svn_sqlite__stmt_t *stmt; 360 361 if (! ffd->rep_cache_db) 362 SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool)); 363 364 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, 365 STMT_DEL_REPS_YOUNGER_THAN_REV)); 366 SVN_ERR(svn_sqlite__bindf(stmt, "r", youngest)); 367 SVN_ERR(svn_sqlite__step_done(stmt)); 368 369 return SVN_NO_ERROR; 370} 371 372/* Start a transaction to take an SQLite reserved lock that prevents 373 other writes. 374 375 See unlock_rep_cache(). */ 376static svn_error_t * 377lock_rep_cache(svn_fs_t *fs, 378 apr_pool_t *pool) 379{ 380 svn_fs_x__data_t *ffd = fs->fsap_data; 381 382 if (! ffd->rep_cache_db) 383 SVN_ERR(svn_fs_x__open_rep_cache(fs, pool)); 384 385 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP)); 386 387 return SVN_NO_ERROR; 388} 389 390/* End the transaction started by lock_rep_cache(). */ 391static svn_error_t * 392unlock_rep_cache(svn_fs_t *fs, 393 apr_pool_t *pool) 394{ 395 svn_fs_x__data_t *ffd = fs->fsap_data; 396 397 SVN_ERR_ASSERT(ffd->rep_cache_db); /* was opened by lock_rep_cache() */ 398 399 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_UNLOCK_REP)); 400 401 return SVN_NO_ERROR; 402} 403 404svn_error_t * 405svn_fs_x__with_rep_cache_lock(svn_fs_t *fs, 406 svn_error_t *(*body)(void *, 407 apr_pool_t *), 408 void *baton, 409 apr_pool_t *pool) 410{ 411 svn_error_t *err; 412 413 SVN_ERR(lock_rep_cache(fs, pool)); 414 err = body(baton, pool); 415 return svn_error_compose_create(err, unlock_rep_cache(fs, pool)); 416} 417