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