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#define SVN_ERR_CLOSE(x, db) do \ 54{ \ 55 svn_error_t *svn__err = (x); \ 56 if (svn__err) \ 57 return svn_error_compose_create(svn__err, svn_sqlite__close(db)); \ 58} while (0) 59 60 61/** Library-private API's. **/ 62 63/* Body of svn_fs_fs__open_rep_cache(). 64 Implements svn_atomic__init_once().init_func. 65 */ 66static svn_error_t * 67open_rep_cache(void *baton, 68 apr_pool_t *pool) 69{ 70 svn_fs_t *fs = baton; 71 fs_fs_data_t *ffd = fs->fsap_data; 72 svn_sqlite__db_t *sdb; 73 const char *db_path; 74 int version; 75 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); 79#ifndef WIN32 80 { 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 83 to umask. */ 84 svn_boolean_t exists; 85 86 SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool)); 87 if (!exists) 88 { 89 const char *current = svn_fs_fs__path_current(fs, pool); 90 svn_error_t *err = svn_io_file_create_empty(db_path, pool); 91 92 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 93 /* A real error. */ 94 return svn_error_trace(err); 95 else if (err) 96 /* Some other thread/process created the file. */ 97 svn_error_clear(err); 98 else 99 /* We created the file. */ 100 SVN_ERR(svn_io_copy_perms(current, db_path, pool)); 101 } 102 } 103#endif 104 SVN_ERR(svn_sqlite__open(&sdb, db_path, 105 svn_sqlite__mode_rwcreate, statements, 106 0, NULL, 0, 107 fs->pool, pool)); 108 109 SVN_ERR_CLOSE(svn_sqlite__read_schema_version(&version, sdb, pool), sdb); 110 if (version < REP_CACHE_SCHEMA_FORMAT) 111 { 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); 115 } 116 117 /* This is used as a flag that the database is available so don't 118 set it earlier. */ 119 ffd->rep_cache_db = sdb; 120 121 return SVN_NO_ERROR; 122} 123 124svn_error_t * 125svn_fs_fs__open_rep_cache(svn_fs_t *fs, 126 apr_pool_t *pool) 127{ 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_wrap(err, _("Couldn't open rep-cache database")); 132} 133 134svn_error_t * 135svn_fs_fs__close_rep_cache(svn_fs_t *fs) 136{ 137 fs_fs_data_t *ffd = fs->fsap_data; 138 139 if (ffd->rep_cache_db) 140 { 141 SVN_ERR(svn_sqlite__close(ffd->rep_cache_db)); 142 ffd->rep_cache_db = NULL; 143 ffd->rep_cache_db_opened = 0; 144 } 145 146 return SVN_NO_ERROR; 147} 148 149svn_error_t * 150svn_fs_fs__exists_rep_cache(svn_boolean_t *exists, 151 svn_fs_t *fs, apr_pool_t *pool) 152{ 153 svn_node_kind_t kind; 154 155 SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, pool), 156 &kind, pool)); 157 158 *exists = (kind != svn_node_none); 159 return SVN_NO_ERROR; 160} 161 162svn_error_t * 163svn_fs_fs__walk_rep_reference(svn_fs_t *fs, 164 svn_revnum_t start, 165 svn_revnum_t end, 166 svn_error_t *(*walker)(representation_t *, 167 void *, 168 svn_fs_t *, 169 apr_pool_t *), 170 void *walker_baton, 171 svn_cancel_func_t cancel_func, 172 void *cancel_baton, 173 apr_pool_t *pool) 174{ 175 fs_fs_data_t *ffd = fs->fsap_data; 176 svn_sqlite__stmt_t *stmt; 177 svn_boolean_t have_row; 178 int iterations = 0; 179 180 apr_pool_t *iterpool = svn_pool_create(pool); 181 182 /* Don't check ffd->rep_sharing_allowed. */ 183 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT); 184 185 if (! ffd->rep_cache_db) 186 SVN_ERR(svn_fs_fs__open_rep_cache(fs, 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_fs__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 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 representation_t. */ 229 rep = apr_pcalloc(iterpool, sizeof(*rep)); 230 svn_fs_fs__id_txn_reset(&rep->txn_id); 231 sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool); 232 err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1, 233 sha1_digest, iterpool); 234 if (err) 235 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 236 237 rep->has_sha1 = TRUE; 238 memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest)); 239 rep->revision = svn_sqlite__column_revnum(stmt, 1); 240 rep->item_index = svn_sqlite__column_int64(stmt, 2); 241 rep->size = svn_sqlite__column_int64(stmt, 3); 242 rep->expanded_size = svn_sqlite__column_int64(stmt, 4); 243 244 /* Walk. */ 245 err = walker(rep, walker_baton, fs, iterpool); 246 if (err) 247 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 248 249 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 250 } 251 252 SVN_ERR(svn_sqlite__reset(stmt)); 253 svn_pool_destroy(iterpool); 254 255 return SVN_NO_ERROR; 256} 257 258 259/* This function's caller ignores most errors it returns. 260 If you extend this function, check the callsite to see if you have 261 to make it not-ignore additional error codes. */ 262svn_error_t * 263svn_fs_fs__get_rep_reference(representation_t **rep, 264 svn_fs_t *fs, 265 svn_checksum_t *checksum, 266 apr_pool_t *pool) 267{ 268 fs_fs_data_t *ffd = fs->fsap_data; 269 svn_sqlite__stmt_t *stmt; 270 svn_boolean_t have_row; 271 272 SVN_ERR_ASSERT(ffd->rep_sharing_allowed); 273 if (! ffd->rep_cache_db) 274 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 275 276 /* We only allow SHA1 checksums in this table. */ 277 if (checksum->kind != svn_checksum_sha1) 278 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, 279 _("Only SHA1 checksums can be used as keys in the " 280 "rep_cache table.\n")); 281 282 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP)); 283 SVN_ERR(svn_sqlite__bindf(stmt, "s", 284 svn_checksum_to_cstring(checksum, pool))); 285 286 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 287 if (have_row) 288 { 289 *rep = apr_pcalloc(pool, sizeof(**rep)); 290 svn_fs_fs__id_txn_reset(&(*rep)->txn_id); 291 memcpy((*rep)->sha1_digest, checksum->digest, 292 sizeof((*rep)->sha1_digest)); 293 (*rep)->has_sha1 = TRUE; 294 (*rep)->revision = svn_sqlite__column_revnum(stmt, 0); 295 (*rep)->item_index = svn_sqlite__column_int64(stmt, 1); 296 (*rep)->size = svn_sqlite__column_int64(stmt, 2); 297 (*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3); 298 } 299 else 300 *rep = NULL; 301 302 SVN_ERR(svn_sqlite__reset(stmt)); 303 304 if (*rep) 305 { 306 /* Check that REP refers to a revision that exists in FS. */ 307 svn_error_t *err = svn_fs_fs__ensure_revision_exists((*rep)->revision, 308 fs, 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, 313 pool)); 314 } 315 316 return SVN_NO_ERROR; 317} 318 319svn_error_t * 320svn_fs_fs__set_rep_reference(svn_fs_t *fs, 321 representation_t *rep, 322 apr_pool_t *pool) 323{ 324 fs_fs_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_fs__open_rep_cache(fs, 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, pool), 344 (apr_int64_t) rep->revision, 345 (apr_int64_t) rep->item_index, 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 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_fs__get_rep_reference(&old_rep, fs, &checksum, pool)); 363 364 if (!old_rep) 365 { 366 /* Something really odd at this point, we failed to insert the 367 checksum AND failed to read an existing checksum. Do we need 368 to flag this? */ 369 } 370 } 371 372 return SVN_NO_ERROR; 373} 374 375 376svn_error_t * 377svn_fs_fs__del_rep_reference(svn_fs_t *fs, 378 svn_revnum_t youngest, 379 apr_pool_t *pool) 380{ 381 fs_fs_data_t *ffd = fs->fsap_data; 382 svn_sqlite__stmt_t *stmt; 383 384 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT); 385 if (! ffd->rep_cache_db) 386 SVN_ERR(svn_fs_fs__open_rep_cache(fs, 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 fs_fs_data_t *ffd = fs->fsap_data; 405 406 if (! ffd->rep_cache_db) 407 SVN_ERR(svn_fs_fs__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 fs_fs_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_fs__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