]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_fs_fs/rep-cache.c
Update svn-1.9.7 to 1.10.0.
[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 "cached_data.h"
28 #include "fs_fs.h"
29 #include "fs.h"
30 #include "rep-cache.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 REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements);
40
41
42 \f
43 /** Helper functions. **/
44 static APR_INLINE const char *
45 path_rep_cache_db(const char *fs_path,
46                   apr_pool_t *result_pool)
47 {
48   return svn_dirent_join(fs_path, REP_CACHE_DB_NAME, result_pool);
49 }
50
51 \f
52 /** Library-private API's. **/
53
54 /* Body of svn_fs_fs__open_rep_cache().
55    Implements svn_atomic__init_once().init_func.
56  */
57 static svn_error_t *
58 open_rep_cache(void *baton,
59                apr_pool_t *pool)
60 {
61   svn_fs_t *fs = baton;
62   fs_fs_data_t *ffd = fs->fsap_data;
63   svn_sqlite__db_t *sdb;
64   const char *db_path;
65   int version;
66
67   /* Open (or create) the sqlite database.  It will be automatically
68      closed when fs->pool is destroyed. */
69   db_path = path_rep_cache_db(fs->path, pool);
70 #ifndef WIN32
71   {
72     /* We want to extend the permissions that apply to the repository
73        as a whole when creating a new rep cache and not simply default
74        to umask. */
75     svn_boolean_t exists;
76
77     SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
78     if (!exists)
79       {
80         const char *current = svn_fs_fs__path_current(fs, pool);
81         svn_error_t *err = svn_io_file_create_empty(db_path, pool);
82
83         if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
84           /* A real error. */
85           return svn_error_trace(err);
86         else if (err)
87           /* Some other thread/process created the file. */
88           svn_error_clear(err);
89         else
90           /* We created the file. */
91           SVN_ERR(svn_io_copy_perms(current, db_path, pool));
92       }
93   }
94 #endif
95   SVN_ERR(svn_sqlite__open(&sdb, db_path,
96                            svn_sqlite__mode_rwcreate, statements,
97                            0, NULL, 0,
98                            fs->pool, pool));
99
100   SVN_SQLITE__ERR_CLOSE(svn_sqlite__read_schema_version(&version, sdb, pool),
101                         sdb);
102   /* If we have an uninitialized database, go ahead and create the schema. */
103   if (version <= 0)
104     {
105       int stmt;
106
107       if (ffd->format >= SVN_FS_FS__MIN_REP_CACHE_SCHEMA_V2_FORMAT)
108         stmt = STMT_CREATE_SCHEMA_V2;
109       else
110         stmt = STMT_CREATE_SCHEMA_V1;
111
112       SVN_SQLITE__ERR_CLOSE(svn_sqlite__exec_statements(sdb, stmt), 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_fs__open_rep_cache(svn_fs_t *fs,
124                           apr_pool_t *pool)
125 {
126   fs_fs_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, 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, pool), pool));
133 }
134
135 svn_error_t *
136 svn_fs_fs__close_rep_cache(svn_fs_t *fs)
137 {
138   fs_fs_data_t *ffd = fs->fsap_data;
139
140   if (ffd->rep_cache_db)
141     {
142       SVN_ERR(svn_sqlite__close(ffd->rep_cache_db));
143       ffd->rep_cache_db = NULL;
144       ffd->rep_cache_db_opened = 0;
145     }
146
147   return SVN_NO_ERROR;
148 }
149
150 svn_error_t *
151 svn_fs_fs__exists_rep_cache(svn_boolean_t *exists,
152                             svn_fs_t *fs, apr_pool_t *pool)
153 {
154   svn_node_kind_t kind;
155
156   SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, pool),
157                             &kind, pool));
158
159   *exists = (kind != svn_node_none);
160   return SVN_NO_ERROR;
161 }
162
163 svn_error_t *
164 svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
165                               svn_revnum_t start,
166                               svn_revnum_t end,
167                               svn_error_t *(*walker)(representation_t *,
168                                                      void *,
169                                                      svn_fs_t *,
170                                                      apr_pool_t *),
171                               void *walker_baton,
172                               svn_cancel_func_t cancel_func,
173                               void *cancel_baton,
174                               apr_pool_t *pool)
175 {
176   fs_fs_data_t *ffd = fs->fsap_data;
177   svn_sqlite__stmt_t *stmt;
178   svn_boolean_t have_row;
179   int iterations = 0;
180
181   apr_pool_t *iterpool = svn_pool_create(pool);
182
183   /* Don't check ffd->rep_sharing_allowed. */
184   SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
185
186   if (! ffd->rep_cache_db)
187     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
188
189   /* Check global invariants. */
190   if (start == 0)
191     {
192       svn_revnum_t max;
193
194       SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
195                                         STMT_GET_MAX_REV));
196       SVN_ERR(svn_sqlite__step(&have_row, stmt));
197       max = svn_sqlite__column_revnum(stmt, 0);
198       SVN_ERR(svn_sqlite__reset(stmt));
199       if (SVN_IS_VALID_REVNUM(max))  /* The rep-cache could be empty. */
200         SVN_ERR(svn_fs_fs__ensure_revision_exists(max, fs, iterpool));
201     }
202
203   SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
204                                     STMT_GET_REPS_FOR_RANGE));
205   SVN_ERR(svn_sqlite__bindf(stmt, "rr",
206                             start, end));
207
208   /* Walk the cache entries. */
209   SVN_ERR(svn_sqlite__step(&have_row, stmt));
210   while (have_row)
211     {
212       representation_t *rep;
213       const char *sha1_digest;
214       svn_error_t *err;
215       svn_checksum_t *checksum;
216
217       /* Clear ITERPOOL occasionally. */
218       if (iterations++ % 16 == 0)
219         svn_pool_clear(iterpool);
220
221       /* Check for cancellation. */
222       if (cancel_func)
223         {
224           err = cancel_func(cancel_baton);
225           if (err)
226             return svn_error_compose_create(err, svn_sqlite__reset(stmt));
227         }
228
229       /* Construct a representation_t. */
230       rep = apr_pcalloc(iterpool, sizeof(*rep));
231       svn_fs_fs__id_txn_reset(&rep->txn_id);
232       sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool);
233       err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
234                                    sha1_digest, iterpool);
235       if (err)
236         return svn_error_compose_create(err, svn_sqlite__reset(stmt));
237
238       rep->has_sha1 = TRUE;
239       memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
240       rep->revision = svn_sqlite__column_revnum(stmt, 1);
241       rep->item_index = svn_sqlite__column_int64(stmt, 2);
242       rep->size = svn_sqlite__column_int64(stmt, 3);
243       rep->expanded_size = svn_sqlite__column_int64(stmt, 4);
244
245       /* Walk. */
246       err = walker(rep, walker_baton, fs, iterpool);
247       if (err)
248         return svn_error_compose_create(err, svn_sqlite__reset(stmt));
249
250       SVN_ERR(svn_sqlite__step(&have_row, stmt));
251     }
252
253   SVN_ERR(svn_sqlite__reset(stmt));
254   svn_pool_destroy(iterpool);
255
256   return SVN_NO_ERROR;
257 }
258
259
260 /* This function's caller ignores most errors it returns.
261    If you extend this function, check the callsite to see if you have
262    to make it not-ignore additional error codes.  */
263 svn_error_t *
264 svn_fs_fs__get_rep_reference(representation_t **rep_p,
265                              svn_fs_t *fs,
266                              svn_checksum_t *checksum,
267                              apr_pool_t *pool)
268 {
269   fs_fs_data_t *ffd = fs->fsap_data;
270   svn_sqlite__stmt_t *stmt;
271   svn_boolean_t have_row;
272   representation_t *rep;
273
274   SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
275   if (! ffd->rep_cache_db)
276     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
277
278   /* We only allow SHA1 checksums in this table. */
279   if (checksum->kind != svn_checksum_sha1)
280     return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
281                             _("Only SHA1 checksums can be used as keys in the "
282                               "rep_cache table.\n"));
283
284   SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP));
285   SVN_ERR(svn_sqlite__bindf(stmt, "s",
286                             svn_checksum_to_cstring(checksum, pool)));
287
288   SVN_ERR(svn_sqlite__step(&have_row, stmt));
289   if (have_row)
290     {
291       rep = apr_pcalloc(pool, sizeof(*rep));
292       svn_fs_fs__id_txn_reset(&(rep->txn_id));
293       memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
294       rep->has_sha1 = TRUE;
295       rep->revision = svn_sqlite__column_revnum(stmt, 0);
296       rep->item_index = svn_sqlite__column_int64(stmt, 1);
297       rep->size = svn_sqlite__column_int64(stmt, 2);
298       rep->expanded_size = svn_sqlite__column_int64(stmt, 3);
299     }
300   else
301     rep = NULL;
302
303   SVN_ERR(svn_sqlite__reset(stmt));
304
305   if (rep)
306     {
307       svn_error_t *err;
308
309       SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, rep, pool));
310
311       /* Check that REP refers to a revision that exists in FS. */
312       err = svn_fs_fs__ensure_revision_exists(rep->revision, fs, pool);
313       if (err)
314         return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
315                                  "Checksum '%s' in rep-cache is beyond HEAD",
316                                  svn_checksum_to_cstring_display(checksum,
317                                                                  pool));
318     }
319
320   *rep_p = rep;
321   return SVN_NO_ERROR;
322 }
323
324 svn_error_t *
325 svn_fs_fs__set_rep_reference(svn_fs_t *fs,
326                              representation_t *rep,
327                              apr_pool_t *pool)
328 {
329   fs_fs_data_t *ffd = fs->fsap_data;
330   svn_sqlite__stmt_t *stmt;
331   svn_error_t *err;
332   svn_checksum_t checksum;
333   checksum.kind = svn_checksum_sha1;
334   checksum.digest = rep->sha1_digest;
335
336   SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
337   if (! ffd->rep_cache_db)
338     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
339
340   /* We only allow SHA1 checksums in this table. */
341   if (! rep->has_sha1)
342     return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
343                             _("Only SHA1 checksums can be used as keys in the "
344                               "rep_cache table.\n"));
345
346   SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_SET_REP));
347   SVN_ERR(svn_sqlite__bindf(stmt, "siiii",
348                             svn_checksum_to_cstring(&checksum, pool),
349                             (apr_int64_t) rep->revision,
350                             (apr_int64_t) rep->item_index,
351                             (apr_int64_t) rep->size,
352                             (apr_int64_t) rep->expanded_size));
353
354   err = svn_sqlite__insert(NULL, stmt);
355   if (err)
356     {
357       representation_t *old_rep;
358
359       if (err->apr_err != SVN_ERR_SQLITE_CONSTRAINT)
360         return svn_error_trace(err);
361
362       svn_error_clear(err);
363
364       /* Constraint failed so the mapping for SHA1_CHECKSUM->REP
365          should exist.  If so that's cool -- just do nothing.  If not,
366          that's a red flag!  */
367       SVN_ERR(svn_fs_fs__get_rep_reference(&old_rep, fs, &checksum, pool));
368
369       if (!old_rep)
370         {
371           /* Something really odd at this point, we failed to insert the
372              checksum AND failed to read an existing checksum.  Do we need
373              to flag this? */
374         }
375     }
376
377   return SVN_NO_ERROR;
378 }
379
380
381 svn_error_t *
382 svn_fs_fs__del_rep_reference(svn_fs_t *fs,
383                              svn_revnum_t youngest,
384                              apr_pool_t *pool)
385 {
386   fs_fs_data_t *ffd = fs->fsap_data;
387   svn_sqlite__stmt_t *stmt;
388
389   SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
390   if (! ffd->rep_cache_db)
391     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
392
393   SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
394                                     STMT_DEL_REPS_YOUNGER_THAN_REV));
395   SVN_ERR(svn_sqlite__bindf(stmt, "r", youngest));
396   SVN_ERR(svn_sqlite__step_done(stmt));
397
398   return SVN_NO_ERROR;
399 }
400
401 /* Start a transaction to take an SQLite reserved lock that prevents
402    other writes.
403
404    See unlock_rep_cache(). */
405 static svn_error_t *
406 lock_rep_cache(svn_fs_t *fs,
407                apr_pool_t *pool)
408 {
409   fs_fs_data_t *ffd = fs->fsap_data;
410
411   if (! ffd->rep_cache_db)
412     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
413
414   SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_LOCK_REP));
415
416   return SVN_NO_ERROR;
417 }
418
419 /* End the transaction started by lock_rep_cache(). */
420 static svn_error_t *
421 unlock_rep_cache(svn_fs_t *fs,
422                  apr_pool_t *pool)
423 {
424   fs_fs_data_t *ffd = fs->fsap_data;
425
426   SVN_ERR_ASSERT(ffd->rep_cache_db); /* was opened by lock_rep_cache() */
427
428   SVN_ERR(svn_sqlite__exec_statements(ffd->rep_cache_db, STMT_UNLOCK_REP));
429
430   return SVN_NO_ERROR;
431 }
432
433 svn_error_t *
434 svn_fs_fs__with_rep_cache_lock(svn_fs_t *fs,
435                                svn_error_t *(*body)(void *,
436                                                     apr_pool_t *),
437                                void *baton,
438                                apr_pool_t *pool)
439 {
440   svn_error_t *err;
441
442   SVN_ERR(lock_rep_cache(fs, pool));
443   err = body(baton, pool);
444   return svn_error_compose_create(err, unlock_rep_cache(fs, pool));
445 }