rep-cache.c revision 309512
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#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
61/** Library-private API's. **/
62
63/* Body of svn_fs_fs__open_rep_cache().
64   Implements svn_atomic__init_once().init_func.
65 */
66static svn_error_t *
67open_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
124svn_error_t *
125svn_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_wrap(err, _("Couldn't open rep-cache database"));
132}
133
134svn_error_t *
135svn_fs_fs__close_rep_cache(svn_fs_t *fs)
136{
137  fs_fs_data_t *ffd = fs->fsap_data;
138
139  if (ffd->rep_cache_db)
140    {
141      SVN_ERR(svn_sqlite__close(ffd->rep_cache_db));
142      ffd->rep_cache_db = NULL;
143      ffd->rep_cache_db_opened = 0;
144    }
145
146  return SVN_NO_ERROR;
147}
148
149svn_error_t *
150svn_fs_fs__exists_rep_cache(svn_boolean_t *exists,
151                            svn_fs_t *fs, apr_pool_t *pool)
152{
153  svn_node_kind_t kind;
154
155  SVN_ERR(svn_io_check_path(path_rep_cache_db(fs->path, pool),
156                            &kind, pool));
157
158  *exists = (kind != svn_node_none);
159  return SVN_NO_ERROR;
160}
161
162svn_error_t *
163svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
164                              svn_revnum_t start,
165                              svn_revnum_t end,
166                              svn_error_t *(*walker)(representation_t *,
167                                                     void *,
168                                                     svn_fs_t *,
169                                                     apr_pool_t *),
170                              void *walker_baton,
171                              svn_cancel_func_t cancel_func,
172                              void *cancel_baton,
173                              apr_pool_t *pool)
174{
175  fs_fs_data_t *ffd = fs->fsap_data;
176  svn_sqlite__stmt_t *stmt;
177  svn_boolean_t have_row;
178  int iterations = 0;
179
180  apr_pool_t *iterpool = svn_pool_create(pool);
181
182  /* Don't check ffd->rep_sharing_allowed. */
183  SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
184
185  if (! ffd->rep_cache_db)
186    SVN_ERR(svn_fs_fs__open_rep_cache(fs, 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_fs__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      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 representation_t. */
229      rep = apr_pcalloc(iterpool, sizeof(*rep));
230      svn_fs_fs__id_txn_reset(&rep->txn_id);
231      sha1_digest = svn_sqlite__column_text(stmt, 0, iterpool);
232      err = svn_checksum_parse_hex(&checksum, svn_checksum_sha1,
233                                   sha1_digest, iterpool);
234      if (err)
235        return svn_error_compose_create(err, svn_sqlite__reset(stmt));
236
237      rep->has_sha1 = TRUE;
238      memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
239      rep->revision = svn_sqlite__column_revnum(stmt, 1);
240      rep->item_index = svn_sqlite__column_int64(stmt, 2);
241      rep->size = svn_sqlite__column_int64(stmt, 3);
242      rep->expanded_size = svn_sqlite__column_int64(stmt, 4);
243
244      /* Walk. */
245      err = walker(rep, walker_baton, fs, iterpool);
246      if (err)
247        return svn_error_compose_create(err, svn_sqlite__reset(stmt));
248
249      SVN_ERR(svn_sqlite__step(&have_row, stmt));
250    }
251
252  SVN_ERR(svn_sqlite__reset(stmt));
253  svn_pool_destroy(iterpool);
254
255  return SVN_NO_ERROR;
256}
257
258
259/* This function's caller ignores most errors it returns.
260   If you extend this function, check the callsite to see if you have
261   to make it not-ignore additional error codes.  */
262svn_error_t *
263svn_fs_fs__get_rep_reference(representation_t **rep,
264                             svn_fs_t *fs,
265                             svn_checksum_t *checksum,
266                             apr_pool_t *pool)
267{
268  fs_fs_data_t *ffd = fs->fsap_data;
269  svn_sqlite__stmt_t *stmt;
270  svn_boolean_t have_row;
271
272  SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
273  if (! ffd->rep_cache_db)
274    SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
275
276  /* We only allow SHA1 checksums in this table. */
277  if (checksum->kind != svn_checksum_sha1)
278    return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
279                            _("Only SHA1 checksums can be used as keys in the "
280                              "rep_cache table.\n"));
281
282  SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db, STMT_GET_REP));
283  SVN_ERR(svn_sqlite__bindf(stmt, "s",
284                            svn_checksum_to_cstring(checksum, pool)));
285
286  SVN_ERR(svn_sqlite__step(&have_row, stmt));
287  if (have_row)
288    {
289      *rep = apr_pcalloc(pool, sizeof(**rep));
290      svn_fs_fs__id_txn_reset(&(*rep)->txn_id);
291      memcpy((*rep)->sha1_digest, checksum->digest,
292             sizeof((*rep)->sha1_digest));
293      (*rep)->has_sha1 = TRUE;
294      (*rep)->revision = svn_sqlite__column_revnum(stmt, 0);
295      (*rep)->item_index = svn_sqlite__column_int64(stmt, 1);
296      (*rep)->size = svn_sqlite__column_int64(stmt, 2);
297      (*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3);
298    }
299  else
300    *rep = NULL;
301
302  SVN_ERR(svn_sqlite__reset(stmt));
303
304  if (*rep)
305    {
306      /* Check that REP refers to a revision that exists in FS. */
307      svn_error_t *err = svn_fs_fs__ensure_revision_exists((*rep)->revision,
308                                                           fs, 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,
313                                                                 pool));
314    }
315
316  return SVN_NO_ERROR;
317}
318
319svn_error_t *
320svn_fs_fs__set_rep_reference(svn_fs_t *fs,
321                             representation_t *rep,
322                             apr_pool_t *pool)
323{
324  fs_fs_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_fs__open_rep_cache(fs, 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, pool),
344                            (apr_int64_t) rep->revision,
345                            (apr_int64_t) rep->item_index,
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      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_fs__get_rep_reference(&old_rep, fs, &checksum, pool));
363
364      if (!old_rep)
365        {
366          /* Something really odd at this point, we failed to insert the
367             checksum AND failed to read an existing checksum.  Do we need
368             to flag this? */
369        }
370    }
371
372  return SVN_NO_ERROR;
373}
374
375
376svn_error_t *
377svn_fs_fs__del_rep_reference(svn_fs_t *fs,
378                             svn_revnum_t youngest,
379                             apr_pool_t *pool)
380{
381  fs_fs_data_t *ffd = fs->fsap_data;
382  svn_sqlite__stmt_t *stmt;
383
384  SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT);
385  if (! ffd->rep_cache_db)
386    SVN_ERR(svn_fs_fs__open_rep_cache(fs, 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(). */
400static svn_error_t *
401lock_rep_cache(svn_fs_t *fs,
402               apr_pool_t *pool)
403{
404  fs_fs_data_t *ffd = fs->fsap_data;
405
406  if (! ffd->rep_cache_db)
407    SVN_ERR(svn_fs_fs__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(). */
415static svn_error_t *
416unlock_rep_cache(svn_fs_t *fs,
417                 apr_pool_t *pool)
418{
419  fs_fs_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
428svn_error_t *
429svn_fs_fs__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}
441