]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_fs/rep-cache.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_fs / rep-cache.c
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
41 REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements);
42
43
44 \f
45 /** Helper functions. **/
46 static APR_INLINE const char *
47 path_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 \f
54 /** Library-private API's. **/
55
56 /* Body of svn_fs_fs__open_rep_cache().
57    Implements svn_atomic__init_once().init_func.
58  */
59 static svn_error_t *
60 open_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
117 svn_error_t *
118 svn_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
127 svn_error_t *
128 svn_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
140 svn_error_t *
141 svn_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.  */
240 svn_error_t *
241 svn_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
297 svn_error_t *
298 svn_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
354 svn_error_t *
355 svn_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(). */
378 static svn_error_t *
379 lock_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(). */
393 static svn_error_t *
394 unlock_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
406 svn_error_t *
407 svn_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 }