diff options
Diffstat (limited to 'subversion/libsvn_fs_x/fs_x.c')
-rw-r--r-- | subversion/libsvn_fs_x/fs_x.c | 1228 |
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; +} |