summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_x/fs_x.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_x/fs_x.c')
-rw-r--r--subversion/libsvn_fs_x/fs_x.c1228
1 files changed, 1228 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_x/fs_x.c b/subversion/libsvn_fs_x/fs_x.c
new file mode 100644
index 0000000..b766b58
--- /dev/null
+++ b/subversion/libsvn_fs_x/fs_x.c
@@ -0,0 +1,1228 @@
+/* fs_x.c --- filesystem operations specific to fs_x
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include "fs_x.h"
+
+#include <apr_uuid.h>
+
+#include "svn_hash.h"
+#include "svn_props.h"
+#include "svn_time.h"
+#include "svn_dirent_uri.h"
+#include "svn_sorts.h"
+#include "svn_version.h"
+
+#include "cached_data.h"
+#include "id.h"
+#include "rep-cache.h"
+#include "revprops.h"
+#include "transaction.h"
+#include "tree.h"
+#include "util.h"
+#include "index.h"
+
+#include "private/svn_fs_util.h"
+#include "private/svn_string_private.h"
+#include "private/svn_subr_private.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+/* The default maximum number of files per directory to store in the
+ rev and revprops directory. The number below is somewhat arbitrary,
+ and can be overridden by defining the macro while compiling; the
+ figure of 1000 is reasonable for VFAT filesystems, which are by far
+ the worst performers in this area. */
+#ifndef SVN_FS_X_DEFAULT_MAX_FILES_PER_DIR
+#define SVN_FS_X_DEFAULT_MAX_FILES_PER_DIR 1000
+#endif
+
+/* Begin deltification after a node history exceeded this this limit.
+ Useful values are 4 to 64 with 16 being a good compromise between
+ computational overhead and repository size savings.
+ Should be a power of 2.
+ Values < 2 will result in standard skip-delta behavior. */
+#define SVN_FS_X_MAX_LINEAR_DELTIFICATION 16
+
+/* Finding a deltification base takes operations proportional to the
+ number of changes being skipped. To prevent exploding runtime
+ during commits, limit the deltification range to this value.
+ Should be a power of 2 minus one.
+ Values < 1 disable deltification. */
+#define SVN_FS_X_MAX_DELTIFICATION_WALK 1023
+
+
+
+
+/* Check that BUF, a nul-terminated buffer of text from format file PATH,
+ contains only digits at OFFSET and beyond, raising an error if not.
+
+ Uses SCRATCH_POOL for temporary allocation. */
+static svn_error_t *
+check_format_file_buffer_numeric(const char *buf,
+ apr_off_t offset,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ return svn_fs_x__check_file_buffer_numeric(buf, offset, path, "Format",
+ scratch_pool);
+}
+
+/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
+ number is not the same as a format number supported by this
+ Subversion. */
+static svn_error_t *
+check_format(int format)
+{
+ /* Put blacklisted versions here. */
+
+ /* We support all formats from 1-current simultaneously */
+ if (1 <= format && format <= SVN_FS_X__FORMAT_NUMBER)
+ return SVN_NO_ERROR;
+
+ return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
+ _("Expected FS format between '1' and '%d'; found format '%d'"),
+ SVN_FS_X__FORMAT_NUMBER, format);
+}
+
+/* Read the format file at PATH and set *PFORMAT to the format version found
+ * and *MAX_FILES_PER_DIR to the shard size. Use SCRATCH_POOL for temporary
+ * allocations. */
+static svn_error_t *
+read_format(int *pformat,
+ int *max_files_per_dir,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ svn_stream_t *stream;
+ svn_stringbuf_t *content;
+ svn_stringbuf_t *buf;
+ svn_boolean_t eos = FALSE;
+
+ SVN_ERR(svn_stringbuf_from_file2(&content, path, scratch_pool));
+ stream = svn_stream_from_stringbuf(content, scratch_pool);
+ SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, scratch_pool));
+ if (buf->len == 0 && eos)
+ {
+ /* Return a more useful error message. */
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("Can't read first line of format file '%s'"),
+ svn_dirent_local_style(path, scratch_pool));
+ }
+
+ /* Check that the first line contains only digits. */
+ SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, scratch_pool));
+ SVN_ERR(svn_cstring_atoi(pformat, buf->data));
+
+ /* Check that we support this format at all */
+ SVN_ERR(check_format(*pformat));
+
+ /* Read any options. */
+ SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, scratch_pool));
+ if (!eos && strncmp(buf->data, "layout sharded ", 15) == 0)
+ {
+ /* Check that the argument is numeric. */
+ SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path,
+ scratch_pool));
+ SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15));
+ }
+ else
+ return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
+ _("'%s' contains invalid filesystem format option '%s'"),
+ svn_dirent_local_style(path, scratch_pool), buf->data);
+
+ return SVN_NO_ERROR;
+}
+
+/* Write the format number and maximum number of files per directory
+ to a new format file in PATH, possibly expecting to overwrite a
+ previously existing file.
+
+ Use SCRATCH_POOL for temporary allocation. */
+svn_error_t *
+svn_fs_x__write_format(svn_fs_t *fs,
+ svn_boolean_t overwrite,
+ apr_pool_t *scratch_pool)
+{
+ svn_stringbuf_t *sb;
+ const char *path = svn_fs_x__path_format(fs, scratch_pool);
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+
+ SVN_ERR_ASSERT(1 <= ffd->format && ffd->format <= SVN_FS_X__FORMAT_NUMBER);
+
+ sb = svn_stringbuf_createf(scratch_pool, "%d\n", ffd->format);
+ svn_stringbuf_appendcstr(sb, apr_psprintf(scratch_pool,
+ "layout sharded %d\n",
+ ffd->max_files_per_dir));
+
+ /* svn_io_write_version_file() does a load of magic to allow it to
+ replace version files that already exist. We only need to do
+ that when we're allowed to overwrite an existing file. */
+ if (! overwrite)
+ {
+ /* Create the file */
+ SVN_ERR(svn_io_file_create(path, sb->data, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(svn_io_write_atomic(path, sb->data, sb->len,
+ NULL /* copy_perms_path */, scratch_pool));
+ }
+
+ /* And set the perms to make it read only */
+ return svn_io_set_file_read_only(path, FALSE, scratch_pool);
+}
+
+/* Check that BLOCK_SIZE is a valid block / page size, i.e. it is within
+ * the range of what the current system may address in RAM and it is a
+ * power of 2. Assume that the element size within the block is ITEM_SIZE.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+verify_block_size(apr_int64_t block_size,
+ apr_size_t item_size,
+ const char *name,
+ apr_pool_t *scratch_pool)
+{
+ /* Limit range. */
+ if (block_size <= 0)
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("%s is too small for fsfs.conf setting '%s'."),
+ apr_psprintf(scratch_pool,
+ "%" APR_INT64_T_FMT,
+ block_size),
+ name);
+
+ if (block_size > SVN_MAX_OBJECT_SIZE / item_size)
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("%s is too large for fsfs.conf setting '%s'."),
+ apr_psprintf(scratch_pool,
+ "%" APR_INT64_T_FMT,
+ block_size),
+ name);
+
+ /* Ensure it is a power of two.
+ * For positive X, X & (X-1) will reset the lowest bit set.
+ * If the result is 0, at most one bit has been set. */
+ if (0 != (block_size & (block_size - 1)))
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("%s is invalid for fsfs.conf setting '%s' "
+ "because it is not a power of 2."),
+ apr_psprintf(scratch_pool,
+ "%" APR_INT64_T_FMT,
+ block_size),
+ name);
+
+ return SVN_NO_ERROR;
+}
+
+/* Read the configuration information of the file system at FS_PATH
+ * and set the respective values in FFD. Use pools as usual.
+ */
+static svn_error_t *
+read_config(svn_fs_x__data_t *ffd,
+ const char *fs_path,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_config_t *config;
+ apr_int64_t compression_level;
+
+ SVN_ERR(svn_config_read3(&config,
+ svn_dirent_join(fs_path, PATH_CONFIG, scratch_pool),
+ FALSE, FALSE, FALSE, scratch_pool));
+
+ /* Initialize ffd->rep_sharing_allowed. */
+ SVN_ERR(svn_config_get_bool(config, &ffd->rep_sharing_allowed,
+ CONFIG_SECTION_REP_SHARING,
+ CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
+
+ /* Initialize deltification settings in ffd. */
+ SVN_ERR(svn_config_get_int64(config, &ffd->max_deltification_walk,
+ CONFIG_SECTION_DELTIFICATION,
+ CONFIG_OPTION_MAX_DELTIFICATION_WALK,
+ SVN_FS_X_MAX_DELTIFICATION_WALK));
+ SVN_ERR(svn_config_get_int64(config, &ffd->max_linear_deltification,
+ CONFIG_SECTION_DELTIFICATION,
+ CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
+ SVN_FS_X_MAX_LINEAR_DELTIFICATION));
+ SVN_ERR(svn_config_get_int64(config, &compression_level,
+ CONFIG_SECTION_DELTIFICATION,
+ CONFIG_OPTION_COMPRESSION_LEVEL,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT));
+ ffd->delta_compression_level
+ = (int)MIN(MAX(SVN_DELTA_COMPRESSION_LEVEL_NONE, compression_level),
+ SVN_DELTA_COMPRESSION_LEVEL_MAX);
+
+ /* Initialize revprop packing settings in ffd. */
+ SVN_ERR(svn_config_get_bool(config, &ffd->compress_packed_revprops,
+ CONFIG_SECTION_PACKED_REVPROPS,
+ CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
+ TRUE));
+ SVN_ERR(svn_config_get_int64(config, &ffd->revprop_pack_size,
+ CONFIG_SECTION_PACKED_REVPROPS,
+ CONFIG_OPTION_REVPROP_PACK_SIZE,
+ ffd->compress_packed_revprops
+ ? 0x100
+ : 0x40));
+
+ ffd->revprop_pack_size *= 1024;
+
+ /* I/O settings in ffd. */
+ SVN_ERR(svn_config_get_int64(config, &ffd->block_size,
+ CONFIG_SECTION_IO,
+ CONFIG_OPTION_BLOCK_SIZE,
+ 64));
+ SVN_ERR(svn_config_get_int64(config, &ffd->l2p_page_size,
+ CONFIG_SECTION_IO,
+ CONFIG_OPTION_L2P_PAGE_SIZE,
+ 0x2000));
+ SVN_ERR(svn_config_get_int64(config, &ffd->p2l_page_size,
+ CONFIG_SECTION_IO,
+ CONFIG_OPTION_P2L_PAGE_SIZE,
+ 0x400));
+
+ /* Don't accept unreasonable or illegal values.
+ * Block size and P2L page size are in kbytes;
+ * L2P blocks are arrays of apr_off_t. */
+ SVN_ERR(verify_block_size(ffd->block_size, 0x400,
+ CONFIG_OPTION_BLOCK_SIZE, scratch_pool));
+ SVN_ERR(verify_block_size(ffd->p2l_page_size, 0x400,
+ CONFIG_OPTION_P2L_PAGE_SIZE, scratch_pool));
+ SVN_ERR(verify_block_size(ffd->l2p_page_size, sizeof(apr_off_t),
+ CONFIG_OPTION_L2P_PAGE_SIZE, scratch_pool));
+
+ /* convert kBytes to bytes */
+ ffd->block_size *= 0x400;
+ ffd->p2l_page_size *= 0x400;
+ /* L2P pages are in entries - not in (k)Bytes */
+
+ /* Debug options. */
+ SVN_ERR(svn_config_get_bool(config, &ffd->pack_after_commit,
+ CONFIG_SECTION_DEBUG,
+ CONFIG_OPTION_PACK_AFTER_COMMIT,
+ FALSE));
+
+ /* memcached configuration */
+ SVN_ERR(svn_cache__make_memcache_from_config(&ffd->memcache, config,
+ result_pool, scratch_pool));
+
+ SVN_ERR(svn_config_get_bool(config, &ffd->fail_stop,
+ CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
+ FALSE));
+
+ return SVN_NO_ERROR;
+}
+
+/* Write FS' initial configuration file.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+write_config(svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+#define NL APR_EOL_STR
+ static const char * const fsx_conf_contents =
+"### This file controls the configuration of the FSX filesystem." NL
+"" NL
+"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]" NL
+"### These options name memcached servers used to cache internal FSX" NL
+"### data. See http://www.danga.com/memcached/ for more information on" NL
+"### memcached. To use memcached with FSX, run one or more memcached" NL
+"### servers, and specify each of them as an option like so:" NL
+"# first-server = 127.0.0.1:11211" NL
+"# remote-memcached = mymemcached.corp.example.com:11212" NL
+"### The option name is ignored; the value is of the form HOST:PORT." NL
+"### memcached servers can be shared between multiple repositories;" NL
+"### however, if you do this, you *must* ensure that repositories have" NL
+"### distinct UUIDs and paths, or else cached data from one repository" NL
+"### might be used by another accidentally. Note also that memcached has" NL
+"### no authentication for reads or writes, so you must ensure that your" NL
+"### memcached servers are only accessible by trusted users." NL
+"" NL
+"[" CONFIG_SECTION_CACHES "]" NL
+"### When a cache-related error occurs, normally Subversion ignores it" NL
+"### and continues, logging an error if the server is appropriately" NL
+"### configured (and ignoring it with file:// access). To make" NL
+"### Subversion never ignore cache errors, uncomment this line." NL
+"# " CONFIG_OPTION_FAIL_STOP " = true" NL
+"" NL
+"[" CONFIG_SECTION_REP_SHARING "]" NL
+"### To conserve space, the filesystem can optionally avoid storing" NL
+"### duplicate representations. This comes at a slight cost in" NL
+"### performance, as maintaining a database of shared representations can" NL
+"### increase commit times. The space savings are dependent upon the size" NL
+"### of the repository, the number of objects it contains and the amount of" NL
+"### duplication between them, usually a function of the branching and" NL
+"### merging process." NL
+"###" NL
+"### The following parameter enables rep-sharing in the repository. It can" NL
+"### be switched on and off at will, but for best space-saving results" NL
+"### should be enabled consistently over the life of the repository." NL
+"### 'svnadmin verify' will check the rep-cache regardless of this setting." NL
+"### rep-sharing is enabled by default." NL
+"# " CONFIG_OPTION_ENABLE_REP_SHARING " = true" NL
+"" NL
+"[" CONFIG_SECTION_DELTIFICATION "]" NL
+"### To conserve space, the filesystem stores data as differences against" NL
+"### existing representations. This comes at a slight cost in performance," NL
+"### as calculating differences can increase commit times. Reading data" NL
+"### will also create higher CPU load and the data will be fragmented." NL
+"### Since deltification tends to save significant amounts of disk space," NL
+"### the overall I/O load can actually be lower." NL
+"###" NL
+"### The options in this section allow for tuning the deltification" NL
+"### strategy. Their effects on data size and server performance may vary" NL
+"### from one repository to another." NL
+"###" NL
+"### During commit, the server may need to walk the whole change history of" NL
+"### of a given node to find a suitable deltification base. This linear" NL
+"### process can impact commit times, svnadmin load and similar operations." NL
+"### This setting limits the depth of the deltification history. If the" NL
+"### threshold has been reached, the node will be stored as fulltext and a" NL
+"### new deltification history begins." NL
+"### Note, this is unrelated to svn log." NL
+"### Very large values rarely provide significant additional savings but" NL
+"### can impact performance greatly - in particular if directory" NL
+"### deltification has been activated. Very small values may be useful in" NL
+"### repositories that are dominated by large, changing binaries." NL
+"### Should be a power of two minus 1. A value of 0 will effectively" NL
+"### disable deltification." NL
+"### For 1.9, the default value is 1023." NL
+"# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023" NL
+"###" NL
+"### The skip-delta scheme used by FSX tends to repeatably store redundant" NL
+"### delta information where a simple delta against the latest version is" NL
+"### often smaller. By default, 1.9+ will therefore use skip deltas only" NL
+"### after the linear chain of deltas has grown beyond the threshold" NL
+"### specified by this setting." NL
+"### Values up to 64 can result in some reduction in repository size for" NL
+"### the cost of quickly increasing I/O and CPU costs. Similarly, smaller" NL
+"### numbers can reduce those costs at the cost of more disk space. For" NL
+"### rarely read repositories or those containing larger binaries, this may" NL
+"### present a better trade-off." NL
+"### Should be a power of two. A value of 1 or smaller will cause the" NL
+"### exclusive use of skip-deltas." NL
+"### For 1.8, the default value is 16." NL
+"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16" NL
+"###" NL
+"### After deltification, we compress the data through zlib to minimize on-" NL
+"### disk size. That can be an expensive and ineffective process. This" NL
+"### setting controls the usage of zlib in future revisions." NL
+"### Revisions with highly compressible data in them may shrink in size" NL
+"### if the setting is increased but may take much longer to commit. The" NL
+"### time taken to uncompress that data again is widely independent of the" NL
+"### compression level." NL
+"### Compression will be ineffective if the incoming content is already" NL
+"### highly compressed. In that case, disabling the compression entirely" NL
+"### will speed up commits as well as reading the data. Repositories with" NL
+"### many small compressible files (source code) but also a high percentage" NL
+"### of large incompressible ones (artwork) may benefit from compression" NL
+"### levels lowered to e.g. 1." NL
+"### Valid values are 0 to 9 with 9 providing the highest compression ratio" NL
+"### and 0 disabling it altogether." NL
+"### The default value is 5." NL
+"# " CONFIG_OPTION_COMPRESSION_LEVEL " = 5" NL
+"" NL
+"[" CONFIG_SECTION_PACKED_REVPROPS "]" NL
+"### This parameter controls the size (in kBytes) of packed revprop files." NL
+"### Revprops of consecutive revisions will be concatenated into a single" NL
+"### file up to but not exceeding the threshold given here. However, each" NL
+"### pack file may be much smaller and revprops of a single revision may be" NL
+"### much larger than the limit set here. The threshold will be applied" NL
+"### before optional compression takes place." NL
+"### Large values will reduce disk space usage at the expense of increased" NL
+"### latency and CPU usage reading and changing individual revprops. They" NL
+"### become an advantage when revprop caching has been enabled because a" NL
+"### lot of data can be read in one go. Values smaller than 4 kByte will" NL
+"### not improve latency any further and quickly render revprop packing" NL
+"### ineffective." NL
+"### revprop-pack-size is 64 kBytes by default for non-compressed revprop" NL
+"### pack files and 256 kBytes when compression has been enabled." NL
+"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 64" NL
+"###" NL
+"### To save disk space, packed revprop files may be compressed. Standard" NL
+"### revprops tend to allow for very effective compression. Reading and" NL
+"### even more so writing, become significantly more CPU intensive. With" NL
+"### revprop caching enabled, the overhead can be offset by reduced I/O" NL
+"### unless you often modify revprops after packing." NL
+"### Compressing packed revprops is enabled by default." NL
+"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = true" NL
+"" NL
+"[" CONFIG_SECTION_IO "]" NL
+"### Parameters in this section control the data access granularity in" NL
+"### format 7 repositories and later. The defaults should translate into" NL
+"### decent performance over a wide range of setups." NL
+"###" NL
+"### When a specific piece of information needs to be read from disk, a" NL
+"### data block is being read at once and its contents are being cached." NL
+"### If the repository is being stored on a RAID, the block size should be" NL
+"### either 50% or 100% of RAID block size / granularity. Also, your file" NL
+"### system blocks/clusters should be properly aligned and sized. In that" NL
+"### setup, each access will hit only one disk (minimizes I/O load) but" NL
+"### uses all the data provided by the disk in a single access." NL
+"### For SSD-based storage systems, slightly lower values around 16 kB" NL
+"### may improve latency while still maximizing throughput." NL
+"### Can be changed at any time but must be a power of 2." NL
+"### block-size is given in kBytes and with a default of 64 kBytes." NL
+"# " CONFIG_OPTION_BLOCK_SIZE " = 64" NL
+"###" NL
+"### The log-to-phys index maps data item numbers to offsets within the" NL
+"### rev or pack file. This index is organized in pages of a fixed maximum" NL
+"### capacity. To access an item, the page table and the respective page" NL
+"### must be read." NL
+"### This parameter only affects revisions with thousands of changed paths." NL
+"### If you have several extremely large revisions (~1 mio changes), think" NL
+"### about increasing this setting. Reducing the value will rarely result" NL
+"### in a net speedup." NL
+"### This is an expert setting. Must be a power of 2." NL
+"### l2p-page-size is 8192 entries by default." NL
+"# " CONFIG_OPTION_L2P_PAGE_SIZE " = 8192" NL
+"###" NL
+"### The phys-to-log index maps positions within the rev or pack file to" NL
+"### to data items, i.e. describes what piece of information is being" NL
+"### stored at any particular offset. The index describes the rev file" NL
+"### in chunks (pages) and keeps a global list of all those pages. Large" NL
+"### pages mean a shorter page table but a larger per-page description of" NL
+"### data items in it. The latency sweet spot depends on the change size" NL
+"### distribution but covers a relatively wide range." NL
+"### If the repository contains very large files, i.e. individual changes" NL
+"### of tens of MB each, increasing the page size will shorten the index" NL
+"### file at the expense of a slightly increased latency in sections with" NL
+"### smaller changes." NL
+"### For source code repositories, this should be about 16x the block-size." NL
+"### Must be a power of 2." NL
+"### p2l-page-size is given in kBytes and with a default of 1024 kBytes." NL
+"# " CONFIG_OPTION_P2L_PAGE_SIZE " = 1024" NL
+;
+#undef NL
+ return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG,
+ scratch_pool),
+ fsx_conf_contents, scratch_pool);
+}
+
+/* Read FS's UUID file and store the data in the FS struct. */
+static svn_error_t *
+read_uuid(svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ apr_file_t *uuid_file;
+ char buf[APR_UUID_FORMATTED_LENGTH + 2];
+ apr_size_t limit;
+
+ /* Read the repository uuid. */
+ SVN_ERR(svn_io_file_open(&uuid_file, svn_fs_x__path_uuid(fs, scratch_pool),
+ APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
+ scratch_pool));
+
+ limit = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, scratch_pool));
+ fs->uuid = apr_pstrdup(fs->pool, buf);
+
+ /* Read the instance ID. */
+ limit = sizeof(buf);
+ SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit,
+ scratch_pool));
+ ffd->instance_id = apr_pstrdup(fs->pool, buf);
+
+ SVN_ERR(svn_io_file_close(uuid_file, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__read_format_file(svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ int format, max_files_per_dir;
+
+ /* Read info from format file. */
+ SVN_ERR(read_format(&format, &max_files_per_dir,
+ svn_fs_x__path_format(fs, scratch_pool), scratch_pool));
+
+ /* Now that we've got *all* info, store / update values in FFD. */
+ ffd->format = format;
+ ffd->max_files_per_dir = max_files_per_dir;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__open(svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ fs->path = apr_pstrdup(fs->pool, path);
+
+ /* Read the FS format file. */
+ SVN_ERR(svn_fs_x__read_format_file(fs, scratch_pool));
+
+ /* Read in and cache the repository uuid. */
+ SVN_ERR(read_uuid(fs, scratch_pool));
+
+ /* Read the min unpacked revision. */
+ SVN_ERR(svn_fs_x__update_min_unpacked_rev(fs, scratch_pool));
+
+ /* Read the configuration file. */
+ SVN_ERR(read_config(ffd, fs->path, fs->pool, scratch_pool));
+
+ return svn_error_trace(svn_fs_x__read_current(&ffd->youngest_rev_cache,
+ fs, scratch_pool));
+}
+
+/* Baton type bridging svn_fs_x__upgrade and upgrade_body carrying
+ * parameters over between them. */
+typedef struct upgrade_baton_t
+{
+ svn_fs_t *fs;
+ svn_fs_upgrade_notify_t notify_func;
+ void *notify_baton;
+ svn_cancel_func_t cancel_func;
+ void *cancel_baton;
+} upgrade_baton_t;
+
+/* Upgrade the FS given in upgrade_baton_t *)BATON to the latest format
+ * version. Apply options an invoke callback from that BATON.
+ * Temporary allocations are to be made from SCRATCH_POOL.
+ *
+ * At the moment, this is a simple placeholder as we don't support upgrades
+ * from experimental FSX versions.
+ */
+static svn_error_t *
+upgrade_body(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ upgrade_baton_t *upgrade_baton = baton;
+ svn_fs_t *fs = upgrade_baton->fs;
+ int format, max_files_per_dir;
+ const char *format_path = svn_fs_x__path_format(fs, scratch_pool);
+
+ /* Read the FS format number and max-files-per-dir setting. */
+ SVN_ERR(read_format(&format, &max_files_per_dir, format_path,
+ scratch_pool));
+
+ /* If we're already up-to-date, there's nothing else to be done here. */
+ if (format == SVN_FS_X__FORMAT_NUMBER)
+ return SVN_NO_ERROR;
+
+ /* Done */
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_x__upgrade(svn_fs_t *fs,
+ svn_fs_upgrade_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *scratch_pool)
+{
+ upgrade_baton_t baton;
+ baton.fs = fs;
+ baton.notify_func = notify_func;
+ baton.notify_baton = notify_baton;
+ baton.cancel_func = cancel_func;
+ baton.cancel_baton = cancel_baton;
+
+ return svn_fs_x__with_all_locks(fs, upgrade_body, (void *)&baton,
+ scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__youngest_rev(svn_revnum_t *youngest_p,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ SVN_ERR(svn_fs_x__read_current(youngest_p, fs, scratch_pool));
+ ffd->youngest_rev_cache = *youngest_p;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__ensure_revision_exists(svn_revnum_t rev,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+
+ if (! SVN_IS_VALID_REVNUM(rev))
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("Invalid revision number '%ld'"), rev);
+
+
+ /* Did the revision exist the last time we checked the current
+ file? */
+ if (rev <= ffd->youngest_rev_cache)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_x__read_current(&ffd->youngest_rev_cache, fs, scratch_pool));
+
+ /* Check again. */
+ if (rev <= ffd->youngest_rev_cache)
+ return SVN_NO_ERROR;
+
+ return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("No such revision %ld"), rev);
+}
+
+
+svn_error_t *
+svn_fs_x__file_length(svn_filesize_t *length,
+ svn_fs_x__noderev_t *noderev)
+{
+ if (noderev->data_rep)
+ *length = noderev->data_rep->expanded_size;
+ else
+ *length = 0;
+
+ return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_fs_x__file_text_rep_equal(svn_fs_x__representation_t *a,
+ svn_fs_x__representation_t *b)
+{
+ svn_boolean_t a_empty = a == NULL || a->expanded_size == 0;
+ svn_boolean_t b_empty = b == NULL || b->expanded_size == 0;
+
+ /* This makes sure that neither rep will be NULL later on */
+ if (a_empty && b_empty)
+ return TRUE;
+
+ if (a_empty != b_empty)
+ return FALSE;
+
+ /* Same physical representation? Note that these IDs are always up-to-date
+ instead of e.g. being set lazily. */
+ if (svn_fs_x__id_eq(&a->id, &b->id))
+ return TRUE;
+
+ /* Contents are equal if the checksums match. These are also always known.
+ */
+ return memcmp(a->md5_digest, b->md5_digest, sizeof(a->md5_digest)) == 0
+ && memcmp(a->sha1_digest, b->sha1_digest, sizeof(a->sha1_digest)) == 0;
+}
+
+svn_error_t *
+svn_fs_x__prop_rep_equal(svn_boolean_t *equal,
+ svn_fs_t *fs,
+ svn_fs_x__noderev_t *a,
+ svn_fs_x__noderev_t *b,
+ svn_boolean_t strict,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__representation_t *rep_a = a->prop_rep;
+ svn_fs_x__representation_t *rep_b = b->prop_rep;
+ apr_hash_t *proplist_a;
+ apr_hash_t *proplist_b;
+
+ /* Mainly for a==b==NULL */
+ if (rep_a == rep_b)
+ {
+ *equal = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Committed property lists can be compared quickly */
+ if ( rep_a && rep_b
+ && svn_fs_x__is_revision(rep_a->id.change_set)
+ && svn_fs_x__is_revision(rep_b->id.change_set))
+ {
+ /* MD5 must be given. Having the same checksum is good enough for
+ accepting the prop lists as equal. */
+ *equal = memcmp(rep_a->md5_digest, rep_b->md5_digest,
+ sizeof(rep_a->md5_digest)) == 0;
+ return SVN_NO_ERROR;
+ }
+
+ /* Same path in same txn? */
+ if (svn_fs_x__id_eq(&a->noderev_id, &b->noderev_id))
+ {
+ *equal = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Skip the expensive bits unless we are in strict mode.
+ Simply assume that there is a different. */
+ if (!strict)
+ {
+ *equal = FALSE;
+ return SVN_NO_ERROR;
+ }
+
+ /* At least one of the reps has been modified in a txn.
+ Fetch and compare them. */
+ SVN_ERR(svn_fs_x__get_proplist(&proplist_a, fs, a, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_fs_x__get_proplist(&proplist_b, fs, b, scratch_pool,
+ scratch_pool));
+
+ *equal = svn_fs__prop_lists_equal(proplist_a, proplist_b, scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_x__file_checksum(svn_checksum_t **checksum,
+ svn_fs_x__noderev_t *noderev,
+ svn_checksum_kind_t kind,
+ apr_pool_t *result_pool)
+{
+ *checksum = NULL;
+
+ if (noderev->data_rep)
+ {
+ svn_checksum_t temp;
+ temp.kind = kind;
+
+ switch(kind)
+ {
+ case svn_checksum_md5:
+ temp.digest = noderev->data_rep->md5_digest;
+ break;
+
+ case svn_checksum_sha1:
+ if (! noderev->data_rep->has_sha1)
+ return SVN_NO_ERROR;
+
+ temp.digest = noderev->data_rep->sha1_digest;
+ break;
+
+ default:
+ return SVN_NO_ERROR;
+ }
+
+ *checksum = svn_checksum_dup(&temp, result_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_fs_x__representation_t *
+svn_fs_x__rep_copy(svn_fs_x__representation_t *rep,
+ apr_pool_t *result_pool)
+{
+ if (rep == NULL)
+ return NULL;
+
+ return apr_pmemdup(result_pool, rep, sizeof(*rep));
+}
+
+
+/* Write out the zeroth revision for filesystem FS.
+ Perform temporary allocations in SCRATCH_POOL. */
+static svn_error_t *
+write_revision_zero(svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ /* Use an explicit sub-pool to have full control over temp file lifetimes.
+ * Since we have it, use it for everything else as well. */
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
+ const char *path_revision_zero = svn_fs_x__path_rev(fs, 0, subpool);
+ apr_hash_t *proplist;
+ svn_string_t date;
+
+ apr_array_header_t *index_entries;
+ svn_fs_x__p2l_entry_t *entry;
+ svn_fs_x__revision_file_t *rev_file;
+ const char *l2p_proto_index, *p2l_proto_index;
+
+ /* Construct a skeleton r0 with no indexes. */
+ svn_string_t *noderev_str = svn_string_create("id: 2+0\n"
+ "node: 0+0\n"
+ "copy: 0+0\n"
+ "type: dir\n"
+ "count: 0\n"
+ "cpath: /\n"
+ "\n",
+ subpool);
+ svn_string_t *changes_str = svn_string_create("\n",
+ subpool);
+ svn_string_t *r0 = svn_string_createf(subpool, "%s%s",
+ noderev_str->data,
+ changes_str->data);
+
+ /* Write skeleton r0 to disk. */
+ SVN_ERR(svn_io_file_create(path_revision_zero, r0->data, subpool));
+
+ /* Construct the index P2L contents: describe the 2 items we have.
+ Be sure to create them in on-disk order. */
+ index_entries = apr_array_make(subpool, 2, sizeof(entry));
+
+ entry = apr_pcalloc(subpool, sizeof(*entry));
+ entry->offset = 0;
+ entry->size = (apr_off_t)noderev_str->len;
+ entry->type = SVN_FS_X__ITEM_TYPE_NODEREV;
+ entry->item_count = 1;
+ entry->items = apr_pcalloc(subpool, sizeof(*entry->items));
+ entry->items[0].change_set = 0;
+ entry->items[0].number = SVN_FS_X__ITEM_INDEX_ROOT_NODE;
+ APR_ARRAY_PUSH(index_entries, svn_fs_x__p2l_entry_t *) = entry;
+
+ entry = apr_pcalloc(subpool, sizeof(*entry));
+ entry->offset = (apr_off_t)noderev_str->len;
+ entry->size = (apr_off_t)changes_str->len;
+ entry->type = SVN_FS_X__ITEM_TYPE_CHANGES;
+ entry->item_count = 1;
+ entry->items = apr_pcalloc(subpool, sizeof(*entry->items));
+ entry->items[0].change_set = 0;
+ entry->items[0].number = SVN_FS_X__ITEM_INDEX_CHANGES;
+ APR_ARRAY_PUSH(index_entries, svn_fs_x__p2l_entry_t *) = entry;
+
+ /* Now re-open r0, create proto-index files from our entries and
+ rewrite the index section of r0. */
+ SVN_ERR(svn_fs_x__open_pack_or_rev_file_writable(&rev_file, fs, 0,
+ subpool, subpool));
+ SVN_ERR(svn_fs_x__p2l_index_from_p2l_entries(&p2l_proto_index, fs,
+ rev_file, index_entries,
+ subpool, subpool));
+ SVN_ERR(svn_fs_x__l2p_index_from_p2l_entries(&l2p_proto_index, fs,
+ index_entries,
+ subpool, subpool));
+ SVN_ERR(svn_fs_x__add_index_data(fs, rev_file->file, l2p_proto_index,
+ p2l_proto_index, 0, subpool));
+ SVN_ERR(svn_fs_x__close_revision_file(rev_file));
+
+ SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
+
+ /* Set a date on revision 0. */
+ date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
+ date.len = strlen(date.data);
+ proplist = apr_hash_make(fs->pool);
+ svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
+ return svn_fs_x__set_revision_proplist(fs, 0, proplist, fs->pool);
+}
+
+svn_error_t *
+svn_fs_x__create_file_tree(svn_fs_t *fs,
+ const char *path,
+ int format,
+ int shard_size,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+
+ fs->path = apr_pstrdup(fs->pool, path);
+ ffd->format = format;
+
+ /* Use an appropriate sharding mode if supported by the format. */
+ ffd->max_files_per_dir = shard_size;
+
+ /* Create the revision data directories. */
+ SVN_ERR(svn_io_make_dir_recursively(
+ svn_fs_x__path_rev_shard(fs, 0, scratch_pool),
+ scratch_pool));
+
+ /* Create the revprops directory. */
+ SVN_ERR(svn_io_make_dir_recursively(
+ svn_fs_x__path_revprops_shard(fs, 0, scratch_pool),
+ scratch_pool));
+
+ /* Create the transaction directory. */
+ SVN_ERR(svn_io_make_dir_recursively(
+ svn_fs_x__path_txns_dir(fs, scratch_pool),
+ scratch_pool));
+
+ /* Create the protorevs directory. */
+ SVN_ERR(svn_io_make_dir_recursively(
+ svn_fs_x__path_txn_proto_revs(fs, scratch_pool),
+ scratch_pool));
+
+ /* Create the 'current' file. */
+ SVN_ERR(svn_io_file_create_empty(svn_fs_x__path_current(fs, scratch_pool),
+ scratch_pool));
+ SVN_ERR(svn_fs_x__write_current(fs, 0, scratch_pool));
+
+ /* Create the 'uuid' file. */
+ SVN_ERR(svn_io_file_create_empty(svn_fs_x__path_lock(fs, scratch_pool),
+ scratch_pool));
+ SVN_ERR(svn_fs_x__set_uuid(fs, NULL, NULL, scratch_pool));
+
+ /* Create the fsfs.conf file. */
+ SVN_ERR(write_config(fs, scratch_pool));
+ SVN_ERR(read_config(ffd, fs->path, fs->pool, scratch_pool));
+
+ /* Add revision 0. */
+ SVN_ERR(write_revision_zero(fs, scratch_pool));
+
+ /* Create the min unpacked rev file. */
+ SVN_ERR(svn_io_file_create(
+ svn_fs_x__path_min_unpacked_rev(fs, scratch_pool),
+ "0\n", scratch_pool));
+
+ /* Create the txn-current file if the repository supports
+ the transaction sequence file. */
+ SVN_ERR(svn_io_file_create(svn_fs_x__path_txn_current(fs, scratch_pool),
+ "0\n", scratch_pool));
+ SVN_ERR(svn_io_file_create_empty(
+ svn_fs_x__path_txn_current_lock(fs, scratch_pool),
+ scratch_pool));
+
+ /* Initialize the revprop caching info. */
+ SVN_ERR(svn_fs_x__reset_revprop_generation_file(fs, scratch_pool));
+
+ ffd->youngest_rev_cache = 0;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__create(svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ int format = SVN_FS_X__FORMAT_NUMBER;
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+
+ fs->path = apr_pstrdup(fs->pool, path);
+ /* See if compatibility with older versions was explicitly requested. */
+ if (fs->config)
+ {
+ svn_version_t *compatible_version;
+ SVN_ERR(svn_fs__compatible_version(&compatible_version, fs->config,
+ scratch_pool));
+
+ /* select format number */
+ switch(compatible_version->minor)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ case 8: return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
+ _("FSX is not compatible with Subversion prior to 1.9"));
+
+ default:format = SVN_FS_X__FORMAT_NUMBER;
+ }
+ }
+
+ /* Actual FS creation. */
+ SVN_ERR(svn_fs_x__create_file_tree(fs, path, format,
+ SVN_FS_X_DEFAULT_MAX_FILES_PER_DIR,
+ scratch_pool));
+
+ /* This filesystem is ready. Stamp it with a format number. */
+ SVN_ERR(svn_fs_x__write_format(fs, FALSE, scratch_pool));
+
+ ffd->youngest_rev_cache = 0;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__set_uuid(svn_fs_t *fs,
+ const char *uuid,
+ const char *instance_id,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ const char *uuid_path = svn_fs_x__path_uuid(fs, scratch_pool);
+ svn_stringbuf_t *contents = svn_stringbuf_create_empty(scratch_pool);
+
+ if (! uuid)
+ uuid = svn_uuid_generate(scratch_pool);
+
+ if (! instance_id)
+ instance_id = svn_uuid_generate(scratch_pool);
+
+ svn_stringbuf_appendcstr(contents, uuid);
+ svn_stringbuf_appendcstr(contents, "\n");
+ svn_stringbuf_appendcstr(contents, instance_id);
+ svn_stringbuf_appendcstr(contents, "\n");
+
+ /* We use the permissions of the 'current' file, because the 'uuid'
+ file does not exist during repository creation. */
+ SVN_ERR(svn_io_write_atomic(uuid_path, contents->data, contents->len,
+ /* perms */
+ svn_fs_x__path_current(fs, scratch_pool),
+ scratch_pool));
+
+ fs->uuid = apr_pstrdup(fs->pool, uuid);
+ ffd->instance_id = apr_pstrdup(fs->pool, instance_id);
+
+ return SVN_NO_ERROR;
+}
+
+/** Node origin lazy cache. */
+
+/* If directory PATH does not exist, create it and give it the same
+ permissions as FS_path.*/
+svn_error_t *
+svn_fs_x__ensure_dir_exists(const char *path,
+ const char *fs_path,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, scratch_pool);
+ if (err && APR_STATUS_IS_EEXIST(err->apr_err))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* We successfully created a new directory. Dup the permissions
+ from FS->path. */
+ return svn_io_copy_perms(fs_path, path, scratch_pool);
+}
+
+
+/*** Revisions ***/
+
+svn_error_t *
+svn_fs_x__revision_prop(svn_string_t **value_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *propname,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *table;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ SVN_ERR(svn_fs_x__get_revision_proplist(&table, fs, rev, FALSE,
+ scratch_pool, scratch_pool));
+
+ *value_p = svn_string_dup(svn_hash_gets(table, propname), result_pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Baton used for change_rev_prop_body below. */
+typedef struct change_rev_prop_baton_t {
+ svn_fs_t *fs;
+ svn_revnum_t rev;
+ const char *name;
+ const svn_string_t *const *old_value_p;
+ const svn_string_t *value;
+} change_rev_prop_baton_t;
+
+/* The work-horse for svn_fs_x__change_rev_prop, called with the FS
+ write lock. This implements the svn_fs_x__with_write_lock()
+ 'body' callback type. BATON is a 'change_rev_prop_baton_t *'. */
+static svn_error_t *
+change_rev_prop_body(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ change_rev_prop_baton_t *cb = baton;
+ apr_hash_t *table;
+
+ /* Read current revprop values from disk (never from cache).
+ Even if somehow the cache got out of sync, we want to make sure that
+ we read, update and write up-to-date data. */
+ SVN_ERR(svn_fs_x__get_revision_proplist(&table, cb->fs, cb->rev, TRUE,
+ scratch_pool, scratch_pool));
+
+ if (cb->old_value_p)
+ {
+ const svn_string_t *wanted_value = *cb->old_value_p;
+ const svn_string_t *present_value = svn_hash_gets(table, cb->name);
+ if ((!wanted_value != !present_value)
+ || (wanted_value && present_value
+ && !svn_string_compare(wanted_value, present_value)))
+ {
+ /* What we expected isn't what we found. */
+ return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
+ _("revprop '%s' has unexpected value in "
+ "filesystem"),
+ cb->name);
+ }
+ /* Fall through. */
+ }
+ svn_hash_sets(table, cb->name, cb->value);
+
+ return svn_fs_x__set_revision_proplist(cb->fs, cb->rev, table,
+ scratch_pool);
+}
+
+svn_error_t *
+svn_fs_x__change_rev_prop(svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ change_rev_prop_baton_t cb;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ cb.fs = fs;
+ cb.rev = rev;
+ cb.name = name;
+ cb.old_value_p = old_value_p;
+ cb.value = value;
+
+ return svn_fs_x__with_write_lock(fs, change_rev_prop_body, &cb,
+ scratch_pool);
+}
+
+
+svn_error_t *
+svn_fs_x__info_format(int *fs_format,
+ svn_version_t **supports_version,
+ svn_fs_t *fs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_x__data_t *ffd = fs->fsap_data;
+ *fs_format = ffd->format;
+ *supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
+
+ (*supports_version)->major = SVN_VER_MAJOR;
+ (*supports_version)->minor = 9;
+ (*supports_version)->patch = 0;
+ (*supports_version)->tag = "";
+
+ switch (ffd->format)
+ {
+ case 1:
+ break;
+#ifdef SVN_DEBUG
+# if SVN_FS_X__FORMAT_NUMBER != 1
+# error "Need to add a 'case' statement here"
+# endif
+#endif
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_x__info_config_files(apr_array_header_t **files,
+ svn_fs_t *fs,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ *files = apr_array_make(result_pool, 1, sizeof(const char *));
+ APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path, PATH_CONFIG,
+ result_pool);
+ return SVN_NO_ERROR;
+}