]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_fs_x/rep-cache.c
Merge lld trunk r338150 (just before the 7.0.0 branch point), and
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_fs_x / rep-cache.c
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 \f
46 /** Helper functions. **/
47 static APR_INLINE const char *
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 \f
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 *
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_SQLITE__ERR_CLOSE(svn_sqlite__read_schema_version(&version, sdb,
104                                                         scratch_pool),
105                         sdb);
106   if (version < REP_CACHE_SCHEMA_FORMAT)
107     {
108       /* Must be 0 -- an uninitialized (no schema) database. Create
109          the schema. Results in schema version of 1.  */
110       SVN_SQLITE__ERR_CLOSE(svn_sqlite__exec_statements(sdb,
111                                                         STMT_CREATE_SCHEMA),
112                             sdb);
113     }
114
115   /* This is used as a flag that the database is available so don't
116      set it earlier. */
117   ffd->rep_cache_db = sdb;
118
119   return SVN_NO_ERROR;
120 }
121
122 svn_error_t *
123 svn_fs_x__open_rep_cache(svn_fs_t *fs,
124                          apr_pool_t *scratch_pool)
125 {
126   svn_fs_x__data_t *ffd = fs->fsap_data;
127   svn_error_t *err = svn_atomic__init_once(&ffd->rep_cache_db_opened,
128                                            open_rep_cache, fs, scratch_pool);
129   return svn_error_quick_wrapf(err,
130                                _("Couldn't open rep-cache database '%s'"),
131                                svn_dirent_local_style(
132                                  path_rep_cache_db(fs->path, scratch_pool),
133                                  scratch_pool));
134 }
135
136 svn_error_t *
137 svn_fs_x__close_rep_cache(svn_fs_t *fs)
138 {
139   svn_fs_x__data_t *ffd = fs->fsap_data;
140
141   if (ffd->rep_cache_db)
142     {
143       SVN_ERR(svn_sqlite__close(ffd->rep_cache_db));
144       ffd->rep_cache_db = NULL;
145       ffd->rep_cache_db_opened = 0;
146     }
147
148   return SVN_NO_ERROR;
149 }
150
151 svn_error_t *
152 svn_fs_x__exists_rep_cache(svn_boolean_t *exists,
153                            svn_fs_t *fs,
154                            apr_pool_t *scratch_pool)
155 {
156   svn_node_kind_t kind;
157
158   SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, scratch_pool),
159                             &kind, scratch_pool));
160
161   *exists = (kind != svn_node_none);
162   return SVN_NO_ERROR;
163 }
164
165 svn_error_t *
166 svn_fs_x__walk_rep_reference(svn_fs_t *fs,
167                              svn_revnum_t start,
168                              svn_revnum_t end,
169                              svn_error_t *(*walker)(svn_fs_x__representation_t *,
170                                                     void *,
171                                                     svn_fs_t *,
172                                                     apr_pool_t *),
173                              void *walker_baton,
174                              svn_cancel_func_t cancel_func,
175                              void *cancel_baton,
176                              apr_pool_t *scratch_pool)
177 {
178   svn_fs_x__data_t *ffd = fs->fsap_data;
179   svn_sqlite__stmt_t *stmt;
180   svn_boolean_t have_row;
181   int iterations = 0;
182
183   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
184
185   if (! ffd->rep_cache_db)
186     SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_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_x__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       svn_fs_x__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 svn_fs_x__representation_t. */
229       rep = apr_pcalloc(iterpool, sizeof(*rep));
230       sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool);
231       err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
232                                    sha1_digest, iterpool);
233       if (err)
234         return svn_error_compose_create(err, svn_sqlite__reset(stmt));
235
236       rep->has_sha1 = TRUE;
237       memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
238       rep->id.change_set = svn_sqlite__column_revnum(stmt, 1);
239       rep->id.number = svn_sqlite__column_int64(stmt, 2);
240       rep->size = svn_sqlite__column_int64(stmt, 3);
241       rep->expanded_size = svn_sqlite__column_int64(stmt, 4);
242
243       /* Walk. */
244       err = walker(rep, walker_baton, fs, iterpool);
245       if (err)
246         return svn_error_compose_create(err, svn_sqlite__reset(stmt));
247
248       SVN_ERR(svn_sqlite__step(&have_row, stmt));
249     }
250
251   SVN_ERR(svn_sqlite__reset(stmt));
252   svn_pool_destroy(iterpool);
253
254   return SVN_NO_ERROR;
255 }
256
257
258 /* This function's caller ignores most errors it returns.
259    If you extend this function, check the callsite to see if you have
260    to make it not-ignore additional error codes.  */
261 svn_error_t *
262 svn_fs_x__get_rep_reference(svn_fs_x__representation_t **rep_p,
263                             svn_fs_t *fs,
264                             svn_checksum_t *checksum,
265                             apr_pool_t *result_pool,
266                             apr_pool_t *scratch_pool)
267 {
268   svn_fs_x__data_t *ffd = fs->fsap_data;
269   svn_sqlite__stmt_t *stmt;
270   svn_boolean_t have_row;
271   svn_fs_x__representation_t *rep;
272
273   SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
274   if (! ffd->rep_cache_db)
275     SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_pool));
276
277   /* We only allow SHA1 checksums in this table. */
278   if (checksum->kind != svn_checksum_sha1)
279     return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
280                             _("Only SHA1 checksums can be used as keys in the "
281                               "rep_cache table.\n"));
282
283   SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP));
284   SVN_ERR(svn_sqlite__bindf(stmt, "s",
285                             svn_checksum_to_cstring(checksum, scratch_pool)));
286
287   SVN_ERR(svn_sqlite__step(&have_row, stmt));
288   if (have_row)
289     {
290       rep = apr_pcalloc(result_pool, sizeof(*rep));
291       memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
292       rep->has_sha1 = TRUE;
293       rep->id.change_set = svn_sqlite__column_revnum(stmt, 0);
294       rep->id.number = svn_sqlite__column_int64(stmt, 1);
295       rep->size = svn_sqlite__column_int64(stmt, 2);
296       rep->expanded_size = svn_sqlite__column_int64(stmt, 3);
297     }
298   else
299     rep = NULL;
300
301   SVN_ERR(svn_sqlite__reset(stmt));
302
303   if (rep)
304     {
305       /* Check that REP refers to a revision that exists in FS. */
306       svn_revnum_t revision = svn_fs_x__get_revnum(rep->id.change_set);
307       svn_error_t *err = svn_fs_x__ensure_revision_exists(revision, fs,
308                                                           scratch_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, scratch_pool));
313     }
314
315   *rep_p = rep;
316   return SVN_NO_ERROR;
317 }
318
319 svn_error_t *
320 svn_fs_x__set_rep_reference(svn_fs_t *fs,
321                             svn_fs_x__representation_t *rep,
322                             apr_pool_t *scratch_pool)
323 {
324   svn_fs_x__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_x__open_rep_cache(fs, scratch_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, scratch_pool),
344                             (apr_int64_t) rep->id.change_set,
345                             (apr_int64_t) rep->id.number,
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       svn_fs_x__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_x__get_rep_reference(&old_rep, fs, &checksum,
363                                           scratch_pool, scratch_pool));
364
365       if (!old_rep)
366         {
367           /* Something really odd at this point, we failed to insert the
368              checksum AND failed to read an existing checksum.  Do we need
369              to flag this? */
370         }
371     }
372
373   return SVN_NO_ERROR;
374 }
375
376
377 svn_error_t *
378 svn_fs_x__del_rep_reference(svn_fs_t *fs,
379                             svn_revnum_t youngest,
380                             apr_pool_t *scratch_pool)
381 {
382   svn_fs_x__data_t *ffd = fs->fsap_data;
383   svn_sqlite__stmt_t *stmt;
384
385   if (! ffd->rep_cache_db)
386     SVN_ERR(svn_fs_x__open_rep_cache(fs, scratch_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(). */
400 static svn_error_t *
401 lock_rep_cache(svn_fs_t *fs,
402                apr_pool_t *pool)
403 {
404   svn_fs_x__data_t *ffd = fs->fsap_data;
405
406   if (! ffd->rep_cache_db)
407     SVN_ERR(svn_fs_x__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(). */
415 static svn_error_t *
416 unlock_rep_cache(svn_fs_t *fs,
417                  apr_pool_t *pool)
418 {
419   svn_fs_x__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
428 svn_error_t *
429 svn_fs_x__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 }