]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_fs_fs/rep-cache.c
MFV: r329072
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_fs_fs / rep-cache.c
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
41 REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements);
42
43
44 \f
45 /** Helper functions. **/
46 static APR_INLINE const char *
47 path_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 \f
61 /** Library-private API's. **/
62
63 /* Body of svn_fs_fs__open_rep_cache().
64    Implements svn_atomic__init_once().init_func.
65  */
66 static svn_error_t *
67 open_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
124 svn_error_t *
125 svn_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_wrapf(err,
132                                _("Couldn't open rep-cache database '%s'"),
133                                svn_dirent_local_style(
134                                  path_rep_cache_db(fs->path, pool), pool));
135 }
136
137 svn_error_t *
138 svn_fs_fs__close_rep_cache(svn_fs_t *fs)
139 {
140   fs_fs_data_t *ffd = fs->fsap_data;
141
142   if (ffd->rep_cache_db)
143     {
144       SVN_ERR(svn_sqlite__close(ffd->rep_cache_db));
145       ffd->rep_cache_db = NULL;
146       ffd->rep_cache_db_opened = 0;
147     }
148
149   return SVN_NO_ERROR;
150 }
151
152 svn_error_t *
153 svn_fs_fs__exists_rep_cache(svn_boolean_t *exists,
154                             svn_fs_t *fs, apr_pool_t *pool)
155 {
156   svn_node_kind_t kind;
157
158   SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, pool),
159                             &kind, pool));
160
161   *exists = (kind != svn_node_none);
162   return SVN_NO_ERROR;
163 }
164
165 svn_error_t *
166 svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
167                               svn_revnum_t start,
168                               svn_revnum_t end,
169                               svn_error_t *(*walker)(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 *pool)
177 {
178   fs_fs_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(pool);
184
185   /* Don't check ffd->rep_sharing_allowed. */
186   SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
187
188   if (! ffd->rep_cache_db)
189     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
190
191   /* Check global invariants. */
192   if (start == 0)
193     {
194       svn_revnum_t max;
195
196       SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
197                                         STMT_GET_MAX_REV));
198       SVN_ERR(svn_sqlite__step(&have_row, stmt));
199       max = svn_sqlite__column_revnum(stmt, 0);
200       SVN_ERR(svn_sqlite__reset(stmt));
201       if (SVN_IS_VALID_REVNUM(max))  /* The rep-cache could be empty. */
202         SVN_ERR(svn_fs_fs__ensure_revision_exists(max, fs, iterpool));
203     }
204
205   SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
206                                     STMT_GET_REPS_FOR_RANGE));
207   SVN_ERR(svn_sqlite__bindf(stmt, "rr",
208                             start, end));
209
210   /* Walk the cache entries. */
211   SVN_ERR(svn_sqlite__step(&have_row, stmt));
212   while (have_row)
213     {
214       representation_t *rep;
215       const char *sha1_digest;
216       svn_error_t *err;
217       svn_checksum_t *checksum;
218
219       /* Clear ITERPOOL occasionally. */
220       if (iterations++ % 16 == 0)
221         svn_pool_clear(iterpool);
222
223       /* Check for cancellation. */
224       if (cancel_func)
225         {
226           err = cancel_func(cancel_baton);
227           if (err)
228             return svn_error_compose_create(err, svn_sqlite__reset(stmt));
229         }
230
231       /* Construct a representation_t. */
232       rep = apr_pcalloc(iterpool, sizeof(*rep));
233       svn_fs_fs__id_txn_reset(&rep->txn_id);
234       sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool);
235       err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
236                                    sha1_digest, iterpool);
237       if (err)
238         return svn_error_compose_create(err, svn_sqlite__reset(stmt));
239
240       rep->has_sha1 = TRUE;
241       memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
242       rep->revision = svn_sqlite__column_revnum(stmt, 1);
243       rep->item_index = svn_sqlite__column_int64(stmt, 2);
244       rep->size = svn_sqlite__column_int64(stmt, 3);
245       rep->expanded_size = svn_sqlite__column_int64(stmt, 4);
246
247       /* Walk. */
248       err = walker(rep, walker_baton, fs, iterpool);
249       if (err)
250         return svn_error_compose_create(err, svn_sqlite__reset(stmt));
251
252       SVN_ERR(svn_sqlite__step(&have_row, stmt));
253     }
254
255   SVN_ERR(svn_sqlite__reset(stmt));
256   svn_pool_destroy(iterpool);
257
258   return SVN_NO_ERROR;
259 }
260
261
262 /* This function's caller ignores most errors it returns.
263    If you extend this function, check the callsite to see if you have
264    to make it not-ignore additional error codes.  */
265 svn_error_t *
266 svn_fs_fs__get_rep_reference(representation_t **rep,
267                              svn_fs_t *fs,
268                              svn_checksum_t *checksum,
269                              apr_pool_t *pool)
270 {
271   fs_fs_data_t *ffd = fs->fsap_data;
272   svn_sqlite__stmt_t *stmt;
273   svn_boolean_t have_row;
274
275   SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
276   if (! ffd->rep_cache_db)
277     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
278
279   /* We only allow SHA1 checksums in this table. */
280   if (checksum->kind != svn_checksum_sha1)
281     return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
282                             _("Only SHA1 checksums can be used as keys in the "
283                               "rep_cache table.\n"));
284
285   SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP));
286   SVN_ERR(svn_sqlite__bindf(stmt, "s",
287                             svn_checksum_to_cstring(checksum, pool)));
288
289   SVN_ERR(svn_sqlite__step(&have_row, stmt));
290   if (have_row)
291     {
292       *rep = apr_pcalloc(pool, sizeof(**rep));
293       svn_fs_fs__id_txn_reset(&(*rep)->txn_id);
294       memcpy((*rep)->sha1_digest, checksum->digest,
295              sizeof((*rep)->sha1_digest));
296       (*rep)->has_sha1 = TRUE;
297       (*rep)->revision = svn_sqlite__column_revnum(stmt, 0);
298       (*rep)->item_index = svn_sqlite__column_int64(stmt, 1);
299       (*rep)->size = svn_sqlite__column_int64(stmt, 2);
300       (*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3);
301     }
302   else
303     *rep = NULL;
304
305   SVN_ERR(svn_sqlite__reset(stmt));
306
307   if (*rep)
308     {
309       /* Check that REP refers to a revision that exists in FS. */
310       svn_error_t *err = svn_fs_fs__ensure_revision_exists((*rep)->revision,
311                                                            fs, pool);
312       if (err)
313         return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
314                                  "Checksum '%s' in rep-cache is beyond HEAD",
315                                  svn_checksum_to_cstring_display(checksum,
316                                                                  pool));
317     }
318
319   return SVN_NO_ERROR;
320 }
321
322 svn_error_t *
323 svn_fs_fs__set_rep_reference(svn_fs_t *fs,
324                              representation_t *rep,
325                              apr_pool_t *pool)
326 {
327   fs_fs_data_t *ffd = fs->fsap_data;
328   svn_sqlite__stmt_t *stmt;
329   svn_error_t *err;
330   svn_checksum_t checksum;
331   checksum.kind = svn_checksum_sha1;
332   checksum.digest = rep->sha1_digest;
333
334   SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
335   if (! ffd->rep_cache_db)
336     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
337
338   /* We only allow SHA1 checksums in this table. */
339   if (! rep->has_sha1)
340     return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
341                             _("Only SHA1 checksums can be used as keys in the "
342                               "rep_cache table.\n"));
343
344   SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_SET_REP));
345   SVN_ERR(svn_sqlite__bindf(stmt, "siiii",
346                             svn_checksum_to_cstring(&checksum, pool),
347                             (apr_int64_t) rep->revision,
348                             (apr_int64_t) rep->item_index,
349                             (apr_int64_t) rep->size,
350                             (apr_int64_t) rep->expanded_size));
351
352   err = svn_sqlite__insert(NULL, stmt);
353   if (err)
354     {
355       representation_t *old_rep;
356
357       if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT)
358         return svn_error_trace(err);
359
360       svn_error_clear(err);
361
362       /* Constraint failed so the mapping for SHA1_CHECKSUM->REP
363          should exist.  If so that's cool -- just do nothing.  If not,
364          that's a red flag!  */
365       SVN_ERR(svn_fs_fs__get_rep_reference(&old_rep, fs, &checksum, pool));
366
367       if (!old_rep)
368         {
369           /* Something really odd at this point, we failed to insert the
370              checksum AND failed to read an existing checksum.  Do we need
371              to flag this? */
372         }
373     }
374
375   return SVN_NO_ERROR;
376 }
377
378
379 svn_error_t *
380 svn_fs_fs__del_rep_reference(svn_fs_t *fs,
381                              svn_revnum_t youngest,
382                              apr_pool_t *pool)
383 {
384   fs_fs_data_t *ffd = fs->fsap_data;
385   svn_sqlite__stmt_t *stmt;
386
387   SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
388   if (! ffd->rep_cache_db)
389     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
390
391   SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
392                                     STMT_DEL_REPS_YOUNGER_THAN_REV));
393   SVN_ERR(svn_sqlite__bindf(stmt, "r", youngest));
394   SVN_ERR(svn_sqlite__step_done(stmt));
395
396   return SVN_NO_ERROR;
397 }
398
399 /* Start a transaction to take an SQLite reserved lock that prevents
400    other writes.
401
402    See unlock_rep_cache(). */
403 static svn_error_t *
404 lock_rep_cache(svn_fs_t *fs,
405                apr_pool_t *pool)
406 {
407   fs_fs_data_t *ffd = fs->fsap_data;
408
409   if (! ffd->rep_cache_db)
410     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
411
412   SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP));
413
414   return SVN_NO_ERROR;
415 }
416
417 /* End the transaction started by lock_rep_cache(). */
418 static svn_error_t *
419 unlock_rep_cache(svn_fs_t *fs,
420                  apr_pool_t *pool)
421 {
422   fs_fs_data_t *ffd = fs->fsap_data;
423
424   SVN_ERR_ASSERT(ffd->rep_cache_db); /* was opened by lock_rep_cache() */
425
426   SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_UNLOCK_REP));
427
428   return SVN_NO_ERROR;
429 }
430
431 svn_error_t *
432 svn_fs_fs__with_rep_cache_lock(svn_fs_t *fs,
433                                svn_error_t *(*body)(void *,
434                                                     apr_pool_t *),
435                                void *baton,
436                                apr_pool_t *pool)
437 {
438   svn_error_t *err;
439
440   SVN_ERR(lock_rep_cache(fs, pool));
441   err = body(baton, pool);
442   return svn_error_compose_create(err, unlock_rep_cache(fs, pool));
443 }