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