]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_x/rep-cache.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_x / rep-cache.c
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
42 REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements);
43
44
45 \f
46 /** Helper functions. **/
47 static APR_INLINE const char *
48 path_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 \f
55 /** Library-private API's. **/
56
57 /* Body of svn_fs_x__open_rep_cache().
58    Implements svn_atomic__init_once().init_func.
59  */
60 static svn_error_t *
61 open_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
118 svn_error_t *
119 svn_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
128 svn_error_t *
129 svn_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
142 svn_error_t *
143 svn_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.  */
238 svn_error_t *
239 svn_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
295 svn_error_t *
296 svn_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
353 svn_error_t *
354 svn_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(). */
376 static svn_error_t *
377 lock_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(). */
391 static svn_error_t *
392 unlock_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
404 svn_error_t *
405 svn_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 }