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/* Check that REP refers to a revision that exists in FS. */ 54static svn_error_t * 55rep_has_been_born(representation_t *rep, 56 svn_fs_t *fs, 57 apr_pool_t *pool) 58{ 59 SVN_ERR_ASSERT(rep); 60 61 SVN_ERR(svn_fs_fs__revision_exists(rep->revision, fs, pool)); 62 63 return SVN_NO_ERROR; 64} 65 66 67 68/** Library-private API's. **/ 69 70/* Body of svn_fs_fs__open_rep_cache(). 71 Implements svn_atomic__init_once().init_func. 72 */ 73static svn_error_t * 74open_rep_cache(void *baton, 75 apr_pool_t *pool) 76{ 77 svn_fs_t *fs = baton; 78 fs_fs_data_t *ffd = fs->fsap_data; 79 svn_sqlite__db_t *sdb; 80 const char *db_path; 81 int version; 82 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); 86#ifndef WIN32 87 { 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 90 to umask. */ 91 svn_boolean_t exists; 92 93 SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool)); 94 if (!exists) 95 { 96 const char *current = svn_fs_fs__path_current(fs, pool); 97 svn_error_t *err = svn_io_file_create(db_path, "", pool); 98 99 if (err && !APR_STATUS_IS_EEXIST(err->apr_err)) 100 /* A real error. */ 101 return svn_error_trace(err); 102 else if (err) 103 /* Some other thread/process created the file. */ 104 svn_error_clear(err); 105 else 106 /* We created the file. */ 107 SVN_ERR(svn_io_copy_perms(current, db_path, pool)); 108 } 109 } 110#endif 111 SVN_ERR(svn_sqlite__open(&sdb, db_path, 112 svn_sqlite__mode_rwcreate, statements, 113 0, NULL, 114 fs->pool, pool)); 115 116 SVN_ERR(svn_sqlite__read_schema_version(&version, sdb, pool)); 117 if (version < REP_CACHE_SCHEMA_FORMAT) 118 { 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)); 122 } 123 124 /* This is used as a flag that the database is available so don't 125 set it earlier. */ 126 ffd->rep_cache_db = sdb; 127 128 return SVN_NO_ERROR; 129} 130 131svn_error_t * 132svn_fs_fs__open_rep_cache(svn_fs_t *fs, 133 apr_pool_t *pool) 134{ 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")); 139} 140 141svn_error_t * 142svn_fs_fs__exists_rep_cache(svn_boolean_t *exists, 143 svn_fs_t *fs, apr_pool_t *pool) 144{ 145 svn_node_kind_t kind; 146 147 SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, pool), 148 &kind, pool)); 149 150 *exists = (kind != svn_node_none); 151 return SVN_NO_ERROR; 152} 153 154svn_error_t * 155svn_fs_fs__walk_rep_reference(svn_fs_t *fs, 156 svn_revnum_t start, 157 svn_revnum_t end, 158 svn_error_t *(*walker)(representation_t *, 159 void *, 160 svn_fs_t *, 161 apr_pool_t *), 162 void *walker_baton, 163 svn_cancel_func_t cancel_func, 164 void *cancel_baton, 165 apr_pool_t *pool) 166{ 167 fs_fs_data_t *ffd = fs->fsap_data; 168 svn_sqlite__stmt_t *stmt; 169 svn_boolean_t have_row; 170 int iterations = 0; 171 172 apr_pool_t *iterpool = svn_pool_create(pool); 173 174 /* Don't check ffd->rep_sharing_allowed. */ 175 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT); 176 177 if (! ffd->rep_cache_db) 178 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 179 180 /* Check global invariants. */ 181 if (start == 0) 182 { 183 svn_revnum_t max; 184 185 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, 186 STMT_GET_MAX_REV)); 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)); 192 } 193 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", 197 start, end)); 198 199 /* Walk the cache entries. */ 200 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 201 while (have_row) 202 { 203 representation_t *rep; 204 const char *sha1_digest; 205 svn_error_t *err; 206 207 /* Clear ITERPOOL occasionally. */ 208 if (iterations++ % 16 == 0) 209 svn_pool_clear(iterpool); 210 211 /* Check for cancellation. */ 212 if (cancel_func) 213 { 214 err = cancel_func(cancel_baton); 215 if (err) 216 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 217 } 218 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, 224 iterpool); 225 if (err) 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); 231 232 /* Walk. */ 233 err = walker(rep, walker_baton, fs, iterpool); 234 if (err) 235 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 236 237 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 238 } 239 240 SVN_ERR(svn_sqlite__reset(stmt)); 241 svn_pool_destroy(iterpool); 242 243 return SVN_NO_ERROR; 244} 245 246 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. */ 250svn_error_t * 251svn_fs_fs__get_rep_reference(representation_t **rep, 252 svn_fs_t *fs, 253 svn_checksum_t *checksum, 254 apr_pool_t *pool) 255{ 256 fs_fs_data_t *ffd = fs->fsap_data; 257 svn_sqlite__stmt_t *stmt; 258 svn_boolean_t have_row; 259 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)); 263 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")); 269 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))); 273 274 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 275 if (have_row) 276 { 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); 283 } 284 else 285 *rep = NULL; 286 287 SVN_ERR(svn_sqlite__reset(stmt)); 288 289 if (*rep) 290 SVN_ERR(rep_has_been_born(*rep, fs, pool)); 291 292 return SVN_NO_ERROR; 293} 294 295svn_error_t * 296svn_fs_fs__set_rep_reference(svn_fs_t *fs, 297 representation_t *rep, 298 svn_boolean_t reject_dup, 299 apr_pool_t *pool) 300{ 301 fs_fs_data_t *ffd = fs->fsap_data; 302 svn_sqlite__stmt_t *stmt; 303 svn_error_t *err; 304 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)); 308 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")); 314 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)); 322 323 err = svn_sqlite__insert(NULL, stmt); 324 if (err) 325 { 326 representation_t *old_rep; 327 328 if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT) 329 return svn_error_trace(err); 330 331 svn_error_clear(err); 332 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, 338 pool)); 339 340 if (old_rep) 341 { 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, 347 apr_psprintf(pool, 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, 358 rep->expanded_size); 359 else 360 return SVN_NO_ERROR; 361 } 362 else 363 { 364 /* Something really odd at this point, we failed to insert the 365 checksum AND failed to read an existing checksum. Do we need 366 to flag this? */ 367 } 368 } 369 370 return SVN_NO_ERROR; 371} 372 373 374svn_error_t * 375svn_fs_fs__del_rep_reference(svn_fs_t *fs, 376 svn_revnum_t youngest, 377 apr_pool_t *pool) 378{ 379 fs_fs_data_t *ffd = fs->fsap_data; 380 svn_sqlite__stmt_t *stmt; 381 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)); 385 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)); 390 391 return SVN_NO_ERROR; 392} 393 394svn_error_t * 395svn_fs_fs__lock_rep_cache(svn_fs_t *fs, 396 apr_pool_t *pool) 397{ 398 fs_fs_data_t *ffd = fs->fsap_data; 399 400 if (! ffd->rep_cache_db) 401 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 402 403 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP)); 404 405 return SVN_NO_ERROR; 406} 407