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
42 REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements);
43
44
45
46 /** Helper functions. **/
47 static APR_INLINE const char *
path_rep_cache_db(const char * fs_path,apr_pool_t * result_pool)48 path_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 */
60 static svn_error_t *
open_rep_cache(void * baton,apr_pool_t * scratch_pool)61 open_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
118 svn_error_t *
svn_fs_x__open_rep_cache(svn_fs_t * fs,apr_pool_t * scratch_pool)119 svn_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
128 svn_error_t *
svn_fs_x__exists_rep_cache(svn_boolean_t * exists,svn_fs_t * fs,apr_pool_t * scratch_pool)129 svn_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
142 svn_error_t *
svn_fs_x__walk_rep_reference(svn_fs_t * fs,svn_revnum_t start,svn_revnum_t end,svn_error_t * (* walker)(svn_fs_x__representation_t *,void *,svn_fs_t *,apr_pool_t *),void * walker_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)143 svn_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. */
238 svn_error_t *
svn_fs_x__get_rep_reference(svn_fs_x__representation_t ** rep,svn_fs_t * fs,svn_checksum_t * checksum,apr_pool_t * result_pool,apr_pool_t * scratch_pool)239 svn_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
295 svn_error_t *
svn_fs_x__set_rep_reference(svn_fs_t * fs,svn_fs_x__representation_t * rep,apr_pool_t * scratch_pool)296 svn_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
353 svn_error_t *
svn_fs_x__del_rep_reference(svn_fs_t * fs,svn_revnum_t youngest,apr_pool_t * scratch_pool)354 svn_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(). */
376 static svn_error_t *
lock_rep_cache(svn_fs_t * fs,apr_pool_t * pool)377 lock_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(). */
391 static svn_error_t *
unlock_rep_cache(svn_fs_t * fs,apr_pool_t * pool)392 unlock_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
404 svn_error_t *
svn_fs_x__with_rep_cache_lock(svn_fs_t * fs,svn_error_t * (* body)(void *,apr_pool_t *),void * baton,apr_pool_t * pool)405 svn_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