1/* fs.c --- creating, opening and closing filesystems
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 <stdlib.h>
24#include <stdio.h>
25#include <string.h>
26
27#include <apr_general.h>
28#include <apr_pools.h>
29#include <apr_file_io.h>
30#include <apr_thread_mutex.h>
31
32#include "svn_fs.h"
33#include "svn_delta.h"
34#include "svn_version.h"
35#include "svn_pools.h"
36#include "fs.h"
37#include "fs_x.h"
38#include "pack.h"
39#include "recovery.h"
40#include "hotcopy.h"
41#include "verify.h"
42#include "tree.h"
43#include "lock.h"
44#include "id.h"
45#include "revprops.h"
46#include "rep-cache.h"
47#include "transaction.h"
48#include "util.h"
49#include "svn_private_config.h"
50#include "private/svn_fs_util.h"
51
52#include "../libsvn_fs/fs-loader.h"
53
54/* A prefix for the pool userdata variables used to hold
55   per-filesystem shared data.  See fs_serialized_init. */
56#define SVN_FSX_SHARED_USERDATA_PREFIX "svn-fsx-shared-"
57
58
59
60/* Initialize the part of FS that requires global serialization across all
61   instances.  The caller is responsible of ensuring that serialization.
62   Use COMMON_POOL for process-wide and SCRATCH_POOL for temporary
63   allocations. */
64static svn_error_t *
65x_serialized_init(svn_fs_t *fs,
66                  apr_pool_t *common_pool,
67                  apr_pool_t *scratch_pool)
68{
69  svn_fs_x__data_t *ffd = fs->fsap_data;
70  const char *key;
71  void *val;
72  svn_fs_x__shared_data_t *ffsd;
73  apr_status_t status;
74
75  /* Note that we are allocating a small amount of long-lived data for
76     each separate repository opened during the lifetime of the
77     svn_fs_initialize pool.  It's unlikely that anyone will notice
78     the modest expenditure; the alternative is to allocate each structure
79     in a subpool, add a reference-count, and add a serialized destructor
80     to the FS vtable.  That's more machinery than it's worth.
81
82     Picking an appropriate key for the shared data is tricky, because,
83     unfortunately, a filesystem UUID is not really unique.  It is implicitly
84     shared between hotcopied (1), dump / loaded (2) or naively copied (3)
85     filesystems.  We tackle this problem by using a combination of the UUID
86     and an instance ID as the key.  This allows us to avoid key clashing
87     in (1) and (2).
88
89     Speaking of (3), there is not so much we can do about it, except maybe
90     provide a convenient way of fixing things.  Naively copied filesystems
91     have identical filesystem UUIDs *and* instance IDs.  With the key being
92     a combination of these two, clashes can be fixed by changing either of
93     them (or both), e.g. with svn_fs_set_uuid(). */
94
95
96  SVN_ERR_ASSERT(fs->uuid);
97  SVN_ERR_ASSERT(ffd->instance_id);
98
99  key = apr_pstrcat(scratch_pool, SVN_FSX_SHARED_USERDATA_PREFIX,
100                    fs->uuid, ":", ffd->instance_id, SVN_VA_NULL);
101  status = apr_pool_userdata_get(&val, key, common_pool);
102  if (status)
103    return svn_error_wrap_apr(status, _("Can't fetch FSX shared data"));
104  ffsd = val;
105
106  if (!ffsd)
107    {
108      ffsd = apr_pcalloc(common_pool, sizeof(*ffsd));
109      ffsd->common_pool = common_pool;
110
111      /* POSIX fcntl locks are per-process, so we need a mutex for
112         intra-process synchronization when grabbing the repository write
113         lock. */
114      SVN_ERR(svn_mutex__init(&ffsd->fs_write_lock,
115                              SVN_FS_X__USE_LOCK_MUTEX, common_pool));
116
117      /* ... the pack lock ... */
118      SVN_ERR(svn_mutex__init(&ffsd->fs_pack_lock,
119                              SVN_FS_X__USE_LOCK_MUTEX, common_pool));
120
121      /* ... not to mention locking the txn-current file. */
122      SVN_ERR(svn_mutex__init(&ffsd->txn_current_lock,
123                              SVN_FS_X__USE_LOCK_MUTEX, common_pool));
124
125      /* We also need a mutex for synchronizing access to the active
126         transaction list and free transaction pointer. */
127      SVN_ERR(svn_mutex__init(&ffsd->txn_list_lock, TRUE, common_pool));
128
129      key = apr_pstrdup(common_pool, key);
130      status = apr_pool_userdata_set(ffsd, key, NULL, common_pool);
131      if (status)
132        return svn_error_wrap_apr(status, _("Can't store FSX shared data"));
133    }
134
135  ffd->shared = ffsd;
136
137  return SVN_NO_ERROR;
138}
139
140
141
142/* This function is provided for Subversion 1.0.x compatibility.  It
143   has no effect for fsx backed Subversion filesystems.  It conforms
144   to the fs_library_vtable_t.bdb_set_errcall() API. */
145static svn_error_t *
146x_set_errcall(svn_fs_t *fs,
147              void (*db_errcall_fcn)(const char *errpfx, char *msg))
148{
149
150  return SVN_NO_ERROR;
151}
152
153typedef struct x_freeze_baton_t {
154  svn_fs_t *fs;
155  svn_fs_freeze_func_t freeze_func;
156  void *freeze_baton;
157} x_freeze_baton_t;
158
159static svn_error_t *
160x_freeze_body(void *baton,
161              apr_pool_t *scratch_pool)
162{
163  x_freeze_baton_t *b = baton;
164  svn_boolean_t exists;
165
166  SVN_ERR(svn_fs_x__exists_rep_cache(&exists, b->fs, scratch_pool));
167  if (exists)
168    SVN_ERR(svn_fs_x__with_rep_cache_lock(b->fs,
169                                          b->freeze_func, b->freeze_baton,
170                                          scratch_pool));
171  else
172    SVN_ERR(b->freeze_func(b->freeze_baton, scratch_pool));
173
174  return SVN_NO_ERROR;
175}
176
177static svn_error_t *
178x_freeze_body2(void *baton,
179               apr_pool_t *scratch_pool)
180{
181  x_freeze_baton_t *b = baton;
182  SVN_ERR(svn_fs_x__with_write_lock(b->fs, x_freeze_body, baton,
183                                    scratch_pool));
184
185  return SVN_NO_ERROR;
186}
187
188static svn_error_t *
189x_freeze(svn_fs_t *fs,
190         svn_fs_freeze_func_t freeze_func,
191         void *freeze_baton,
192         apr_pool_t *scratch_pool)
193{
194  x_freeze_baton_t b;
195
196  b.fs = fs;
197  b.freeze_func = freeze_func;
198  b.freeze_baton = freeze_baton;
199
200  SVN_ERR(svn_fs__check_fs(fs, TRUE));
201  SVN_ERR(svn_fs_x__with_pack_lock(fs, x_freeze_body2, &b, scratch_pool));
202
203  return SVN_NO_ERROR;
204}
205
206static svn_error_t *
207x_info(const void **fsx_info,
208       svn_fs_t *fs,
209       apr_pool_t *result_pool,
210       apr_pool_t *scratch_pool)
211{
212  svn_fs_x__data_t *ffd = fs->fsap_data;
213  svn_fs_fsx_info_t *info = apr_palloc(result_pool, sizeof(*info));
214  info->fs_type = SVN_FS_TYPE_FSX;
215  info->shard_size = ffd->max_files_per_dir;
216  info->min_unpacked_rev = ffd->min_unpacked_rev;
217  *fsx_info = info;
218  return SVN_NO_ERROR;
219}
220
221/* Wrapper around svn_fs_x__revision_prop() adapting between function
222   signatures. */
223static svn_error_t *
224x_revision_prop(svn_string_t **value_p,
225                svn_fs_t *fs,
226                svn_revnum_t rev,
227                const char *propname,
228                apr_pool_t *pool)
229{
230  apr_pool_t *scratch_pool = svn_pool_create(pool);
231  SVN_ERR(svn_fs_x__revision_prop(value_p, fs, rev, propname, pool,
232                                  scratch_pool));
233  svn_pool_destroy(scratch_pool);
234
235  return SVN_NO_ERROR;
236}
237
238/* Wrapper around svn_fs_x__get_revision_proplist() adapting between function
239   signatures. */
240static svn_error_t *
241x_revision_proplist(apr_hash_t **proplist_p,
242                    svn_fs_t *fs,
243                    svn_revnum_t rev,
244                    apr_pool_t *pool)
245{
246  apr_pool_t *scratch_pool = svn_pool_create(pool);
247
248  /* No need to bypass the caches for r/o access to revprops. */
249  SVN_ERR(svn_fs_x__get_revision_proplist(proplist_p, fs, rev, FALSE,
250                                          pool, scratch_pool));
251  svn_pool_destroy(scratch_pool);
252
253  return SVN_NO_ERROR;
254}
255
256/* Wrapper around svn_fs_x__set_uuid() adapting between function
257   signatures. */
258static svn_error_t *
259x_set_uuid(svn_fs_t *fs,
260           const char *uuid,
261           apr_pool_t *scratch_pool)
262{
263  /* Whenever we set a new UUID, imply that FS will also be a different
264   * instance (on formats that support this). */
265  return svn_error_trace(svn_fs_x__set_uuid(fs, uuid, NULL, scratch_pool));
266}
267
268/* Wrapper around svn_fs_x__begin_txn() providing the scratch pool. */
269static svn_error_t *
270x_begin_txn(svn_fs_txn_t **txn_p,
271            svn_fs_t *fs,
272            svn_revnum_t rev,
273            apr_uint32_t flags,
274            apr_pool_t *pool)
275{
276  apr_pool_t *scratch_pool = svn_pool_create(pool);
277  SVN_ERR(svn_fs_x__begin_txn(txn_p, fs, rev, flags, pool, scratch_pool));
278  svn_pool_destroy(scratch_pool);
279
280  return SVN_NO_ERROR;
281}
282
283
284
285/* The vtable associated with a specific open filesystem. */
286static fs_vtable_t fs_vtable = {
287  svn_fs_x__youngest_rev,
288  x_revision_prop,
289  x_revision_proplist,
290  svn_fs_x__change_rev_prop,
291  x_set_uuid,
292  svn_fs_x__revision_root,
293  x_begin_txn,
294  svn_fs_x__open_txn,
295  svn_fs_x__purge_txn,
296  svn_fs_x__list_transactions,
297  svn_fs_x__deltify,
298  svn_fs_x__lock,
299  svn_fs_x__generate_lock_token,
300  svn_fs_x__unlock,
301  svn_fs_x__get_lock,
302  svn_fs_x__get_locks,
303  svn_fs_x__info_format,
304  svn_fs_x__info_config_files,
305  x_info,
306  svn_fs_x__verify_root,
307  x_freeze,
308  x_set_errcall
309};
310
311
312/* Creating a new filesystem. */
313
314/* Set up vtable and fsap_data fields in FS. */
315static svn_error_t *
316initialize_fs_struct(svn_fs_t *fs)
317{
318  svn_fs_x__data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd));
319  fs->vtable = &fs_vtable;
320  fs->fsap_data = ffd;
321  return SVN_NO_ERROR;
322}
323
324/* Reset vtable and fsap_data fields in FS such that the FS is basically
325 * closed now.  Note that FS must not hold locks when you call this. */
326static void
327uninitialize_fs_struct(svn_fs_t *fs)
328{
329  fs->vtable = NULL;
330  fs->fsap_data = NULL;
331}
332
333/* This implements the fs_library_vtable_t.create() API.  Create a new
334   fsx-backed Subversion filesystem at path PATH and link it into
335   *FS.
336
337   Perform temporary allocations in SCRATCH_POOL, and fs-global allocations
338   in COMMON_POOL.  The latter must be serialized using COMMON_POOL_LOCK. */
339static svn_error_t *
340x_create(svn_fs_t *fs,
341         const char *path,
342         svn_mutex__t *common_pool_lock,
343         apr_pool_t *scratch_pool,
344         apr_pool_t *common_pool)
345{
346  SVN_ERR(svn_fs__check_fs(fs, FALSE));
347
348  SVN_ERR(initialize_fs_struct(fs));
349
350  SVN_ERR(svn_fs_x__create(fs, path, scratch_pool));
351
352  SVN_ERR(svn_fs_x__initialize_caches(fs, scratch_pool));
353  SVN_MUTEX__WITH_LOCK(common_pool_lock,
354                       x_serialized_init(fs, common_pool, scratch_pool));
355
356  return SVN_NO_ERROR;
357}
358
359
360
361/* Gaining access to an existing filesystem.  */
362
363/* This implements the fs_library_vtable_t.open() API.  Open an FSX
364   Subversion filesystem located at PATH, set *FS to point to the
365   correct vtable for the filesystem.  Use SCRATCH_POOL for any temporary
366   allocations, and COMMON_POOL for fs-global allocations.
367   The latter must be serialized using COMMON_POOL_LOCK.  */
368static svn_error_t *
369x_open(svn_fs_t *fs,
370       const char *path,
371       svn_mutex__t *common_pool_lock,
372       apr_pool_t *scratch_pool,
373       apr_pool_t *common_pool)
374{
375  apr_pool_t *subpool = svn_pool_create(scratch_pool);
376
377  SVN_ERR(svn_fs__check_fs(fs, FALSE));
378
379  SVN_ERR(initialize_fs_struct(fs));
380
381  SVN_ERR(svn_fs_x__open(fs, path, subpool));
382
383  SVN_ERR(svn_fs_x__initialize_caches(fs, subpool));
384  SVN_MUTEX__WITH_LOCK(common_pool_lock,
385                       x_serialized_init(fs, common_pool, subpool));
386
387  svn_pool_destroy(subpool);
388
389  return SVN_NO_ERROR;
390}
391
392
393
394/* This implements the fs_library_vtable_t.open_for_recovery() API. */
395static svn_error_t *
396x_open_for_recovery(svn_fs_t *fs,
397                    const char *path,
398                    svn_mutex__t *common_pool_lock,
399                    apr_pool_t *scratch_pool,
400                    apr_pool_t *common_pool)
401{
402  svn_error_t * err;
403  svn_revnum_t youngest_rev;
404  apr_pool_t * subpool = svn_pool_create(scratch_pool);
405
406  /* Recovery for FSFS is currently limited to recreating the 'current'
407     file from the latest revision. */
408
409  /* The only thing we have to watch out for is that the 'current' file
410     might not exist or contain garbage.  So we'll try to read it here
411     and provide or replace the existing file if we couldn't read it.
412     (We'll also need it to exist later anyway as a source for the new
413     file's permissions). */
414
415  /* Use a partly-filled fs pointer first to create 'current'. */
416  fs->path = apr_pstrdup(fs->pool, path);
417
418  SVN_ERR(initialize_fs_struct(fs));
419
420  /* Figure out the repo format and check that we can even handle it. */
421  SVN_ERR(svn_fs_x__read_format_file(fs, subpool));
422
423  /* Now, read 'current' and try to patch it if necessary. */
424  err = svn_fs_x__youngest_rev(&youngest_rev, fs, subpool);
425  if (err)
426    {
427      const char *file_path;
428
429      /* 'current' file is missing or contains garbage.  Since we are trying
430       * to recover from whatever problem there is, being picky about the
431       * error code here won't do us much good.  If there is a persistent
432       * problem that we can't fix, it will show up when we try rewrite the
433       * file a few lines further below and we will report the failure back
434       * to the caller.
435       *
436       * Start recovery with HEAD = 0. */
437      svn_error_clear(err);
438      file_path = svn_fs_x__path_current(fs, subpool);
439
440      /* Best effort to ensure the file exists and is valid.
441       * This may fail for r/o filesystems etc. */
442      SVN_ERR(svn_io_remove_file2(file_path, TRUE, subpool));
443      SVN_ERR(svn_io_file_create_empty(file_path, subpool));
444      SVN_ERR(svn_fs_x__write_current(fs, 0, subpool));
445    }
446
447  uninitialize_fs_struct(fs);
448  svn_pool_destroy(subpool);
449
450  /* Now open the filesystem properly by calling the vtable method directly. */
451  return x_open(fs, path, common_pool_lock, scratch_pool, common_pool);
452}
453
454
455
456/* This implements the fs_library_vtable_t.upgrade_fs() API. */
457static svn_error_t *
458x_upgrade(svn_fs_t *fs,
459          const char *path,
460          svn_fs_upgrade_notify_t notify_func,
461          void *notify_baton,
462          svn_cancel_func_t cancel_func,
463          void *cancel_baton,
464          svn_mutex__t *common_pool_lock,
465          apr_pool_t *scratch_pool,
466          apr_pool_t *common_pool)
467{
468  SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
469  return svn_fs_x__upgrade(fs, notify_func, notify_baton,
470                           cancel_func, cancel_baton, scratch_pool);
471}
472
473static svn_error_t *
474x_verify(svn_fs_t *fs,
475         const char *path,
476         svn_revnum_t start,
477         svn_revnum_t end,
478         svn_fs_progress_notify_func_t notify_func,
479         void *notify_baton,
480         svn_cancel_func_t cancel_func,
481         void *cancel_baton,
482         svn_mutex__t *common_pool_lock,
483         apr_pool_t *scratch_pool,
484         apr_pool_t *common_pool)
485{
486  SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
487  return svn_fs_x__verify(fs, start, end, notify_func, notify_baton,
488                          cancel_func, cancel_baton, scratch_pool);
489}
490
491static svn_error_t *
492x_pack(svn_fs_t *fs,
493       const char *path,
494       svn_fs_pack_notify_t notify_func,
495       void *notify_baton,
496       svn_cancel_func_t cancel_func,
497       void *cancel_baton,
498       svn_mutex__t *common_pool_lock,
499       apr_pool_t *scratch_pool,
500       apr_pool_t *common_pool)
501{
502  SVN_ERR(x_open(fs, path, common_pool_lock, scratch_pool, common_pool));
503  return svn_fs_x__pack(fs, notify_func, notify_baton,
504                        cancel_func, cancel_baton, scratch_pool);
505}
506
507
508
509
510/* This implements the fs_library_vtable_t.hotcopy() API.  Copy a
511   possibly live Subversion filesystem SRC_FS from SRC_PATH to a
512   DST_FS at DEST_PATH. If INCREMENTAL is TRUE, make an effort not to
513   re-copy data which already exists in DST_FS.
514   The CLEAN_LOGS argument is ignored and included for Subversion
515   1.0.x compatibility.  The NOTIFY_FUNC and NOTIFY_BATON arguments
516   are also currently ignored.
517   Perform all temporary allocations in SCRATCH_POOL. */
518static svn_error_t *
519x_hotcopy(svn_fs_t *src_fs,
520          svn_fs_t *dst_fs,
521          const char *src_path,
522          const char *dst_path,
523          svn_boolean_t clean_logs,
524          svn_boolean_t incremental,
525          svn_fs_hotcopy_notify_t notify_func,
526          void *notify_baton,
527          svn_cancel_func_t cancel_func,
528          void *cancel_baton,
529          svn_mutex__t *common_pool_lock,
530          apr_pool_t *scratch_pool,
531          apr_pool_t *common_pool)
532{
533  /* Open the source repo as usual. */
534  SVN_ERR(x_open(src_fs, src_path, common_pool_lock, scratch_pool,
535                 common_pool));
536  if (cancel_func)
537    SVN_ERR(cancel_func(cancel_baton));
538
539  /* Test target repo when in INCREMENTAL mode, initialize it when not.
540   * For this, we need our FS internal data structures to be temporarily
541   * available. */
542  SVN_ERR(initialize_fs_struct(dst_fs));
543  SVN_ERR(svn_fs_x__hotcopy_prepare_target(src_fs, dst_fs, dst_path,
544                                           incremental, scratch_pool));
545  uninitialize_fs_struct(dst_fs);
546
547  /* Now, the destination repo should open just fine. */
548  SVN_ERR(x_open(dst_fs, dst_path, common_pool_lock, scratch_pool,
549                 common_pool));
550  if (cancel_func)
551    SVN_ERR(cancel_func(cancel_baton));
552
553  /* Now, we may copy data as needed ... */
554  return svn_fs_x__hotcopy(src_fs, dst_fs, incremental,
555                           notify_func, notify_baton,
556                           cancel_func, cancel_baton, scratch_pool);
557}
558
559
560
561/* This function is included for Subversion 1.0.x compatibility.  It
562   has no effect for fsx backed Subversion filesystems.  It conforms
563   to the fs_library_vtable_t.bdb_logfiles() API. */
564static svn_error_t *
565x_logfiles(apr_array_header_t **logfiles,
566           const char *path,
567           svn_boolean_t only_unused,
568           apr_pool_t *pool)
569{
570  /* A no-op for FSX. */
571  *logfiles = apr_array_make(pool, 0, sizeof(const char *));
572
573  return SVN_NO_ERROR;
574}
575
576
577
578
579
580/* Delete the filesystem located at path PATH.  Perform any temporary
581   allocations in SCRATCH_POOL. */
582static svn_error_t *
583x_delete_fs(const char *path,
584            apr_pool_t *scratch_pool)
585{
586  /* Remove everything. */
587  return svn_error_trace(svn_io_remove_dir2(path, FALSE, NULL, NULL,
588                                            scratch_pool));
589}
590
591static const svn_version_t *
592x_version(void)
593{
594  SVN_VERSION_BODY;
595}
596
597static const char *
598x_get_description(void)
599{
600  return _("Module for working with an experimental (FSX) repository.");
601}
602
603static svn_error_t *
604x_set_svn_fs_open(svn_fs_t *fs,
605                  svn_error_t *(*svn_fs_open_)(svn_fs_t **,
606                                               const char *,
607                                               apr_hash_t *,
608                                               apr_pool_t *,
609                                               apr_pool_t *))
610{
611  svn_fs_x__data_t *ffd = fs->fsap_data;
612  ffd->svn_fs_open_ = svn_fs_open_;
613  return SVN_NO_ERROR;
614}
615
616static void *
617x_info_dup(const void *fsx_info_void,
618           apr_pool_t *result_pool)
619{
620  /* All fields are either ints or static strings. */
621  const svn_fs_fsx_info_t *fsx_info = fsx_info_void;
622  return apr_pmemdup(result_pool, fsx_info, sizeof(*fsx_info));
623}
624
625
626/* Base FS library vtable, used by the FS loader library. */
627
628static fs_library_vtable_t library_vtable = {
629  x_version,
630  x_create,
631  x_open,
632  x_open_for_recovery,
633  x_upgrade,
634  x_verify,
635  x_delete_fs,
636  x_hotcopy,
637  x_get_description,
638  svn_fs_x__recover,
639  x_pack,
640  x_logfiles,
641  NULL /* parse_id */,
642  x_set_svn_fs_open,
643  x_info_dup
644};
645
646svn_error_t *
647svn_fs_x__init(const svn_version_t *loader_version,
648               fs_library_vtable_t **vtable,
649               apr_pool_t* common_pool)
650{
651  static const svn_version_checklist_t checklist[] =
652    {
653      { "svn_subr",  svn_subr_version },
654      { "svn_delta", svn_delta_version },
655      { "svn_fs_util", svn_fs_util__version },
656      { NULL, NULL }
657    };
658
659  /* Simplified version check to make sure we can safely use the
660     VTABLE parameter. The FS loader does a more exhaustive check. */
661  if (loader_version->major != SVN_VER_MAJOR)
662    return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
663                             _("Unsupported FS loader version (%d) for fsx"),
664                             loader_version->major);
665  SVN_ERR(svn_ver_check_list2(x_version(), checklist, svn_ver_equal));
666
667  *vtable = &library_vtable;
668  return SVN_NO_ERROR;
669}
670