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