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
31#include "svn_hash.h"
32#include "svn_pools.h"
33#include "svn_fs.h"
34#include "svn_path.h"
35#include "svn_utf.h"
36#include "svn_delta.h"
37#include "svn_version.h"
38#include "fs.h"
39#include "err.h"
40#include "dag.h"
41#include "revs-txns.h"
42#include "uuid.h"
43#include "tree.h"
44#include "id.h"
45#include "lock.h"
46#define SVN_WANT_BDB
47#include "svn_private_config.h"
48
49#include "bdb/bdb-err.h"
50#include "bdb/bdb_compat.h"
51#include "bdb/env.h"
52#include "bdb/nodes-table.h"
53#include "bdb/rev-table.h"
54#include "bdb/txn-table.h"
55#include "bdb/copies-table.h"
56#include "bdb/changes-table.h"
57#include "bdb/reps-table.h"
58#include "bdb/strings-table.h"
59#include "bdb/uuids-table.h"
60#include "bdb/locks-table.h"
61#include "bdb/lock-tokens-table.h"
62#include "bdb/node-origins-table.h"
63#include "bdb/miscellaneous-table.h"
64#include "bdb/checksum-reps-table.h"
65
66#include "../libsvn_fs/fs-loader.h"
67#include "private/svn_fs_util.h"
68
69
70/* Checking for return values, and reporting errors.  */
71
72/* Check that we're using the right Berkeley DB version. */
73/* FIXME: This check should be abstracted into the DB back-end layer. */
74static svn_error_t *
75check_bdb_version(void)
76{
77  int major, minor, patch;
78
79  db_version(&major, &minor, &patch);
80
81  /* First, check that we're using a reasonably correct of Berkeley DB. */
82  if ((major < SVN_FS_WANT_DB_MAJOR)
83      || (major == SVN_FS_WANT_DB_MAJOR && minor < SVN_FS_WANT_DB_MINOR)
84      || (major == SVN_FS_WANT_DB_MAJOR && minor == SVN_FS_WANT_DB_MINOR
85          && patch < SVN_FS_WANT_DB_PATCH))
86    return svn_error_createf(SVN_ERR_FS_GENERAL, 0,
87                             _("Bad database version: got %d.%d.%d,"
88                               " should be at least %d.%d.%d"),
89                             major, minor, patch,
90                             SVN_FS_WANT_DB_MAJOR,
91                             SVN_FS_WANT_DB_MINOR,
92                             SVN_FS_WANT_DB_PATCH);
93
94  /* Now, check that the version we're running against is the same as
95     the one we compiled with. */
96  if (major != DB_VERSION_MAJOR || minor != DB_VERSION_MINOR)
97    return svn_error_createf(SVN_ERR_FS_GENERAL, 0,
98                             _("Bad database version:"
99                               " compiled with %d.%d.%d,"
100                               " running against %d.%d.%d"),
101                             DB_VERSION_MAJOR,
102                             DB_VERSION_MINOR,
103                             DB_VERSION_PATCH,
104                             major, minor, patch);
105  return SVN_NO_ERROR;
106}
107
108
109
110/* Cleanup functions.  */
111
112/* Close a database in the filesystem FS.
113   DB_PTR is a pointer to the DB pointer in *FS to close.
114   NAME is the name of the database, for use in error messages.  */
115static svn_error_t *
116cleanup_fs_db(svn_fs_t *fs, DB **db_ptr, const char *name)
117{
118  /* If the BDB environment is panicked, don't do anything, since
119     attempting to close the database will fail anyway. */
120  base_fs_data_t *bfd = fs->fsap_data;
121  if (*db_ptr && !svn_fs_bdb__get_panic(bfd->bdb))
122    {
123      DB *db = *db_ptr;
124      char *msg = apr_psprintf(fs->pool, "closing '%s' database", name);
125      int db_err;
126
127      *db_ptr = 0;
128      db_err = db->close(db, 0);
129      if (db_err == DB_RUNRECOVERY)
130        {
131          /* We can ignore DB_RUNRECOVERY errors from DB->close, but
132             must set the panic flag in the environment baton.  The
133             error will be propagated appropriately from
134             svn_fs_bdb__close. */
135          svn_fs_bdb__set_panic(bfd->bdb);
136          db_err = 0;
137        }
138
139#if SVN_BDB_HAS_DB_INCOMPLETE
140      /* We can ignore DB_INCOMPLETE on db->close and db->sync; it
141       * just means someone else was using the db at the same time
142       * we were.  See the Berkeley documentation at:
143       * http://www.sleepycat.com/docs/ref/program/errorret.html#DB_INCOMPLETE
144       * http://www.sleepycat.com/docs/api_c/db_close.html
145       */
146      if (db_err == DB_INCOMPLETE)
147        db_err = 0;
148#endif /* SVN_BDB_HAS_DB_INCOMPLETE */
149
150      SVN_ERR(BDB_WRAP(fs, msg, db_err));
151    }
152
153  return SVN_NO_ERROR;
154}
155
156/* Close whatever Berkeley DB resources are allocated to FS.  */
157static svn_error_t *
158cleanup_fs(svn_fs_t *fs)
159{
160  base_fs_data_t *bfd = fs->fsap_data;
161  bdb_env_baton_t *bdb = (bfd ? bfd->bdb : NULL);
162
163  if (!bdb)
164    return SVN_NO_ERROR;
165
166  /* Close the databases.  */
167  SVN_ERR(cleanup_fs_db(fs, &bfd->nodes, "nodes"));
168  SVN_ERR(cleanup_fs_db(fs, &bfd->revisions, "revisions"));
169  SVN_ERR(cleanup_fs_db(fs, &bfd->transactions, "transactions"));
170  SVN_ERR(cleanup_fs_db(fs, &bfd->copies, "copies"));
171  SVN_ERR(cleanup_fs_db(fs, &bfd->changes, "changes"));
172  SVN_ERR(cleanup_fs_db(fs, &bfd->representations, "representations"));
173  SVN_ERR(cleanup_fs_db(fs, &bfd->strings, "strings"));
174  SVN_ERR(cleanup_fs_db(fs, &bfd->uuids, "uuids"));
175  SVN_ERR(cleanup_fs_db(fs, &bfd->locks, "locks"));
176  SVN_ERR(cleanup_fs_db(fs, &bfd->lock_tokens, "lock-tokens"));
177  SVN_ERR(cleanup_fs_db(fs, &bfd->node_origins, "node-origins"));
178  SVN_ERR(cleanup_fs_db(fs, &bfd->checksum_reps, "checksum-reps"));
179  SVN_ERR(cleanup_fs_db(fs, &bfd->miscellaneous, "miscellaneous"));
180
181  /* Finally, close the environment.  */
182  bfd->bdb = 0;
183  {
184    svn_error_t *err = svn_fs_bdb__close(bdb);
185    if (err)
186      return svn_error_createf
187        (err->apr_err, err,
188         _("Berkeley DB error for filesystem '%s'"
189           " while closing environment:\n"),
190         fs->path);
191  }
192  return SVN_NO_ERROR;
193}
194
195#if 0   /* Set to 1 for instrumenting. */
196static void print_fs_stats(svn_fs_t *fs)
197{
198  base_fs_data_t *bfd = fs->fsap_data;
199  DB_TXN_STAT *t;
200  DB_LOCK_STAT *l;
201  int db_err;
202
203  /* Print transaction statistics for this DB env. */
204  if ((db_err = bfd->bdb->env->txn_stat(bfd->bdb->env, &t, 0)) != 0)
205    fprintf(stderr, "Error running bfd->bdb->env->txn_stat(): %s",
206            db_strerror(db_err));
207  else
208    {
209      printf("*** DB transaction stats, right before closing env:\n");
210      printf("   Number of transactions currently active: %d\n",
211             t->st_nactive);
212      printf("   Max number of active transactions at any one time: %d\n",
213             t->st_maxnactive);
214      printf("   Number of transactions that have begun: %d\n",
215             t->st_nbegins);
216      printf("   Number of transactions that have aborted: %d\n",
217             t->st_naborts);
218      printf("   Number of transactions that have committed: %d\n",
219             t->st_ncommits);
220      printf("   Number of times a thread was forced to wait: %d\n",
221             t->st_region_wait);
222      printf("   Number of times a thread didn't need to wait: %d\n",
223             t->st_region_nowait);
224      printf("*** End DB transaction stats.\n\n");
225    }
226
227  /* Print transaction statistics for this DB env. */
228  if ((db_err = bfd->bdb->env->lock_stat(bfd->bdb->env, &l, 0)) != 0)
229    fprintf(stderr, "Error running bfd->bdb->env->lock_stat(): %s",
230            db_strerror(db_err));
231  else
232    {
233      printf("*** DB lock stats, right before closing env:\n");
234      printf("   The number of current locks: %d\n",
235             l->st_nlocks);
236      printf("   Max number of locks at any one time: %d\n",
237             l->st_maxnlocks);
238      printf("   Number of current lockers: %d\n",
239             l->st_nlockers);
240      printf("   Max number of lockers at any one time: %d\n",
241             l->st_maxnlockers);
242      printf("   Number of current objects: %d\n",
243             l->st_nobjects);
244      printf("   Max number of objects at any one time: %d\n",
245             l->st_maxnobjects);
246      printf("   Total number of locks requested: %d\n",
247             l->st_nrequests);
248      printf("   Total number of locks released: %d\n",
249             l->st_nreleases);
250      printf("   Total number of lock reqs failed because "
251             "DB_LOCK_NOWAIT was set: %d\n", l->st_nnowaits);
252      printf("   Total number of locks not immediately available "
253             "due to conflicts: %d\n", l->st_nconflicts);
254      printf("   Number of deadlocks detected: %d\n", l->st_ndeadlocks);
255      printf("   Number of times a thread waited before "
256             "obtaining the region lock: %d\n", l->st_region_wait);
257      printf("   Number of times a thread didn't have to wait: %d\n",
258             l->st_region_nowait);
259      printf("*** End DB lock stats.\n\n");
260    }
261
262}
263#else
264#  define print_fs_stats(fs)
265#endif /* 0/1 */
266
267/* An APR pool cleanup function for a filesystem.  DATA must be a
268   pointer to the filesystem to clean up.
269
270   When the filesystem object's pool is freed, we want the resources
271   held by Berkeley DB to go away, just like everything else.  So we
272   register this cleanup function with the filesystem's pool, and let
273   it take care of closing the databases, the environment, and any
274   other DB objects we might be using.  APR calls this function before
275   actually freeing the pool's memory.
276
277   It's a pity that we can't return an svn_error_t object from an APR
278   cleanup function.  For now, we return the rather generic
279   SVN_ERR_FS_CLEANUP, and pass the real svn_error_t to the registered
280   warning callback.  */
281
282static apr_status_t
283cleanup_fs_apr(void *data)
284{
285  svn_fs_t *fs = data;
286  svn_error_t *err;
287
288  print_fs_stats(fs);
289
290  err = cleanup_fs(fs);
291  if (! err)
292    return APR_SUCCESS;
293
294  /* Darn. An error during cleanup. Call the warning handler to
295     try and do something "right" with this error. Note that
296     the default will simply abort().  */
297  (*fs->warning)(fs->warning_baton, err);
298
299  svn_error_clear(err);
300
301  return SVN_ERR_FS_CLEANUP;
302}
303
304
305static svn_error_t *
306base_bdb_set_errcall(svn_fs_t *fs,
307                     void (*db_errcall_fcn)(const char *errpfx, char *msg))
308{
309  base_fs_data_t *bfd = fs->fsap_data;
310
311  SVN_ERR(svn_fs__check_fs(fs, TRUE));
312  bfd->bdb->error_info->user_callback = db_errcall_fcn;
313
314  return SVN_NO_ERROR;
315}
316
317
318/* Write the DB_CONFIG file. */
319static svn_error_t *
320bdb_write_config(svn_fs_t *fs)
321{
322  const char *dbconfig_file_name =
323    svn_dirent_join(fs->path, BDB_CONFIG_FILE, fs->pool);
324  apr_file_t *dbconfig_file = NULL;
325  int i;
326
327  static const char dbconfig_contents[] =
328    "# This is the configuration file for the Berkeley DB environment\n"
329    "# used by your Subversion repository.\n"
330    "# You must run 'svnadmin recover' whenever you modify this file,\n"
331    "# for your changes to take effect.\n"
332    "\n"
333    "### Lock subsystem\n"
334    "#\n"
335    "# Make sure you read the documentation at:\n"
336    "#\n"
337    "#   http://docs.oracle.com/cd/E17076_02/html/programmer_reference/lock_max.html\n"
338    "#\n"
339    "# before tweaking these values.\n"
340    "#\n"
341    "set_lk_max_locks   2000\n"
342    "set_lk_max_lockers 2000\n"
343    "set_lk_max_objects 2000\n"
344    "\n"
345    "### Log file subsystem\n"
346    "#\n"
347    "# Make sure you read the documentation at:\n"
348    "#\n"
349    "#   http://docs.oracle.com/cd/E17076_02/html/api_reference/C/envset_lg_bsize.html\n"
350    "#   http://docs.oracle.com/cd/E17076_02/html/api_reference/C/envset_lg_max.html\n"
351    "#   http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_limits.html\n"
352    "#\n"
353    "# Increase the size of the in-memory log buffer from the default\n"
354    "# of 32 Kbytes to 256 Kbytes.  Decrease the log file size from\n"
355    "# 10 Mbytes to 1 Mbyte.  This will help reduce the amount of disk\n"
356    "# space required for hot backups.  The size of the log file must be\n"
357    "# at least four times the size of the in-memory log buffer.\n"
358    "#\n"
359    "# Note: Decreasing the in-memory buffer size below 256 Kbytes will hurt\n"
360    "# hurt commit performance. For details, see:\n"
361    "#\n"
362    "#   http://svn.haxx.se/dev/archive-2002-02/0141.shtml\n"
363    "#\n"
364    "set_lg_bsize     262144\n"
365    "set_lg_max      1048576\n"
366    "#\n"
367    "# If you see \"log region out of memory\" errors, bump lg_regionmax.\n"
368    "# For more information, see:\n"
369    "#\n"
370    "#   http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n"
371    "#   http://svn.haxx.se/users/archive-2004-10/1000.shtml\n"
372    "#\n"
373    "set_lg_regionmax 131072\n"
374    "#\n"
375    /* ### Configure this with "svnadmin create --bdb-cache-size" */
376    "# The default cache size in BDB is only 256k. As explained in\n"
377    "# http://svn.haxx.se/dev/archive-2004-12/0368.shtml, this is too\n"
378    "# small for most applications. Bump this number if \"db_stat -m\"\n"
379    "# shows too many cache misses.\n"
380    "#\n"
381    "set_cachesize    0 1048576 1\n";
382
383  /* Run-time configurable options.
384     Each option set consists of a minimum required BDB version, a
385     config hash key, a header, an inactive form and an active
386     form. We always write the header; then, depending on the
387     run-time configuration and the BDB version we're compiling
388     against, we write either the active or inactive form of the
389     value. */
390  static const struct
391  {
392    int bdb_major;
393    int bdb_minor;
394    const char *config_key;
395    const char *header;
396    const char *inactive;
397    const char *active;
398  } dbconfig_options[] = {
399    /* Controlled by "svnadmin create --bdb-txn-nosync" */
400    { 4, 0, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
401      /* header */
402      "#\n"
403      "# Disable fsync of log files on transaction commit. Read the\n"
404      "# documentation about DB_TXN_NOSYNC at:\n"
405      "#\n"
406      "#   http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n"
407      "#\n"
408      "# [requires Berkeley DB 4.0]\n"
409      "#\n",
410      /* inactive */
411      "#set_flags DB_TXN_NOSYNC\n",
412      /* active */
413      "set_flags DB_TXN_NOSYNC\n" },
414    /* Controlled by "svnadmin create --bdb-log-keep" */
415    { 4, 2, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
416      /* header */
417      "#\n"
418      "# Enable automatic removal of unused transaction log files.\n"
419      "# Read the documentation about DB_LOG_AUTOREMOVE at:\n"
420      "#\n"
421      "#   http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n"
422      "#\n"
423      "# [requires Berkeley DB 4.2]\n"
424      "#\n",
425      /* inactive */
426      "#set_flags DB_LOG_AUTOREMOVE\n",
427      /* active */
428      "set_flags DB_LOG_AUTOREMOVE\n" },
429  };
430  static const int dbconfig_options_length =
431    sizeof(dbconfig_options)/sizeof(*dbconfig_options);
432
433
434  SVN_ERR(svn_io_file_open(&dbconfig_file, dbconfig_file_name,
435                           APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
436                           fs->pool));
437
438  SVN_ERR(svn_io_file_write_full(dbconfig_file, dbconfig_contents,
439                                 sizeof(dbconfig_contents) - 1, NULL,
440                                 fs->pool));
441
442  /* Write the variable DB_CONFIG flags. */
443  for (i = 0; i < dbconfig_options_length; ++i)
444    {
445      void *value = NULL;
446      const char *choice;
447
448      if (fs->config)
449        {
450          value = svn_hash_gets(fs->config, dbconfig_options[i].config_key);
451        }
452
453      SVN_ERR(svn_io_file_write_full(dbconfig_file,
454                                     dbconfig_options[i].header,
455                                     strlen(dbconfig_options[i].header),
456                                     NULL, fs->pool));
457
458      if (((DB_VERSION_MAJOR == dbconfig_options[i].bdb_major
459            && DB_VERSION_MINOR >= dbconfig_options[i].bdb_minor)
460           || DB_VERSION_MAJOR > dbconfig_options[i].bdb_major)
461          && value != NULL && strcmp(value, "0") != 0)
462        choice = dbconfig_options[i].active;
463      else
464        choice = dbconfig_options[i].inactive;
465
466      SVN_ERR(svn_io_file_write_full(dbconfig_file, choice, strlen(choice),
467                                     NULL, fs->pool));
468    }
469
470  return svn_io_file_close(dbconfig_file, fs->pool);
471}
472
473static svn_error_t *
474base_bdb_info_format(int *fs_format,
475                     svn_version_t **supports_version,
476                     svn_fs_t *fs,
477                     apr_pool_t *result_pool,
478                     apr_pool_t *scratch_pool)
479{
480  base_fs_data_t *bfd = fs->fsap_data;
481
482  *fs_format = bfd->format;
483  *supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
484
485  (*supports_version)->major = SVN_VER_MAJOR;
486  (*supports_version)->minor = 0;
487  (*supports_version)->patch = 0;
488  (*supports_version)->tag = "";
489
490  switch (bfd->format)
491    {
492    case 1:
493      break;
494    case 2:
495      (*supports_version)->minor = 4;
496      break;
497    case 3:
498      (*supports_version)->minor = 5;
499      break;
500    case 4:
501      (*supports_version)->minor = 6;
502      break;
503#ifdef SVN_DEBUG
504# if SVN_FS_BASE__FORMAT_NUMBER != 4
505#  error "Need to add a 'case' statement here"
506# endif
507#endif
508    }
509
510  return SVN_NO_ERROR;
511}
512
513static svn_error_t *
514base_bdb_info_config_files(apr_array_header_t **files,
515                           svn_fs_t *fs,
516                           apr_pool_t *result_pool,
517                           apr_pool_t *scratch_pool)
518{
519  *files = apr_array_make(result_pool, 1, sizeof(const char *));
520  APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path,
521                                                         BDB_CONFIG_FILE,
522                                                         result_pool);
523  return SVN_NO_ERROR;
524}
525
526static svn_error_t *
527base_bdb_verify_root(svn_fs_root_t *root,
528                     apr_pool_t *scratch_pool)
529{
530  /* Verifying is currently a no op for BDB. */
531  return SVN_NO_ERROR;
532}
533
534static svn_error_t *
535base_bdb_freeze(svn_fs_t *fs,
536                svn_fs_freeze_func_t freeze_func,
537                void *freeze_baton,
538                apr_pool_t *pool)
539{
540  SVN__NOT_IMPLEMENTED();
541}
542
543
544/* Creating a new filesystem */
545
546static fs_vtable_t fs_vtable = {
547  svn_fs_base__youngest_rev,
548  svn_fs_base__revision_prop,
549  svn_fs_base__revision_proplist,
550  svn_fs_base__change_rev_prop,
551  svn_fs_base__set_uuid,
552  svn_fs_base__revision_root,
553  svn_fs_base__begin_txn,
554  svn_fs_base__open_txn,
555  svn_fs_base__purge_txn,
556  svn_fs_base__list_transactions,
557  svn_fs_base__deltify,
558  svn_fs_base__lock,
559  svn_fs_base__generate_lock_token,
560  svn_fs_base__unlock,
561  svn_fs_base__get_lock,
562  svn_fs_base__get_locks,
563  base_bdb_info_format,
564  base_bdb_info_config_files,
565  NULL /* info_fsap */,
566  base_bdb_verify_root,
567  base_bdb_freeze,
568  base_bdb_set_errcall,
569};
570
571/* Where the format number is stored. */
572#define FORMAT_FILE   "format"
573
574/* Depending on CREATE, create or open the environment and databases
575   for filesystem FS in PATH. Use POOL for temporary allocations. */
576static svn_error_t *
577open_databases(svn_fs_t *fs,
578               svn_boolean_t create,
579               int format,
580               const char *path,
581               apr_pool_t *pool)
582{
583  base_fs_data_t *bfd;
584
585  SVN_ERR(svn_fs__check_fs(fs, FALSE));
586
587  bfd = apr_pcalloc(fs->pool, sizeof(*bfd));
588  fs->vtable = &fs_vtable;
589  fs->fsap_data = bfd;
590
591  /* Initialize the fs's path. */
592  fs->path = apr_pstrdup(fs->pool, path);
593
594  if (create)
595    SVN_ERR(bdb_write_config(fs));
596
597  /* Create the Berkeley DB environment.  */
598  {
599    svn_error_t *err = svn_fs_bdb__open(&(bfd->bdb), path,
600                                        SVN_BDB_STANDARD_ENV_FLAGS,
601                                        0666, fs->pool);
602    if (err)
603      {
604        if (create)
605          return svn_error_createf
606            (err->apr_err, err,
607             _("Berkeley DB error for filesystem '%s'"
608               " while creating environment:\n"),
609             fs->path);
610        else
611          return svn_error_createf
612            (err->apr_err, err,
613             _("Berkeley DB error for filesystem '%s'"
614               " while opening environment:\n"),
615             fs->path);
616      }
617  }
618
619  /* We must register the FS cleanup function *after* opening the
620     environment, so that it's run before the environment baton
621     cleanup. */
622  apr_pool_cleanup_register(fs->pool, fs, cleanup_fs_apr,
623                            apr_pool_cleanup_null);
624
625
626  /* Create the databases in the environment.  */
627  SVN_ERR(BDB_WRAP(fs, (create
628                        ? N_("creating 'nodes' table")
629                        : N_("opening 'nodes' table")),
630                   svn_fs_bdb__open_nodes_table(&bfd->nodes,
631                                                bfd->bdb->env,
632                                                create)));
633  SVN_ERR(BDB_WRAP(fs, (create
634                        ? N_("creating 'revisions' table")
635                        : N_("opening 'revisions' table")),
636                   svn_fs_bdb__open_revisions_table(&bfd->revisions,
637                                                    bfd->bdb->env,
638                                                    create)));
639  SVN_ERR(BDB_WRAP(fs, (create
640                        ? N_("creating 'transactions' table")
641                        : N_("opening 'transactions' table")),
642                   svn_fs_bdb__open_transactions_table(&bfd->transactions,
643                                                       bfd->bdb->env,
644                                                       create)));
645  SVN_ERR(BDB_WRAP(fs, (create
646                        ? N_("creating 'copies' table")
647                        : N_("opening 'copies' table")),
648                   svn_fs_bdb__open_copies_table(&bfd->copies,
649                                                 bfd->bdb->env,
650                                                 create)));
651  SVN_ERR(BDB_WRAP(fs, (create
652                        ? N_("creating 'changes' table")
653                        : N_("opening 'changes' table")),
654                   svn_fs_bdb__open_changes_table(&bfd->changes,
655                                                  bfd->bdb->env,
656                                                  create)));
657  SVN_ERR(BDB_WRAP(fs, (create
658                        ? N_("creating 'representations' table")
659                        : N_("opening 'representations' table")),
660                   svn_fs_bdb__open_reps_table(&bfd->representations,
661                                               bfd->bdb->env,
662                                               create)));
663  SVN_ERR(BDB_WRAP(fs, (create
664                        ? N_("creating 'strings' table")
665                        : N_("opening 'strings' table")),
666                   svn_fs_bdb__open_strings_table(&bfd->strings,
667                                                  bfd->bdb->env,
668                                                  create)));
669  SVN_ERR(BDB_WRAP(fs, (create
670                        ? N_("creating 'uuids' table")
671                        : N_("opening 'uuids' table")),
672                   svn_fs_bdb__open_uuids_table(&bfd->uuids,
673                                                bfd->bdb->env,
674                                                create)));
675  SVN_ERR(BDB_WRAP(fs, (create
676                        ? N_("creating 'locks' table")
677                        : N_("opening 'locks' table")),
678                   svn_fs_bdb__open_locks_table(&bfd->locks,
679                                                bfd->bdb->env,
680                                                create)));
681  SVN_ERR(BDB_WRAP(fs, (create
682                        ? N_("creating 'lock-tokens' table")
683                        : N_("opening 'lock-tokens' table")),
684                   svn_fs_bdb__open_lock_tokens_table(&bfd->lock_tokens,
685                                                      bfd->bdb->env,
686                                                      create)));
687
688  if (format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)
689    {
690      SVN_ERR(BDB_WRAP(fs, (create
691                            ? N_("creating 'node-origins' table")
692                            : N_("opening 'node-origins' table")),
693                       svn_fs_bdb__open_node_origins_table(&bfd->node_origins,
694                                                           bfd->bdb->env,
695                                                           create)));
696    }
697
698  if (format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT)
699    {
700      SVN_ERR(BDB_WRAP(fs, (create
701                            ? N_("creating 'miscellaneous' table")
702                            : N_("opening 'miscellaneous' table")),
703                       svn_fs_bdb__open_miscellaneous_table(&bfd->miscellaneous,
704                                                            bfd->bdb->env,
705                                                            create)));
706    }
707
708  if (format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
709    {
710      SVN_ERR(BDB_WRAP(fs, (create
711                            ? N_("creating 'checksum-reps' table")
712                            : N_("opening 'checksum-reps' table")),
713                       svn_fs_bdb__open_checksum_reps_table(&bfd->checksum_reps,
714                                                            bfd->bdb->env,
715                                                            create)));
716    }
717
718  return SVN_NO_ERROR;
719}
720
721
722/* Called by functions that initialize an svn_fs_t struct, after that
723   initialization is done, to populate svn_fs_t->uuid. */
724static svn_error_t *
725populate_opened_fs(svn_fs_t *fs, apr_pool_t *scratch_pool)
726{
727  SVN_ERR(svn_fs_base__populate_uuid(fs, scratch_pool));
728  return SVN_NO_ERROR;
729}
730
731static svn_error_t *
732base_create(svn_fs_t *fs,
733            const char *path,
734            svn_mutex__t *common_pool_lock,
735            apr_pool_t *pool,
736            apr_pool_t *common_pool)
737{
738  int format = SVN_FS_BASE__FORMAT_NUMBER;
739  svn_error_t *svn_err;
740
741  /* See if compatibility with older versions was explicitly requested. */
742  if (fs->config)
743    {
744      svn_version_t *compatible_version;
745      SVN_ERR(svn_fs__compatible_version(&compatible_version, fs->config,
746                                         pool));
747
748      /* select format number */
749      switch(compatible_version->minor)
750        {
751          case 0:
752          case 1:
753          case 2:
754          case 3: format = 1;
755                  break;
756
757          case 4: format = 2;
758                  break;
759
760          case 5: format = 3;
761                  break;
762
763          default:format = SVN_FS_BASE__FORMAT_NUMBER;
764        }
765    }
766
767  /* Create the environment and databases. */
768  svn_err = open_databases(fs, TRUE, format, path, pool);
769  if (svn_err) goto error;
770
771  /* Initialize the DAG subsystem. */
772  svn_err = svn_fs_base__dag_init_fs(fs);
773  if (svn_err) goto error;
774
775  /* This filesystem is ready.  Stamp it with a format number. */
776  svn_err = svn_io_write_version_file(
777   svn_dirent_join(fs->path, FORMAT_FILE, pool), format, pool);
778  if (svn_err) goto error;
779
780  ((base_fs_data_t *) fs->fsap_data)->format = format;
781
782  SVN_ERR(populate_opened_fs(fs, pool));
783  return SVN_NO_ERROR;;
784
785error:
786  return svn_error_compose_create(svn_err,
787                                  svn_error_trace(cleanup_fs(fs)));
788}
789
790
791/* Gaining access to an existing Berkeley DB-based filesystem.  */
792
793svn_error_t *
794svn_fs_base__test_required_feature_format(svn_fs_t *fs,
795                                          const char *feature,
796                                          int requires)
797{
798  base_fs_data_t *bfd = fs->fsap_data;
799  if (bfd->format < requires)
800    return svn_error_createf
801      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
802       _("The '%s' feature requires version %d of the filesystem schema; "
803         "filesystem '%s' uses only version %d"),
804       feature, requires, fs->path, bfd->format);
805  return SVN_NO_ERROR;
806}
807
808/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
809   number is not the same as the format number supported by this
810   Subversion. */
811static svn_error_t *
812check_format(int format)
813{
814  /* We currently support any format less than the compiled format number
815     simultaneously.  */
816  if (format <= SVN_FS_BASE__FORMAT_NUMBER)
817    return SVN_NO_ERROR;
818
819  return svn_error_createf(
820        SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
821        _("Expected FS format '%d'; found format '%d'"),
822        SVN_FS_BASE__FORMAT_NUMBER, format);
823}
824
825static svn_error_t *
826base_open(svn_fs_t *fs,
827          const char *path,
828          svn_mutex__t *common_pool_lock,
829          apr_pool_t *pool,
830          apr_pool_t *common_pool)
831{
832  int format;
833  svn_error_t *svn_err;
834  svn_boolean_t write_format_file = FALSE;
835
836  /* Read the FS format number. */
837  svn_err = svn_io_read_version_file(&format,
838                                     svn_dirent_join(path, FORMAT_FILE, pool),
839                                     pool);
840  if (svn_err && APR_STATUS_IS_ENOENT(svn_err->apr_err))
841    {
842      /* Pre-1.2 filesystems did not have a format file (you could say
843         they were format "0"), so they get upgraded on the fly.
844         However, we stopped "upgrading on the fly" in 1.5, so older
845         filesystems should only be bumped to 1.3, which is format "1". */
846      svn_error_clear(svn_err);
847      svn_err = SVN_NO_ERROR;
848      format = 1;
849      write_format_file = TRUE;
850    }
851  else if (svn_err)
852    goto error;
853
854  /* Create the environment and databases. */
855  svn_err = open_databases(fs, FALSE, format, path, pool);
856  if (svn_err) goto error;
857
858  ((base_fs_data_t *) fs->fsap_data)->format = format;
859  SVN_ERR(check_format(format));
860
861  /* If we lack a format file, write one. */
862  if (write_format_file)
863    {
864      svn_err = svn_io_write_version_file(svn_dirent_join(path, FORMAT_FILE,
865                                                        pool),
866                                          format, pool);
867      if (svn_err) goto error;
868    }
869
870  SVN_ERR(populate_opened_fs(fs, pool));
871  return SVN_NO_ERROR;
872
873 error:
874  return svn_error_compose_create(svn_err,
875                                  svn_error_trace(cleanup_fs(fs)));
876}
877
878
879/* Running recovery on a Berkeley DB-based filesystem.  */
880
881
882/* Recover a database at PATH. Perform catastrophic recovery if FATAL
883   is TRUE. Use POOL for temporary allocation. */
884static svn_error_t *
885bdb_recover(const char *path, svn_boolean_t fatal, apr_pool_t *pool)
886{
887  bdb_env_baton_t *bdb;
888
889  /* Here's the comment copied from db_recover.c:
890
891     Initialize the environment -- we don't actually do anything
892     else, that all that's needed to run recovery.
893
894     Note that we specify a private environment, as we're about to
895     create a region, and we don't want to leave it around.  If we
896     leave the region around, the application that should create it
897     will simply join it instead, and will then be running with
898     incorrectly sized (and probably terribly small) caches.  */
899
900  /* Note that since we're using a private environment, we shoudl
901     /not/ initialize locking. We want the environment files to go
902     away. */
903
904  SVN_ERR(svn_fs_bdb__open(&bdb, path,
905                           ((fatal ? DB_RECOVER_FATAL : DB_RECOVER)
906                            | SVN_BDB_PRIVATE_ENV_FLAGS),
907                           0666, pool));
908  return svn_fs_bdb__close(bdb);
909}
910
911static svn_error_t *
912base_open_for_recovery(svn_fs_t *fs,
913                       const char *path,
914                       svn_mutex__t *common_pool_lock,
915                       apr_pool_t *pool,
916                       apr_pool_t *common_pool)
917{
918  /* Just stash the path in the fs pointer - it's all we really need. */
919  fs->path = apr_pstrdup(fs->pool, path);
920
921  return SVN_NO_ERROR;
922}
923
924static svn_error_t *
925base_upgrade(svn_fs_t *fs,
926             const char *path,
927             svn_fs_upgrade_notify_t notify_func,
928             void *notify_baton,
929             svn_cancel_func_t cancel_func,
930             void *cancel_baton,
931             svn_mutex__t *common_pool_lock,
932             apr_pool_t *pool,
933             apr_pool_t *common_pool)
934{
935  const char *version_file_path;
936  int old_format_number;
937  svn_error_t *err;
938
939  version_file_path = svn_dirent_join(path, FORMAT_FILE, pool);
940
941  /* Read the old number so we've got it on hand later on. */
942  err = svn_io_read_version_file(&old_format_number, version_file_path, pool);
943  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
944    {
945      /* Pre-1.2 filesystems do not have a 'format' file. */
946      old_format_number = 0;
947      svn_error_clear(err);
948      err = SVN_NO_ERROR;
949    }
950  SVN_ERR(err);
951
952  /* Bump the format file's stored version number. */
953  SVN_ERR(svn_io_write_version_file(version_file_path,
954                                    SVN_FS_BASE__FORMAT_NUMBER, pool));
955  if (notify_func)
956    SVN_ERR(notify_func(notify_baton, SVN_FS_BASE__FORMAT_NUMBER,
957                        svn_fs_upgrade_format_bumped, pool));
958
959  /* Check and see if we need to record the "bump" revision. */
960  if (old_format_number < SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT)
961    {
962      apr_pool_t *subpool = svn_pool_create(pool);
963      svn_revnum_t youngest_rev;
964      const char *value;
965
966      /* Open the filesystem in a subpool (so we can control its
967         closure) and do our fiddling.
968
969         NOTE: By using base_open() here instead of open_databases(),
970         we will end up re-reading the format file that we just wrote.
971         But it's better to use the existing encapsulation of "opening
972         the filesystem" rather than duplicating (or worse, partially
973         duplicating) that logic here.  */
974      SVN_ERR(base_open(fs, path, common_pool_lock, subpool, common_pool));
975
976      /* Fetch the youngest rev, and record it */
977      SVN_ERR(svn_fs_base__youngest_rev(&youngest_rev, fs, subpool));
978      value = apr_psprintf(subpool, "%ld", youngest_rev);
979      SVN_ERR(svn_fs_base__miscellaneous_set
980              (fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE,
981               value, subpool));
982      svn_pool_destroy(subpool);
983    }
984
985  return SVN_NO_ERROR;
986}
987
988static svn_error_t *
989base_verify(svn_fs_t *fs, const char *path,
990            svn_revnum_t start,
991            svn_revnum_t end,
992            svn_fs_progress_notify_func_t notify_func,
993            void *notify_baton,
994            svn_cancel_func_t cancel_func,
995            void *cancel_baton,
996            svn_mutex__t *common_pool_lock,
997            apr_pool_t *pool,
998            apr_pool_t *common_pool)
999{
1000  /* Verifying is currently a no op for BDB. */
1001  return SVN_NO_ERROR;
1002}
1003
1004static svn_error_t *
1005base_bdb_recover(svn_fs_t *fs,
1006                 svn_cancel_func_t cancel_func, void *cancel_baton,
1007                 apr_pool_t *pool)
1008{
1009  /* The fs pointer is a fake created in base_open_for_recovery above.
1010     We only care about the path. */
1011  return bdb_recover(fs->path, FALSE, pool);
1012}
1013
1014static svn_error_t *
1015base_bdb_pack(svn_fs_t *fs,
1016              const char *path,
1017              svn_fs_pack_notify_t notify_func,
1018              void *notify_baton,
1019              svn_cancel_func_t cancel,
1020              void *cancel_baton,
1021              svn_mutex__t *common_pool_lock,
1022              apr_pool_t *pool,
1023              apr_pool_t *common_pool)
1024{
1025  /* Packing is currently a no op for BDB. */
1026  return SVN_NO_ERROR;
1027}
1028
1029
1030
1031/* Running the 'archive' command on a Berkeley DB-based filesystem.  */
1032
1033
1034static svn_error_t *
1035base_bdb_logfiles(apr_array_header_t **logfiles,
1036                  const char *path,
1037                  svn_boolean_t only_unused,
1038                  apr_pool_t *pool)
1039{
1040  bdb_env_baton_t *bdb;
1041  char **filelist;
1042  char **filename;
1043  u_int32_t flags = only_unused ? 0 : DB_ARCH_LOG;
1044
1045  *logfiles = apr_array_make(pool, 4, sizeof(const char *));
1046
1047  SVN_ERR(svn_fs_bdb__open(&bdb, path,
1048                           SVN_BDB_STANDARD_ENV_FLAGS,
1049                           0666, pool));
1050  SVN_BDB_ERR(bdb, bdb->env->log_archive(bdb->env, &filelist, flags));
1051
1052  if (filelist == NULL)
1053    return svn_fs_bdb__close(bdb);
1054
1055  for (filename = filelist; *filename != NULL; ++filename)
1056    {
1057      APR_ARRAY_PUSH(*logfiles, const char *) = apr_pstrdup(pool, *filename);
1058    }
1059
1060  free(filelist);
1061
1062  return svn_fs_bdb__close(bdb);
1063}
1064
1065
1066
1067/* Copying a live Berkeley DB-base filesystem.  */
1068
1069/**
1070 * Delete all unused log files from DBD enviroment at @a live_path that exist
1071 * in @a backup_path.
1072 */
1073static svn_error_t *
1074svn_fs_base__clean_logs(const char *live_path,
1075                        const char *backup_path,
1076                        apr_pool_t *pool)
1077{
1078  apr_array_header_t *logfiles;
1079
1080  SVN_ERR(base_bdb_logfiles(&logfiles,
1081                            live_path,
1082                            TRUE,        /* Only unused logs */
1083                            pool));
1084
1085  {  /* Process unused logs from live area */
1086    int idx;
1087    apr_pool_t *subpool = svn_pool_create(pool);
1088
1089    /* Process log files. */
1090    for (idx = 0; idx < logfiles->nelts; idx++)
1091      {
1092        const char *log_file = APR_ARRAY_IDX(logfiles, idx, const char *);
1093        const char *live_log_path;
1094        const char *backup_log_path;
1095
1096        svn_pool_clear(subpool);
1097        live_log_path = svn_dirent_join(live_path, log_file, subpool);
1098        backup_log_path = svn_dirent_join(backup_path, log_file, subpool);
1099
1100        { /* Compare files. No point in using MD5 and wasting CPU cycles as we
1101             got full copies of both logs */
1102
1103          svn_boolean_t files_match = FALSE;
1104          svn_node_kind_t kind;
1105
1106          /* Check to see if there is a corresponding log file in the backup
1107             directory */
1108          SVN_ERR(svn_io_check_path(backup_log_path, &kind, pool));
1109
1110          /* If the copy of the log exists, compare them */
1111          if (kind == svn_node_file)
1112            SVN_ERR(svn_io_files_contents_same_p(&files_match,
1113                                                 live_log_path,
1114                                                 backup_log_path,
1115                                                 subpool));
1116
1117          /* If log files do not match, go to the next log file. */
1118          if (!files_match)
1119            continue;
1120        }
1121
1122        SVN_ERR(svn_io_remove_file2(live_log_path, FALSE, subpool));
1123      }
1124
1125    svn_pool_destroy(subpool);
1126  }
1127
1128  return SVN_NO_ERROR;
1129}
1130
1131
1132/* DB_ENV->get_flags() and DB->get_pagesize() don't exist prior to
1133   Berkeley DB 4.2. */
1134#if SVN_BDB_VERSION_AT_LEAST(4, 2)
1135
1136/* Open the BDB environment at PATH and compare its configuration
1137   flags with FLAGS.  If every flag in FLAGS is set in the
1138   environment, then set *MATCH to true.  Else set *MATCH to false. */
1139static svn_error_t *
1140check_env_flags(svn_boolean_t *match,
1141                u_int32_t flags,
1142                const char *path,
1143                apr_pool_t *pool)
1144{
1145  bdb_env_baton_t *bdb;
1146#if SVN_BDB_VERSION_AT_LEAST(4, 7)
1147  int flag_state;
1148#else
1149  u_int32_t envflags;
1150#endif
1151
1152  SVN_ERR(svn_fs_bdb__open(&bdb, path,
1153                           SVN_BDB_STANDARD_ENV_FLAGS,
1154                           0666, pool));
1155#if SVN_BDB_VERSION_AT_LEAST(4, 7)
1156  SVN_BDB_ERR(bdb, bdb->env->log_get_config(bdb->env, flags, &flag_state));
1157#else
1158  SVN_BDB_ERR(bdb, bdb->env->get_flags(bdb->env, &envflags));
1159#endif
1160
1161  SVN_ERR(svn_fs_bdb__close(bdb));
1162
1163#if SVN_BDB_VERSION_AT_LEAST(4, 7)
1164  if (flag_state == 0)
1165#else
1166  if (flags & envflags)
1167#endif
1168    *match = TRUE;
1169  else
1170    *match = FALSE;
1171
1172  return SVN_NO_ERROR;
1173}
1174
1175
1176/* Set *PAGESIZE to the size of pages used to hold items in the
1177   database environment located at PATH.
1178*/
1179static svn_error_t *
1180get_db_pagesize(u_int32_t *pagesize,
1181                const char *path,
1182                apr_pool_t *pool)
1183{
1184  bdb_env_baton_t *bdb;
1185  DB *nodes_table;
1186
1187  SVN_ERR(svn_fs_bdb__open(&bdb, path,
1188                           SVN_BDB_STANDARD_ENV_FLAGS,
1189                           0666, pool));
1190
1191  /* ### We're only asking for the pagesize on the 'nodes' table.
1192         Is this enough?  We never call DB->set_pagesize() on any of
1193         our tables, so presumably BDB is using the same default
1194         pagesize for all our databases, right? */
1195  SVN_BDB_ERR(bdb, svn_fs_bdb__open_nodes_table(&nodes_table, bdb->env,
1196                                                FALSE));
1197  SVN_BDB_ERR(bdb, nodes_table->get_pagesize(nodes_table, pagesize));
1198  SVN_BDB_ERR(bdb, nodes_table->close(nodes_table, 0));
1199
1200  return svn_fs_bdb__close(bdb);
1201}
1202#endif /* SVN_BDB_VERSION_AT_LEAST(4, 2) */
1203
1204
1205/* Copy FILENAME from SRC_DIR to DST_DIR in byte increments of size
1206   CHUNKSIZE.  The read/write buffer of size CHUNKSIZE will be
1207   allocated in POOL.  If ALLOW_MISSING is set, we won't make a fuss
1208   if FILENAME isn't found in SRC_DIR; otherwise, we will.  */
1209static svn_error_t *
1210copy_db_file_safely(const char *src_dir,
1211                    const char *dst_dir,
1212                    const char *filename,
1213                    u_int32_t chunksize,
1214                    svn_boolean_t allow_missing,
1215                    apr_pool_t *pool)
1216{
1217  apr_file_t *s = NULL, *d = NULL;  /* init to null important for APR */
1218  const char *file_src_path = svn_dirent_join(src_dir, filename, pool);
1219  const char *file_dst_path = svn_dirent_join(dst_dir, filename, pool);
1220  svn_error_t *err;
1221  char *buf;
1222
1223  /* Open source file.  If it's missing and that's allowed, there's
1224     nothing more to do here. */
1225  err = svn_io_file_open(&s, file_src_path,
1226                         (APR_READ | APR_LARGEFILE),
1227                         APR_OS_DEFAULT, pool);
1228  if (err && APR_STATUS_IS_ENOENT(err->apr_err) && allow_missing)
1229    {
1230      svn_error_clear(err);
1231      return SVN_NO_ERROR;
1232    }
1233  SVN_ERR(err);
1234
1235  /* Open destination file. */
1236  SVN_ERR(svn_io_file_open(&d, file_dst_path, (APR_WRITE | APR_CREATE |
1237                                               APR_LARGEFILE),
1238                           APR_OS_DEFAULT, pool));
1239
1240  /* Allocate our read/write buffer. */
1241  buf = apr_palloc(pool, chunksize);
1242
1243  /* Copy bytes till the cows come home. */
1244  while (1)
1245    {
1246      apr_size_t bytes_this_time = chunksize;
1247      svn_error_t *read_err, *write_err;
1248
1249      /* Read 'em. */
1250      if ((read_err = svn_io_file_read(s, buf, &bytes_this_time, pool)))
1251        {
1252          if (APR_STATUS_IS_EOF(read_err->apr_err))
1253            svn_error_clear(read_err);
1254          else
1255            {
1256              svn_error_clear(svn_io_file_close(s, pool));
1257              svn_error_clear(svn_io_file_close(d, pool));
1258              return read_err;
1259            }
1260        }
1261
1262      /* Write 'em. */
1263      if ((write_err = svn_io_file_write_full(d, buf, bytes_this_time, NULL,
1264                                              pool)))
1265        {
1266          svn_error_clear(svn_io_file_close(s, pool));
1267          svn_error_clear(svn_io_file_close(d, pool));
1268          return write_err;
1269        }
1270
1271      /* read_err is either NULL, or a dangling pointer - but it is only a
1272         dangling pointer if it used to be an EOF error. */
1273      if (read_err)
1274        {
1275          SVN_ERR(svn_io_file_close(s, pool));
1276          SVN_ERR(svn_io_file_close(d, pool));
1277          break;  /* got EOF on read, all files closed, all done. */
1278        }
1279    }
1280
1281  return SVN_NO_ERROR;
1282}
1283
1284
1285
1286
1287static svn_error_t *
1288base_hotcopy(svn_fs_t *src_fs,
1289             svn_fs_t *dst_fs,
1290             const char *src_path,
1291             const char *dest_path,
1292             svn_boolean_t clean_logs,
1293             svn_boolean_t incremental,
1294             svn_fs_hotcopy_notify_t notify_func,
1295             void *notify_baton,
1296             svn_cancel_func_t cancel_func,
1297             void *cancel_baton,
1298             svn_mutex__t *common_pool_lock,
1299             apr_pool_t *pool,
1300             apr_pool_t *common_pool)
1301{
1302  svn_error_t *err;
1303  u_int32_t pagesize;
1304  svn_boolean_t log_autoremove = FALSE;
1305  int format;
1306
1307  if (incremental)
1308    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1309                             _("BDB repositories do not support incremental "
1310                               "hotcopy"));
1311
1312  /* Check the FS format number to be certain that we know how to
1313     hotcopy this FS.  Pre-1.2 filesystems did not have a format file (you
1314     could say they were format "0"), so we will error here.  This is not
1315     optimal, but since this has been the case since 1.2.0, and no one has
1316     complained, it apparently isn't much of a concern.  (We did not check
1317     the 'format' file in 1.2.x, but we did blindly try to copy 'locks',
1318     which would have errored just the same.)  */
1319  SVN_ERR(svn_io_read_version_file(
1320          &format, svn_dirent_join(src_path, FORMAT_FILE, pool), pool));
1321  SVN_ERR(check_format(format));
1322
1323  /* If using Berkeley DB 4.2 or later, note whether the DB_LOG_AUTO_REMOVE
1324     feature is on.  If it is, we have a potential race condition:
1325     another process might delete a logfile while we're in the middle
1326     of copying all the logfiles.  (This is not a huge deal; at worst,
1327     the hotcopy fails with a file-not-found error.) */
1328#if SVN_BDB_VERSION_AT_LEAST(4, 2)
1329  err = check_env_flags(&log_autoremove,
1330#if SVN_BDB_VERSION_AT_LEAST(4, 7)
1331                          DB_LOG_AUTO_REMOVE,
1332 /* DB_LOG_AUTO_REMOVE was named DB_LOG_AUTOREMOVE before Berkeley DB 4.7. */
1333#else
1334                          DB_LOG_AUTOREMOVE,
1335#endif
1336                          src_path, pool);
1337#endif
1338  SVN_ERR(err);
1339
1340  /* Copy the DB_CONFIG file. */
1341  SVN_ERR(svn_io_dir_file_copy(src_path, dest_path, "DB_CONFIG", pool));
1342
1343  /* In order to copy the database files safely and atomically, we
1344     must copy them in chunks which are multiples of the page-size
1345     used by BDB.  See sleepycat docs for details, or svn issue #1818. */
1346#if SVN_BDB_VERSION_AT_LEAST(4, 2)
1347  SVN_ERR(get_db_pagesize(&pagesize, src_path, pool));
1348  if (pagesize < SVN__STREAM_CHUNK_SIZE)
1349    {
1350      /* use the largest multiple of BDB pagesize we can. */
1351      int multiple = SVN__STREAM_CHUNK_SIZE / pagesize;
1352      pagesize *= multiple;
1353    }
1354#else
1355  /* default to 128K chunks, which should be safe.
1356     BDB almost certainly uses a power-of-2 pagesize. */
1357  pagesize = (4096 * 32);
1358#endif
1359
1360  /* Copy the databases.  */
1361  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1362                              "nodes", pagesize, FALSE, pool));
1363  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1364                              "transactions", pagesize, FALSE, pool));
1365  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1366                              "revisions", pagesize, FALSE, pool));
1367  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1368                              "copies", pagesize, FALSE, pool));
1369  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1370                              "changes", pagesize, FALSE, pool));
1371  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1372                              "representations", pagesize, FALSE, pool));
1373  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1374                              "strings", pagesize, FALSE, pool));
1375  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1376                              "uuids", pagesize, TRUE, pool));
1377  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1378                              "locks", pagesize, TRUE, pool));
1379  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1380                              "lock-tokens", pagesize, TRUE, pool));
1381  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1382                              "node-origins", pagesize, TRUE, pool));
1383  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1384                              "checksum-reps", pagesize, TRUE, pool));
1385  SVN_ERR(copy_db_file_safely(src_path, dest_path,
1386                              "miscellaneous", pagesize, TRUE, pool));
1387
1388  {
1389    apr_array_header_t *logfiles;
1390    int idx;
1391    apr_pool_t *subpool;
1392
1393    SVN_ERR(base_bdb_logfiles(&logfiles,
1394                              src_path,
1395                              FALSE,   /* All logs */
1396                              pool));
1397
1398    /* Process log files. */
1399    subpool = svn_pool_create(pool);
1400    for (idx = 0; idx < logfiles->nelts; idx++)
1401      {
1402        svn_pool_clear(subpool);
1403        err = svn_io_dir_file_copy(src_path, dest_path,
1404                                   APR_ARRAY_IDX(logfiles, idx,
1405                                                 const char *),
1406                                   subpool);
1407        if (err)
1408          {
1409            if (log_autoremove)
1410              return
1411                svn_error_quick_wrap
1412                (err,
1413                 _("Error copying logfile;  the DB_LOG_AUTOREMOVE feature\n"
1414                   "may be interfering with the hotcopy algorithm.  If\n"
1415                   "the problem persists, try deactivating this feature\n"
1416                   "in DB_CONFIG"));
1417            else
1418              return svn_error_trace(err);
1419          }
1420      }
1421    svn_pool_destroy(subpool);
1422  }
1423
1424  /* Since this is a copy we will have exclusive access to the repository. */
1425  err = bdb_recover(dest_path, TRUE, pool);
1426  if (err)
1427    {
1428      if (log_autoremove)
1429        return
1430          svn_error_quick_wrap
1431          (err,
1432           _("Error running catastrophic recovery on hotcopy;  the\n"
1433             "DB_LOG_AUTOREMOVE feature may be interfering with the\n"
1434             "hotcopy algorithm.  If the problem persists, try deactivating\n"
1435             "this feature in DB_CONFIG"));
1436      else
1437        return svn_error_trace(err);
1438    }
1439
1440  /* Only now that the hotcopied filesystem is complete,
1441     stamp it with a format file. */
1442  SVN_ERR(svn_io_write_version_file(
1443             svn_dirent_join(dest_path, FORMAT_FILE, pool), format, pool));
1444
1445  if (clean_logs)
1446    SVN_ERR(svn_fs_base__clean_logs(src_path, dest_path, pool));
1447
1448  return SVN_NO_ERROR;
1449}
1450
1451
1452
1453/* Deleting a Berkeley DB-based filesystem.  */
1454
1455
1456static svn_error_t *
1457base_delete_fs(const char *path,
1458               apr_pool_t *pool)
1459{
1460  /* First, use the Berkeley DB library function to remove any shared
1461     memory segments.  */
1462  SVN_ERR(svn_fs_bdb__remove(path, pool));
1463
1464  /* Remove the environment directory. */
1465  return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
1466}
1467
1468static const svn_version_t *
1469base_version(void)
1470{
1471  SVN_VERSION_BODY;
1472}
1473
1474static const char *
1475base_get_description(void)
1476{
1477  return _("Module for working with a Berkeley DB repository.");
1478}
1479
1480static svn_error_t *
1481base_set_svn_fs_open(svn_fs_t *fs,
1482                     svn_error_t *(*svn_fs_open_)(svn_fs_t **,
1483                                                  const char *,
1484                                                  apr_hash_t *,
1485                                                  apr_pool_t *,
1486                                                  apr_pool_t *))
1487{
1488  return SVN_NO_ERROR;
1489}
1490
1491
1492/* Base FS library vtable, used by the FS loader library. */
1493static fs_library_vtable_t library_vtable = {
1494  base_version,
1495  base_create,
1496  base_open,
1497  base_open_for_recovery,
1498  base_upgrade,
1499  base_verify,
1500  base_delete_fs,
1501  base_hotcopy,
1502  base_get_description,
1503  base_bdb_recover,
1504  base_bdb_pack,
1505  base_bdb_logfiles,
1506  svn_fs_base__id_parse,
1507  base_set_svn_fs_open,
1508  NULL /* info_fsap_dup */
1509};
1510
1511svn_error_t *
1512svn_fs_base__init(const svn_version_t *loader_version,
1513                  fs_library_vtable_t **vtable, apr_pool_t* common_pool)
1514{
1515  static const svn_version_checklist_t checklist[] =
1516    {
1517      { "svn_subr",  svn_subr_version },
1518      { "svn_delta", svn_delta_version },
1519      { "svn_fs_util", svn_fs_util__version },
1520      { NULL, NULL }
1521    };
1522
1523  /* Simplified version check to make sure we can safely use the
1524     VTABLE parameter. The FS loader does a more exhaustive check. */
1525  if (loader_version->major != SVN_VER_MAJOR)
1526    return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
1527                             _("Unsupported FS loader version (%d) for bdb"),
1528                             loader_version->major);
1529  SVN_ERR(svn_ver_check_list2(base_version(), checklist, svn_ver_equal));
1530  SVN_ERR(check_bdb_version());
1531  SVN_ERR(svn_fs_bdb__init(common_pool));
1532
1533  *vtable = &library_vtable;
1534  return SVN_NO_ERROR;
1535}
1536