rep-cache.c revision 256281
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 SVN_ERR(svn_sqlite__open(&sdb, db_path, 87 svn_sqlite__mode_rwcreate, statements, 88 0, NULL, 89 fs->pool, pool)); 90 91 SVN_ERR(svn_sqlite__read_schema_version(&version, sdb, pool)); 92 if (version < REP_CACHE_SCHEMA_FORMAT) 93 { 94 /* Must be 0 -- an uninitialized (no schema) database. Create 95 the schema. Results in schema version of 1. */ 96 SVN_ERR(svn_sqlite__exec_statements(sdb, STMT_CREATE_SCHEMA)); 97 } 98 99 /* This is used as a flag that the database is available so don't 100 set it earlier. */ 101 ffd->rep_cache_db = sdb; 102 103 return SVN_NO_ERROR; 104} 105 106svn_error_t * 107svn_fs_fs__open_rep_cache(svn_fs_t *fs, 108 apr_pool_t *pool) 109{ 110 fs_fs_data_t *ffd = fs->fsap_data; 111 svn_error_t *err = svn_atomic__init_once(&ffd->rep_cache_db_opened, 112 open_rep_cache, fs, pool); 113 return svn_error_quick_wrap(err, _("Couldn't open rep-cache database")); 114} 115 116svn_error_t * 117svn_fs_fs__exists_rep_cache(svn_boolean_t *exists, 118 svn_fs_t *fs, apr_pool_t *pool) 119{ 120 svn_node_kind_t kind; 121 122 SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, pool), 123 &kind, pool)); 124 125 *exists = (kind != svn_node_none); 126 return SVN_NO_ERROR; 127} 128 129svn_error_t * 130svn_fs_fs__walk_rep_reference(svn_fs_t *fs, 131 svn_revnum_t start, 132 svn_revnum_t end, 133 svn_error_t *(*walker)(representation_t *, 134 void *, 135 svn_fs_t *, 136 apr_pool_t *), 137 void *walker_baton, 138 svn_cancel_func_t cancel_func, 139 void *cancel_baton, 140 apr_pool_t *pool) 141{ 142 fs_fs_data_t *ffd = fs->fsap_data; 143 svn_sqlite__stmt_t *stmt; 144 svn_boolean_t have_row; 145 int iterations = 0; 146 147 apr_pool_t *iterpool = svn_pool_create(pool); 148 149 /* Don't check ffd->rep_sharing_allowed. */ 150 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT); 151 152 if (! ffd->rep_cache_db) 153 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 154 155 /* Check global invariants. */ 156 if (start == 0) 157 { 158 svn_revnum_t max; 159 160 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, 161 STMT_GET_MAX_REV)); 162 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 163 max = svn_sqlite__column_revnum(stmt, 0); 164 SVN_ERR(svn_sqlite__reset(stmt)); 165 if (SVN_IS_VALID_REVNUM(max)) /* The rep-cache could be empty. */ 166 SVN_ERR(svn_fs_fs__revision_exists(max, fs, iterpool)); 167 } 168 169 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, 170 STMT_GET_REPS_FOR_RANGE)); 171 SVN_ERR(svn_sqlite__bindf(stmt, "rr", 172 start, end)); 173 174 /* Walk the cache entries. */ 175 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 176 while (have_row) 177 { 178 representation_t *rep; 179 const char *sha1_digest; 180 svn_error_t *err; 181 182 /* Clear ITERPOOL occasionally. */ 183 if (iterations++ % 16 == 0) 184 svn_pool_clear(iterpool); 185 186 /* Check for cancellation. */ 187 if (cancel_func) 188 { 189 err = cancel_func(cancel_baton); 190 if (err) 191 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 192 } 193 194 /* Construct a representation_t. */ 195 rep = apr_pcalloc(iterpool, sizeof(*rep)); 196 sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool); 197 err = svn_checksum_parse_hex(&rep->sha1_checksum, 198 svn_checksum_sha1, sha1_digest, 199 iterpool); 200 if (err) 201 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 202 rep->revision = svn_sqlite__column_revnum(stmt, 1); 203 rep->offset = svn_sqlite__column_int64(stmt, 2); 204 rep->size = svn_sqlite__column_int64(stmt, 3); 205 rep->expanded_size = svn_sqlite__column_int64(stmt, 4); 206 207 /* Walk. */ 208 err = walker(rep, walker_baton, fs, iterpool); 209 if (err) 210 return svn_error_compose_create(err, svn_sqlite__reset(stmt)); 211 212 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 213 } 214 215 SVN_ERR(svn_sqlite__reset(stmt)); 216 svn_pool_destroy(iterpool); 217 218 return SVN_NO_ERROR; 219} 220 221 222/* This function's caller ignores most errors it returns. 223 If you extend this function, check the callsite to see if you have 224 to make it not-ignore additional error codes. */ 225svn_error_t * 226svn_fs_fs__get_rep_reference(representation_t **rep, 227 svn_fs_t *fs, 228 svn_checksum_t *checksum, 229 apr_pool_t *pool) 230{ 231 fs_fs_data_t *ffd = fs->fsap_data; 232 svn_sqlite__stmt_t *stmt; 233 svn_boolean_t have_row; 234 235 SVN_ERR_ASSERT(ffd->rep_sharing_allowed); 236 if (! ffd->rep_cache_db) 237 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 238 239 /* We only allow SHA1 checksums in this table. */ 240 if (checksum->kind != svn_checksum_sha1) 241 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, 242 _("Only SHA1 checksums can be used as keys in the " 243 "rep_cache table.\n")); 244 245 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP)); 246 SVN_ERR(svn_sqlite__bindf(stmt, "s", 247 svn_checksum_to_cstring(checksum, pool))); 248 249 SVN_ERR(svn_sqlite__step(&have_row, stmt)); 250 if (have_row) 251 { 252 *rep = apr_pcalloc(pool, sizeof(**rep)); 253 (*rep)->sha1_checksum = svn_checksum_dup(checksum, pool); 254 (*rep)->revision = svn_sqlite__column_revnum(stmt, 0); 255 (*rep)->offset = svn_sqlite__column_int64(stmt, 1); 256 (*rep)->size = svn_sqlite__column_int64(stmt, 2); 257 (*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3); 258 } 259 else 260 *rep = NULL; 261 262 SVN_ERR(svn_sqlite__reset(stmt)); 263 264 if (*rep) 265 SVN_ERR(rep_has_been_born(*rep, fs, pool)); 266 267 return SVN_NO_ERROR; 268} 269 270svn_error_t * 271svn_fs_fs__set_rep_reference(svn_fs_t *fs, 272 representation_t *rep, 273 svn_boolean_t reject_dup, 274 apr_pool_t *pool) 275{ 276 fs_fs_data_t *ffd = fs->fsap_data; 277 svn_sqlite__stmt_t *stmt; 278 svn_error_t *err; 279 280 SVN_ERR_ASSERT(ffd->rep_sharing_allowed); 281 if (! ffd->rep_cache_db) 282 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 283 284 /* We only allow SHA1 checksums in this table. */ 285 if (rep->sha1_checksum == NULL) 286 return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, 287 _("Only SHA1 checksums can be used as keys in the " 288 "rep_cache table.\n")); 289 290 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_SET_REP)); 291 SVN_ERR(svn_sqlite__bindf(stmt, "siiii", 292 svn_checksum_to_cstring(rep->sha1_checksum, pool), 293 (apr_int64_t) rep->revision, 294 (apr_int64_t) rep->offset, 295 (apr_int64_t) rep->size, 296 (apr_int64_t) rep->expanded_size)); 297 298 err = svn_sqlite__insert(NULL, stmt); 299 if (err) 300 { 301 representation_t *old_rep; 302 303 if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT) 304 return svn_error_trace(err); 305 306 svn_error_clear(err); 307 308 /* Constraint failed so the mapping for SHA1_CHECKSUM->REP 309 should exist. If so, and the value is the same one we were 310 about to write, that's cool -- just do nothing. If, however, 311 the value is *different*, that's a red flag! */ 312 SVN_ERR(svn_fs_fs__get_rep_reference(&old_rep, fs, rep->sha1_checksum, 313 pool)); 314 315 if (old_rep) 316 { 317 if (reject_dup && ((old_rep->revision != rep->revision) 318 || (old_rep->offset != rep->offset) 319 || (old_rep->size != rep->size) 320 || (old_rep->expanded_size != rep->expanded_size))) 321 return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, 322 apr_psprintf(pool, 323 _("Representation key for checksum '%%s' exists " 324 "in filesystem '%%s' with a different value " 325 "(%%ld,%%%s,%%%s,%%%s) than what we were about " 326 "to store (%%ld,%%%s,%%%s,%%%s)"), 327 APR_OFF_T_FMT, SVN_FILESIZE_T_FMT, 328 SVN_FILESIZE_T_FMT, APR_OFF_T_FMT, 329 SVN_FILESIZE_T_FMT, SVN_FILESIZE_T_FMT), 330 svn_checksum_to_cstring_display(rep->sha1_checksum, pool), 331 fs->path, old_rep->revision, old_rep->offset, old_rep->size, 332 old_rep->expanded_size, rep->revision, rep->offset, rep->size, 333 rep->expanded_size); 334 else 335 return SVN_NO_ERROR; 336 } 337 else 338 { 339 /* Something really odd at this point, we failed to insert the 340 checksum AND failed to read an existing checksum. Do we need 341 to flag this? */ 342 } 343 } 344 345 return SVN_NO_ERROR; 346} 347 348 349svn_error_t * 350svn_fs_fs__del_rep_reference(svn_fs_t *fs, 351 svn_revnum_t youngest, 352 apr_pool_t *pool) 353{ 354 fs_fs_data_t *ffd = fs->fsap_data; 355 svn_sqlite__stmt_t *stmt; 356 357 SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT); 358 if (! ffd->rep_cache_db) 359 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 360 361 SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, 362 STMT_DEL_REPS_YOUNGER_THAN_REV)); 363 SVN_ERR(svn_sqlite__bindf(stmt, "r", youngest)); 364 SVN_ERR(svn_sqlite__step_done(stmt)); 365 366 return SVN_NO_ERROR; 367} 368 369svn_error_t * 370svn_fs_fs__lock_rep_cache(svn_fs_t *fs, 371 apr_pool_t *pool) 372{ 373 fs_fs_data_t *ffd = fs->fsap_data; 374 375 if (! ffd->rep_cache_db) 376 SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); 377 378 SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP)); 379 380 return SVN_NO_ERROR; 381} 382