1/* fs_x.c --- filesystem operations specific to fs_x
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 "fs_x.h"
24
25#include <apr_uuid.h>
26
27#include "svn_hash.h"
28#include "svn_props.h"
29#include "svn_time.h"
30#include "svn_dirent_uri.h"
31#include "svn_sorts.h"
32#include "svn_version.h"
33
34#include "cached_data.h"
35#include "id.h"
36#include "rep-cache.h"
37#include "revprops.h"
38#include "transaction.h"
39#include "tree.h"
40#include "util.h"
41#include "index.h"
42
43#include "private/svn_fs_util.h"
44#include "private/svn_string_private.h"
45#include "private/svn_subr_private.h"
46#include "../libsvn_fs/fs-loader.h"
47
48#include "svn_private_config.h"
49
50/* The default maximum number of files per directory to store in the
51   rev and revprops directory.  The number below is somewhat arbitrary,
52   and can be overridden by defining the macro while compiling; the
53   figure of 1000 is reasonable for VFAT filesystems, which are by far
54   the worst performers in this area. */
55#ifndef SVN_FS_X_DEFAULT_MAX_FILES_PER_DIR
56#define SVN_FS_X_DEFAULT_MAX_FILES_PER_DIR 1000
57#endif
58
59/* Begin deltification after a node history exceeded this this limit.
60   Useful values are 4 to 64 with 16 being a good compromise between
61   computational overhead and repository size savings.
62   Should be a power of 2.
63   Values < 2 will result in standard skip-delta behavior. */
64#define SVN_FS_X_MAX_LINEAR_DELTIFICATION 16
65
66/* Finding a deltification base takes operations proportional to the
67   number of changes being skipped. To prevent exploding runtime
68   during commits, limit the deltification range to this value.
69   Should be a power of 2 minus one.
70   Values < 1 disable deltification. */
71#define SVN_FS_X_MAX_DELTIFICATION_WALK 1023
72
73
74
75
76/* Check that BUF, a nul-terminated buffer of text from format file PATH,
77   contains only digits at OFFSET and beyond, raising an error if not.
78
79   Uses SCRATCH_POOL for temporary allocation. */
80static svn_error_t *
81check_format_file_buffer_numeric(const char *buf,
82                                 apr_off_t offset,
83                                 const char *path,
84                                 apr_pool_t *scratch_pool)
85{
86  return svn_fs_x__check_file_buffer_numeric(buf, offset, path, "Format",
87                                             scratch_pool);
88}
89
90/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
91   number is not the same as a format number supported by this
92   Subversion. */
93static svn_error_t *
94check_format(int format)
95{
96  /* Put blacklisted versions here. */
97
98  /* We support all formats from 1-current simultaneously */
99  if (1 <= format && format <= SVN_FS_X__FORMAT_NUMBER)
100    return SVN_NO_ERROR;
101
102  return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
103     _("Expected FS format between '1' and '%d'; found format '%d'"),
104     SVN_FS_X__FORMAT_NUMBER, format);
105}
106
107/* Read the format file at PATH and set *PFORMAT to the format version found
108 * and *MAX_FILES_PER_DIR to the shard size.  Use SCRATCH_POOL for temporary
109 * allocations. */
110static svn_error_t *
111read_format(int *pformat,
112            int *max_files_per_dir,
113            const char *path,
114            apr_pool_t *scratch_pool)
115{
116  svn_stream_t *stream;
117  svn_stringbuf_t *content;
118  svn_stringbuf_t *buf;
119  svn_boolean_t eos = FALSE;
120
121  SVN_ERR(svn_stringbuf_from_file2(&content, path, scratch_pool));
122  stream = svn_stream_from_stringbuf(content, scratch_pool);
123  SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, scratch_pool));
124  if (buf->len == 0 && eos)
125    {
126      /* Return a more useful error message. */
127      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
128                               _("Can't read first line of format file '%s'"),
129                               svn_dirent_local_style(path, scratch_pool));
130    }
131
132  /* Check that the first line contains only digits. */
133  SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, scratch_pool));
134  SVN_ERR(svn_cstring_atoi(pformat, buf->data));
135
136  /* Check that we support this format at all */
137  SVN_ERR(check_format(*pformat));
138
139  /* Read any options. */
140  SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, scratch_pool));
141  if (!eos && strncmp(buf->data, "layout sharded ", 15) == 0)
142    {
143      /* Check that the argument is numeric. */
144      SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path,
145                                               scratch_pool));
146      SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15));
147    }
148  else
149    return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
150                  _("'%s' contains invalid filesystem format option '%s'"),
151                  svn_dirent_local_style(path, scratch_pool), buf->data);
152
153  return SVN_NO_ERROR;
154}
155
156/* Write the format number and maximum number of files per directory
157   to a new format file in PATH, possibly expecting to overwrite a
158   previously existing file.
159
160   Use SCRATCH_POOL for temporary allocation. */
161svn_error_t *
162svn_fs_x__write_format(svn_fs_t *fs,
163                       svn_boolean_t overwrite,
164                       apr_pool_t *scratch_pool)
165{
166  svn_stringbuf_t *sb;
167  const char *path = svn_fs_x__path_format(fs, scratch_pool);
168  svn_fs_x__data_t *ffd = fs->fsap_data;
169
170  SVN_ERR_ASSERT(1 <= ffd->format && ffd->format <= SVN_FS_X__FORMAT_NUMBER);
171
172  sb = svn_stringbuf_createf(scratch_pool, "%d\n", ffd->format);
173  svn_stringbuf_appendcstr(sb, apr_psprintf(scratch_pool,
174                                            "layout sharded %d\n",
175                                            ffd->max_files_per_dir));
176
177  /* svn_io_write_version_file() does a load of magic to allow it to
178     replace version files that already exist.  We only need to do
179     that when we're allowed to overwrite an existing file. */
180  if (! overwrite)
181    {
182      /* Create the file */
183      SVN_ERR(svn_io_file_create(path, sb->data, scratch_pool));
184    }
185  else
186    {
187      SVN_ERR(svn_io_write_atomic(path, sb->data, sb->len,
188                                  NULL /* copy_perms_path */, scratch_pool));
189    }
190
191  /* And set the perms to make it read only */
192  return svn_io_set_file_read_only(path, FALSE, scratch_pool);
193}
194
195/* Check that BLOCK_SIZE is a valid block / page size, i.e. it is within
196 * the range of what the current system may address in RAM and it is a
197 * power of 2.  Assume that the element size within the block is ITEM_SIZE.
198 * Use SCRATCH_POOL for temporary allocations.
199 */
200static svn_error_t *
201verify_block_size(apr_int64_t block_size,
202                  apr_size_t item_size,
203                  const char *name,
204                  apr_pool_t *scratch_pool)
205{
206  /* Limit range. */
207  if (block_size <= 0)
208    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
209                             _("%s is too small for fsfs.conf setting '%s'."),
210                             apr_psprintf(scratch_pool,
211                                          "%" APR_INT64_T_FMT,
212                                          block_size),
213                             name);
214
215  if (block_size > SVN_MAX_OBJECT_SIZE / item_size)
216    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
217                             _("%s is too large for fsfs.conf setting '%s'."),
218                             apr_psprintf(scratch_pool,
219                                          "%" APR_INT64_T_FMT,
220                                          block_size),
221                             name);
222
223  /* Ensure it is a power of two.
224   * For positive X,  X & (X-1) will reset the lowest bit set.
225   * If the result is 0, at most one bit has been set. */
226  if (0 != (block_size & (block_size - 1)))
227    return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
228                             _("%s is invalid for fsfs.conf setting '%s' "
229                               "because it is not a power of 2."),
230                             apr_psprintf(scratch_pool,
231                                          "%" APR_INT64_T_FMT,
232                                          block_size),
233                             name);
234
235  return SVN_NO_ERROR;
236}
237
238/* Read the configuration information of the file system at FS_PATH
239 * and set the respective values in FFD.  Use pools as usual.
240 */
241static svn_error_t *
242read_config(svn_fs_x__data_t *ffd,
243            const char *fs_path,
244            apr_pool_t *result_pool,
245            apr_pool_t *scratch_pool)
246{
247  svn_config_t *config;
248  apr_int64_t compression_level;
249
250  SVN_ERR(svn_config_read3(&config,
251                           svn_dirent_join(fs_path, PATH_CONFIG, scratch_pool),
252                           FALSE, FALSE, FALSE, scratch_pool));
253
254  /* Initialize ffd->rep_sharing_allowed. */
255  SVN_ERR(svn_config_get_bool(config, &ffd->rep_sharing_allowed,
256                              CONFIG_SECTION_REP_SHARING,
257                              CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
258
259  /* Initialize deltification settings in ffd. */
260  SVN_ERR(svn_config_get_int64(config, &ffd->max_deltification_walk,
261                               CONFIG_SECTION_DELTIFICATION,
262                               CONFIG_OPTION_MAX_DELTIFICATION_WALK,
263                               SVN_FS_X_MAX_DELTIFICATION_WALK));
264  SVN_ERR(svn_config_get_int64(config, &ffd->max_linear_deltification,
265                               CONFIG_SECTION_DELTIFICATION,
266                               CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
267                               SVN_FS_X_MAX_LINEAR_DELTIFICATION));
268  SVN_ERR(svn_config_get_int64(config, &compression_level,
269                               CONFIG_SECTION_DELTIFICATION,
270                               CONFIG_OPTION_COMPRESSION_LEVEL,
271                               SVN_DELTA_COMPRESSION_LEVEL_DEFAULT));
272  ffd->delta_compression_level
273    = (int)MIN(MAX(SVN_DELTA_COMPRESSION_LEVEL_NONE, compression_level),
274                SVN_DELTA_COMPRESSION_LEVEL_MAX);
275
276  /* Initialize revprop packing settings in ffd. */
277  SVN_ERR(svn_config_get_bool(config, &ffd->compress_packed_revprops,
278                              CONFIG_SECTION_PACKED_REVPROPS,
279                              CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
280                              TRUE));
281  SVN_ERR(svn_config_get_int64(config, &ffd->revprop_pack_size,
282                               CONFIG_SECTION_PACKED_REVPROPS,
283                               CONFIG_OPTION_REVPROP_PACK_SIZE,
284                               ffd->compress_packed_revprops
285                                   ? 0x100
286                                   : 0x40));
287
288  ffd->revprop_pack_size *= 1024;
289
290  /* I/O settings in ffd. */
291  SVN_ERR(svn_config_get_int64(config, &ffd->block_size,
292                               CONFIG_SECTION_IO,
293                               CONFIG_OPTION_BLOCK_SIZE,
294                               64));
295  SVN_ERR(svn_config_get_int64(config, &ffd->l2p_page_size,
296                               CONFIG_SECTION_IO,
297                               CONFIG_OPTION_L2P_PAGE_SIZE,
298                               0x2000));
299  SVN_ERR(svn_config_get_int64(config, &ffd->p2l_page_size,
300                               CONFIG_SECTION_IO,
301                               CONFIG_OPTION_P2L_PAGE_SIZE,
302                               0x400));
303
304  /* Don't accept unreasonable or illegal values.
305   * Block size and P2L page size are in kbytes;
306   * L2P blocks are arrays of apr_off_t. */
307  SVN_ERR(verify_block_size(ffd->block_size, 0x400,
308                            CONFIG_OPTION_BLOCK_SIZE, scratch_pool));
309  SVN_ERR(verify_block_size(ffd->p2l_page_size, 0x400,
310                            CONFIG_OPTION_P2L_PAGE_SIZE, scratch_pool));
311  SVN_ERR(verify_block_size(ffd->l2p_page_size, sizeof(apr_off_t),
312                            CONFIG_OPTION_L2P_PAGE_SIZE, scratch_pool));
313
314  /* convert kBytes to bytes */
315  ffd->block_size *= 0x400;
316  ffd->p2l_page_size *= 0x400;
317  /* L2P pages are in entries - not in (k)Bytes */
318
319  /* Debug options. */
320  SVN_ERR(svn_config_get_bool(config, &ffd->pack_after_commit,
321                              CONFIG_SECTION_DEBUG,
322                              CONFIG_OPTION_PACK_AFTER_COMMIT,
323                              FALSE));
324
325  /* memcached configuration */
326  SVN_ERR(svn_cache__make_memcache_from_config(&ffd->memcache, config,
327                                               result_pool, scratch_pool));
328
329  SVN_ERR(svn_config_get_bool(config, &ffd->fail_stop,
330                              CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
331                              FALSE));
332
333  return SVN_NO_ERROR;
334}
335
336/* Write FS' initial configuration file.
337 * Use SCRATCH_POOL for temporary allocations. */
338static svn_error_t *
339write_config(svn_fs_t *fs,
340             apr_pool_t *scratch_pool)
341{
342#define NL APR_EOL_STR
343  static const char * const fsx_conf_contents =
344"### This file controls the configuration of the FSX filesystem."            NL
345""                                                                           NL
346"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]"                          NL
347"### These options name memcached servers used to cache internal FSX"        NL
348"### data.  See http://www.danga.com/memcached/ for more information on"     NL
349"### memcached.  To use memcached with FSX, run one or more memcached"       NL
350"### servers, and specify each of them as an option like so:"                NL
351"# first-server = 127.0.0.1:11211"                                           NL
352"# remote-memcached = mymemcached.corp.example.com:11212"                    NL
353"### The option name is ignored; the value is of the form HOST:PORT."        NL
354"### memcached servers can be shared between multiple repositories;"         NL
355"### however, if you do this, you *must* ensure that repositories have"      NL
356"### distinct UUIDs and paths, or else cached data from one repository"      NL
357"### might be used by another accidentally.  Note also that memcached has"   NL
358"### no authentication for reads or writes, so you must ensure that your"    NL
359"### memcached servers are only accessible by trusted users."                NL
360""                                                                           NL
361"[" CONFIG_SECTION_CACHES "]"                                                NL
362"### When a cache-related error occurs, normally Subversion ignores it"      NL
363"### and continues, logging an error if the server is appropriately"         NL
364"### configured (and ignoring it with file:// access).  To make"             NL
365"### Subversion never ignore cache errors, uncomment this line."             NL
366"# " CONFIG_OPTION_FAIL_STOP " = true"                                       NL
367""                                                                           NL
368"[" CONFIG_SECTION_REP_SHARING "]"                                           NL
369"### To conserve space, the filesystem can optionally avoid storing"         NL
370"### duplicate representations.  This comes at a slight cost in"             NL
371"### performance, as maintaining a database of shared representations can"   NL
372"### increase commit times.  The space savings are dependent upon the size"  NL
373"### of the repository, the number of objects it contains and the amount of" NL
374"### duplication between them, usually a function of the branching and"      NL
375"### merging process."                                                       NL
376"###"                                                                        NL
377"### The following parameter enables rep-sharing in the repository.  It can" NL
378"### be switched on and off at will, but for best space-saving results"      NL
379"### should be enabled consistently over the life of the repository."        NL
380"### 'svnadmin verify' will check the rep-cache regardless of this setting." NL
381"### rep-sharing is enabled by default."                                     NL
382"# " CONFIG_OPTION_ENABLE_REP_SHARING " = true"                              NL
383""                                                                           NL
384"[" CONFIG_SECTION_DELTIFICATION "]"                                         NL
385"### To conserve space, the filesystem stores data as differences against"   NL
386"### existing representations.  This comes at a slight cost in performance," NL
387"### as calculating differences can increase commit times.  Reading data"    NL
388"### will also create higher CPU load and the data will be fragmented."      NL
389"### Since deltification tends to save significant amounts of disk space,"   NL
390"### the overall I/O load can actually be lower."                            NL
391"###"                                                                        NL
392"### The options in this section allow for tuning the deltification"         NL
393"### strategy.  Their effects on data size and server performance may vary"  NL
394"### from one repository to another."                                        NL
395"###"                                                                        NL
396"### During commit, the server may need to walk the whole change history of" NL
397"### of a given node to find a suitable deltification base.  This linear"    NL
398"### process can impact commit times, svnadmin load and similar operations." NL
399"### This setting limits the depth of the deltification history.  If the"    NL
400"### threshold has been reached, the node will be stored as fulltext and a"  NL
401"### new deltification history begins."                                      NL
402"### Note, this is unrelated to svn log."                                    NL
403"### Very large values rarely provide significant additional savings but"    NL
404"### can impact performance greatly - in particular if directory"            NL
405"### deltification has been activated.  Very small values may be useful in"  NL
406"### repositories that are dominated by large, changing binaries."           NL
407"### Should be a power of two minus 1.  A value of 0 will effectively"       NL
408"### disable deltification."                                                 NL
409"### For 1.9, the default value is 1023."                                    NL
410"# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023"                          NL
411"###"                                                                        NL
412"### The skip-delta scheme used by FSX tends to repeatably store redundant"  NL
413"### delta information where a simple delta against the latest version is"   NL
414"### often smaller.  By default, 1.9+ will therefore use skip deltas only"   NL
415"### after the linear chain of deltas has grown beyond the threshold"        NL
416"### specified by this setting."                                             NL
417"### Values up to 64 can result in some reduction in repository size for"    NL
418"### the cost of quickly increasing I/O and CPU costs. Similarly, smaller"   NL
419"### numbers can reduce those costs at the cost of more disk space.  For"    NL
420"### rarely read repositories or those containing larger binaries, this may" NL
421"### present a better trade-off."                                            NL
422"### Should be a power of two.  A value of 1 or smaller will cause the"      NL
423"### exclusive use of skip-deltas."                                          NL
424"### For 1.8, the default value is 16."                                      NL
425"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16"                          NL
426"###"                                                                        NL
427"### After deltification, we compress the data through zlib to minimize on-" NL
428"### disk size.  That can be an expensive and ineffective process.  This"    NL
429"### setting controls the usage of zlib in future revisions."                NL
430"### Revisions with highly compressible data in them may shrink in size"     NL
431"### if the setting is increased but may take much longer to commit.  The"   NL
432"### time taken to uncompress that data again is widely independent of the"  NL
433"### compression level."                                                     NL
434"### Compression will be ineffective if the incoming content is already"     NL
435"### highly compressed.  In that case, disabling the compression entirely"   NL
436"### will speed up commits as well as reading the data.  Repositories with"  NL
437"### many small compressible files (source code) but also a high percentage" NL
438"### of large incompressible ones (artwork) may benefit from compression"    NL
439"### levels lowered to e.g. 1."                                              NL
440"### Valid values are 0 to 9 with 9 providing the highest compression ratio" NL
441"### and 0 disabling it altogether."                                         NL
442"### The default value is 5."                                                NL
443"# " CONFIG_OPTION_COMPRESSION_LEVEL " = 5"                                  NL
444""                                                                           NL
445"[" CONFIG_SECTION_PACKED_REVPROPS "]"                                       NL
446"### This parameter controls the size (in kBytes) of packed revprop files."  NL
447"### Revprops of consecutive revisions will be concatenated into a single"   NL
448"### file up to but not exceeding the threshold given here.  However, each"  NL
449"### pack file may be much smaller and revprops of a single revision may be" NL
450"### much larger than the limit set here.  The threshold will be applied"    NL
451"### before optional compression takes place."                               NL
452"### Large values will reduce disk space usage at the expense of increased"  NL
453"### latency and CPU usage reading and changing individual revprops.  They"  NL
454"### become an advantage when revprop caching has been enabled because a"    NL
455"### lot of data can be read in one go.  Values smaller than 4 kByte will"   NL
456"### not improve latency any further and quickly render revprop packing"     NL
457"### ineffective."                                                           NL
458"### revprop-pack-size is 64 kBytes by default for non-compressed revprop"   NL
459"### pack files and 256 kBytes when compression has been enabled."           NL
460"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 64"                                 NL
461"###"                                                                        NL
462"### To save disk space, packed revprop files may be compressed.  Standard"  NL
463"### revprops tend to allow for very effective compression.  Reading and"    NL
464"### even more so writing, become significantly more CPU intensive.  With"   NL
465"### revprop caching enabled, the overhead can be offset by reduced I/O"     NL
466"### unless you often modify revprops after packing."                        NL
467"### Compressing packed revprops is enabled by default."                     NL
468"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = true"                        NL
469""                                                                           NL
470"[" CONFIG_SECTION_IO "]"                                                    NL
471"### Parameters in this section control the data access granularity in"      NL
472"### format 7 repositories and later.  The defaults should translate into"   NL
473"### decent performance over a wide range of setups."                        NL
474"###"                                                                        NL
475"### When a specific piece of information needs to be read from disk,  a"    NL
476"### data block is being read at once and its contents are being cached."    NL
477"### If the repository is being stored on a RAID, the block size should be"  NL
478"### either 50% or 100% of RAID block size / granularity.  Also, your file"  NL
479"### system blocks/clusters should be properly aligned and sized.  In that"  NL
480"### setup, each access will hit only one disk (minimizes I/O load) but"     NL
481"### uses all the data provided by the disk in a single access."             NL
482"### For SSD-based storage systems, slightly lower values around 16 kB"      NL
483"### may improve latency while still maximizing throughput."                 NL
484"### Can be changed at any time but must be a power of 2."                   NL
485"### block-size is given in kBytes and with a default of 64 kBytes."         NL
486"# " CONFIG_OPTION_BLOCK_SIZE " = 64"                                        NL
487"###"                                                                        NL
488"### The log-to-phys index maps data item numbers to offsets within the"     NL
489"### rev or pack file.  This index is organized in pages of a fixed maximum" NL
490"### capacity.  To access an item, the page table and the respective page"   NL
491"### must be read."                                                          NL
492"### This parameter only affects revisions with thousands of changed paths." NL
493"### If you have several extremely large revisions (~1 mio changes), think"  NL
494"### about increasing this setting.  Reducing the value will rarely result"  NL
495"### in a net speedup."                                                      NL
496"### This is an expert setting.  Must be a power of 2."                      NL
497"### l2p-page-size is 8192 entries by default."                              NL
498"# " CONFIG_OPTION_L2P_PAGE_SIZE " = 8192"                                   NL
499"###"                                                                        NL
500"### The phys-to-log index maps positions within the rev or pack file to"    NL
501"### to data items,  i.e. describes what piece of information is being"      NL
502"### stored at any particular offset.  The index describes the rev file"     NL
503"### in chunks (pages) and keeps a global list of all those pages.  Large"   NL
504"### pages mean a shorter page table but a larger per-page description of"   NL
505"### data items in it.  The latency sweet spot depends on the change size"   NL
506"### distribution but covers a relatively wide range."                       NL
507"### If the repository contains very large files,  i.e. individual changes"  NL
508"### of tens of MB each,  increasing the page size will shorten the index"   NL
509"### file at the expense of a slightly increased latency in sections with"   NL
510"### smaller changes."                                                       NL
511"### For source code repositories, this should be about 16x the block-size." NL
512"### Must be a power of 2."                                                  NL
513"### p2l-page-size is given in kBytes and with a default of 1024 kBytes."    NL
514"# " CONFIG_OPTION_P2L_PAGE_SIZE " = 1024"                                   NL
515;
516#undef NL
517  return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG,
518                                            scratch_pool),
519                            fsx_conf_contents, scratch_pool);
520}
521
522/* Read FS's UUID file and store the data in the FS struct. */
523static svn_error_t *
524read_uuid(svn_fs_t *fs,
525          apr_pool_t *scratch_pool)
526{
527  svn_fs_x__data_t *ffd = fs->fsap_data;
528  apr_file_t *uuid_file;
529  char buf[APR_UUID_FORMATTED_LENGTH + 2];
530  apr_size_t limit;
531
532  /* Read the repository uuid. */
533  SVN_ERR(svn_io_file_open(&uuid_file, svn_fs_x__path_uuid(fs, scratch_pool),
534                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
535                           scratch_pool));
536
537  limit = sizeof(buf);
538  SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, scratch_pool));
539  fs->uuid = apr_pstrdup(fs->pool, buf);
540
541  /* Read the instance ID. */
542  limit = sizeof(buf);
543  SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit,
544                                  scratch_pool));
545  ffd->instance_id = apr_pstrdup(fs->pool, buf);
546
547  SVN_ERR(svn_io_file_close(uuid_file, scratch_pool));
548
549  return SVN_NO_ERROR;
550}
551
552svn_error_t *
553svn_fs_x__read_format_file(svn_fs_t *fs,
554                           apr_pool_t *scratch_pool)
555{
556  svn_fs_x__data_t *ffd = fs->fsap_data;
557  int format, max_files_per_dir;
558
559  /* Read info from format file. */
560  SVN_ERR(read_format(&format, &max_files_per_dir,
561                      svn_fs_x__path_format(fs, scratch_pool), scratch_pool));
562
563  /* Now that we've got *all* info, store / update values in FFD. */
564  ffd->format = format;
565  ffd->max_files_per_dir = max_files_per_dir;
566
567  return SVN_NO_ERROR;
568}
569
570svn_error_t *
571svn_fs_x__open(svn_fs_t *fs,
572               const char *path,
573               apr_pool_t *scratch_pool)
574{
575  svn_fs_x__data_t *ffd = fs->fsap_data;
576  fs->path = apr_pstrdup(fs->pool, path);
577
578  /* Read the FS format file. */
579  SVN_ERR(svn_fs_x__read_format_file(fs, scratch_pool));
580
581  /* Read in and cache the repository uuid. */
582  SVN_ERR(read_uuid(fs, scratch_pool));
583
584  /* Read the min unpacked revision. */
585  SVN_ERR(svn_fs_x__update_min_unpacked_rev(fs, scratch_pool));
586
587  /* Read the configuration file. */
588  SVN_ERR(read_config(ffd, fs->path, fs->pool, scratch_pool));
589
590  return svn_error_trace(svn_fs_x__read_current(&ffd->youngest_rev_cache,
591                                                fs, scratch_pool));
592}
593
594/* Baton type bridging svn_fs_x__upgrade and upgrade_body carrying
595 * parameters over between them. */
596typedef struct upgrade_baton_t
597{
598  svn_fs_t *fs;
599  svn_fs_upgrade_notify_t notify_func;
600  void *notify_baton;
601  svn_cancel_func_t cancel_func;
602  void *cancel_baton;
603} upgrade_baton_t;
604
605/* Upgrade the FS given in upgrade_baton_t *)BATON to the latest format
606 * version.  Apply options an invoke callback from that BATON.
607 * Temporary allocations are to be made from SCRATCH_POOL.
608 *
609 * At the moment, this is a simple placeholder as we don't support upgrades
610 * from experimental FSX versions.
611 */
612static svn_error_t *
613upgrade_body(void *baton,
614             apr_pool_t *scratch_pool)
615{
616  upgrade_baton_t *upgrade_baton = baton;
617  svn_fs_t *fs = upgrade_baton->fs;
618  int format, max_files_per_dir;
619  const char *format_path = svn_fs_x__path_format(fs, scratch_pool);
620
621  /* Read the FS format number and max-files-per-dir setting. */
622  SVN_ERR(read_format(&format, &max_files_per_dir, format_path,
623                      scratch_pool));
624
625  /* If we're already up-to-date, there's nothing else to be done here. */
626  if (format == SVN_FS_X__FORMAT_NUMBER)
627    return SVN_NO_ERROR;
628
629  /* Done */
630  return SVN_NO_ERROR;
631}
632
633
634svn_error_t *
635svn_fs_x__upgrade(svn_fs_t *fs,
636                  svn_fs_upgrade_notify_t notify_func,
637                  void *notify_baton,
638                  svn_cancel_func_t cancel_func,
639                  void *cancel_baton,
640                  apr_pool_t *scratch_pool)
641{
642  upgrade_baton_t baton;
643  baton.fs = fs;
644  baton.notify_func = notify_func;
645  baton.notify_baton = notify_baton;
646  baton.cancel_func = cancel_func;
647  baton.cancel_baton = cancel_baton;
648
649  return svn_fs_x__with_all_locks(fs, upgrade_body, (void *)&baton,
650                                  scratch_pool);
651}
652
653
654svn_error_t *
655svn_fs_x__youngest_rev(svn_revnum_t *youngest_p,
656                       svn_fs_t *fs,
657                       apr_pool_t *scratch_pool)
658{
659  svn_fs_x__data_t *ffd = fs->fsap_data;
660  SVN_ERR(svn_fs_x__read_current(youngest_p, fs, scratch_pool));
661  ffd->youngest_rev_cache = *youngest_p;
662
663  return SVN_NO_ERROR;
664}
665
666svn_error_t *
667svn_fs_x__ensure_revision_exists(svn_revnum_t rev,
668                                 svn_fs_t *fs,
669                                 apr_pool_t *scratch_pool)
670{
671  svn_fs_x__data_t *ffd = fs->fsap_data;
672
673  if (! SVN_IS_VALID_REVNUM(rev))
674    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
675                             _("Invalid revision number '%ld'"), rev);
676
677
678  /* Did the revision exist the last time we checked the current
679     file? */
680  if (rev <= ffd->youngest_rev_cache)
681    return SVN_NO_ERROR;
682
683  SVN_ERR(svn_fs_x__read_current(&ffd->youngest_rev_cache, fs, scratch_pool));
684
685  /* Check again. */
686  if (rev <= ffd->youngest_rev_cache)
687    return SVN_NO_ERROR;
688
689  return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
690                           _("No such revision %ld"), rev);
691}
692
693
694svn_error_t *
695svn_fs_x__file_length(svn_filesize_t *length,
696                      svn_fs_x__noderev_t *noderev)
697{
698  if (noderev->data_rep)
699    *length = noderev->data_rep->expanded_size;
700  else
701    *length = 0;
702
703  return SVN_NO_ERROR;
704}
705
706svn_boolean_t
707svn_fs_x__file_text_rep_equal(svn_fs_x__representation_t *a,
708                              svn_fs_x__representation_t *b)
709{
710  svn_boolean_t a_empty = a == NULL || a->expanded_size == 0;
711  svn_boolean_t b_empty = b == NULL || b->expanded_size == 0;
712
713  /* This makes sure that neither rep will be NULL later on */
714  if (a_empty && b_empty)
715    return TRUE;
716
717  if (a_empty != b_empty)
718    return FALSE;
719
720  /* Same physical representation?  Note that these IDs are always up-to-date
721     instead of e.g. being set lazily. */
722  if (svn_fs_x__id_eq(&a->id, &b->id))
723    return TRUE;
724
725  /* Contents are equal if the checksums match.  These are also always known.
726   */
727  return memcmp(a->md5_digest, b->md5_digest, sizeof(a->md5_digest)) == 0
728      && memcmp(a->sha1_digest, b->sha1_digest, sizeof(a->sha1_digest)) == 0;
729}
730
731svn_error_t *
732svn_fs_x__prop_rep_equal(svn_boolean_t *equal,
733                         svn_fs_t *fs,
734                         svn_fs_x__noderev_t *a,
735                         svn_fs_x__noderev_t *b,
736                         svn_boolean_t strict,
737                         apr_pool_t *scratch_pool)
738{
739  svn_fs_x__representation_t *rep_a = a->prop_rep;
740  svn_fs_x__representation_t *rep_b = b->prop_rep;
741  apr_hash_t *proplist_a;
742  apr_hash_t *proplist_b;
743
744  /* Mainly for a==b==NULL */
745  if (rep_a == rep_b)
746    {
747      *equal = TRUE;
748      return SVN_NO_ERROR;
749    }
750
751  /* Committed property lists can be compared quickly */
752  if (   rep_a && rep_b
753      && svn_fs_x__is_revision(rep_a->id.change_set)
754      && svn_fs_x__is_revision(rep_b->id.change_set))
755    {
756      /* MD5 must be given. Having the same checksum is good enough for
757         accepting the prop lists as equal. */
758      *equal = memcmp(rep_a->md5_digest, rep_b->md5_digest,
759                      sizeof(rep_a->md5_digest)) == 0;
760      return SVN_NO_ERROR;
761    }
762
763  /* Same path in same txn? */
764  if (svn_fs_x__id_eq(&a->noderev_id, &b->noderev_id))
765    {
766      *equal = TRUE;
767      return SVN_NO_ERROR;
768    }
769
770  /* Skip the expensive bits unless we are in strict mode.
771     Simply assume that there is a different. */
772  if (!strict)
773    {
774      *equal = FALSE;
775      return SVN_NO_ERROR;
776    }
777
778  /* At least one of the reps has been modified in a txn.
779     Fetch and compare them. */
780  SVN_ERR(svn_fs_x__get_proplist(&proplist_a, fs, a, scratch_pool,
781                                 scratch_pool));
782  SVN_ERR(svn_fs_x__get_proplist(&proplist_b, fs, b, scratch_pool,
783                                 scratch_pool));
784
785  *equal = svn_fs__prop_lists_equal(proplist_a, proplist_b, scratch_pool);
786  return SVN_NO_ERROR;
787}
788
789
790svn_error_t *
791svn_fs_x__file_checksum(svn_checksum_t **checksum,
792                        svn_fs_x__noderev_t *noderev,
793                        svn_checksum_kind_t kind,
794                        apr_pool_t *result_pool)
795{
796  *checksum = NULL;
797
798  if (noderev->data_rep)
799    {
800      svn_checksum_t temp;
801      temp.kind = kind;
802
803      switch(kind)
804        {
805          case svn_checksum_md5:
806            temp.digest = noderev->data_rep->md5_digest;
807            break;
808
809          case svn_checksum_sha1:
810            if (! noderev->data_rep->has_sha1)
811              return SVN_NO_ERROR;
812
813            temp.digest = noderev->data_rep->sha1_digest;
814            break;
815
816          default:
817            return SVN_NO_ERROR;
818        }
819
820      *checksum = svn_checksum_dup(&temp, result_pool);
821    }
822
823  return SVN_NO_ERROR;
824}
825
826svn_fs_x__representation_t *
827svn_fs_x__rep_copy(svn_fs_x__representation_t *rep,
828                   apr_pool_t *result_pool)
829{
830  if (rep == NULL)
831    return NULL;
832
833  return apr_pmemdup(result_pool, rep, sizeof(*rep));
834}
835
836
837/* Write out the zeroth revision for filesystem FS.
838   Perform temporary allocations in SCRATCH_POOL. */
839static svn_error_t *
840write_revision_zero(svn_fs_t *fs,
841                    apr_pool_t *scratch_pool)
842{
843  /* Use an explicit sub-pool to have full control over temp file lifetimes.
844   * Since we have it, use it for everything else as well. */
845  apr_pool_t *subpool = svn_pool_create(scratch_pool);
846  const char *path_revision_zero = svn_fs_x__path_rev(fs, 0, subpool);
847  apr_hash_t *proplist;
848  svn_string_t date;
849
850  apr_array_header_t *index_entries;
851  svn_fs_x__p2l_entry_t *entry;
852  svn_fs_x__revision_file_t *rev_file;
853  const char *l2p_proto_index, *p2l_proto_index;
854
855  /* Construct a skeleton r0 with no indexes. */
856  svn_string_t *noderev_str = svn_string_create("id: 2+0\n"
857                                                "node: 0+0\n"
858                                                "copy: 0+0\n"
859                                                "type: dir\n"
860                                                "count: 0\n"
861                                                "cpath: /\n"
862                                                "\n",
863                                                subpool);
864  svn_string_t *changes_str = svn_string_create("\n",
865                                                subpool);
866  svn_string_t *r0 = svn_string_createf(subpool, "%s%s",
867                                        noderev_str->data,
868                                        changes_str->data);
869
870  /* Write skeleton r0 to disk. */
871  SVN_ERR(svn_io_file_create(path_revision_zero, r0->data, subpool));
872
873  /* Construct the index P2L contents: describe the 2 items we have.
874     Be sure to create them in on-disk order. */
875  index_entries = apr_array_make(subpool, 2, sizeof(entry));
876
877  entry = apr_pcalloc(subpool, sizeof(*entry));
878  entry->offset = 0;
879  entry->size = (apr_off_t)noderev_str->len;
880  entry->type = SVN_FS_X__ITEM_TYPE_NODEREV;
881  entry->item_count = 1;
882  entry->items = apr_pcalloc(subpool, sizeof(*entry->items));
883  entry->items[0].change_set = 0;
884  entry->items[0].number = SVN_FS_X__ITEM_INDEX_ROOT_NODE;
885  APR_ARRAY_PUSH(index_entries, svn_fs_x__p2l_entry_t *) = entry;
886
887  entry = apr_pcalloc(subpool, sizeof(*entry));
888  entry->offset = (apr_off_t)noderev_str->len;
889  entry->size = (apr_off_t)changes_str->len;
890  entry->type = SVN_FS_X__ITEM_TYPE_CHANGES;
891  entry->item_count = 1;
892  entry->items = apr_pcalloc(subpool, sizeof(*entry->items));
893  entry->items[0].change_set = 0;
894  entry->items[0].number = SVN_FS_X__ITEM_INDEX_CHANGES;
895  APR_ARRAY_PUSH(index_entries, svn_fs_x__p2l_entry_t *) = entry;
896
897  /* Now re-open r0, create proto-index files from our entries and
898      rewrite the index section of r0. */
899  SVN_ERR(svn_fs_x__open_pack_or_rev_file_writable(&rev_file, fs, 0,
900                                                   subpool, subpool));
901  SVN_ERR(svn_fs_x__p2l_index_from_p2l_entries(&p2l_proto_index, fs,
902                                               rev_file, index_entries,
903                                               subpool, subpool));
904  SVN_ERR(svn_fs_x__l2p_index_from_p2l_entries(&l2p_proto_index, fs,
905                                               index_entries,
906                                               subpool, subpool));
907  SVN_ERR(svn_fs_x__add_index_data(fs, rev_file->file, l2p_proto_index,
908                                   p2l_proto_index, 0, subpool));
909  SVN_ERR(svn_fs_x__close_revision_file(rev_file));
910
911  SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
912
913  /* Set a date on revision 0. */
914  date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
915  date.len = strlen(date.data);
916  proplist = apr_hash_make(fs->pool);
917  svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
918  return svn_fs_x__set_revision_proplist(fs, 0, proplist, fs->pool);
919}
920
921svn_error_t *
922svn_fs_x__create_file_tree(svn_fs_t *fs,
923                           const char *path,
924                           int format,
925                           int shard_size,
926                           apr_pool_t *scratch_pool)
927{
928  svn_fs_x__data_t *ffd = fs->fsap_data;
929
930  fs->path = apr_pstrdup(fs->pool, path);
931  ffd->format = format;
932
933  /* Use an appropriate sharding mode if supported by the format. */
934  ffd->max_files_per_dir = shard_size;
935
936  /* Create the revision data directories. */
937  SVN_ERR(svn_io_make_dir_recursively(
938                              svn_fs_x__path_rev_shard(fs, 0, scratch_pool),
939                              scratch_pool));
940
941  /* Create the revprops directory. */
942  SVN_ERR(svn_io_make_dir_recursively(
943                         svn_fs_x__path_revprops_shard(fs, 0, scratch_pool),
944                         scratch_pool));
945
946  /* Create the transaction directory. */
947  SVN_ERR(svn_io_make_dir_recursively(
948                                  svn_fs_x__path_txns_dir(fs, scratch_pool),
949                                  scratch_pool));
950
951  /* Create the protorevs directory. */
952  SVN_ERR(svn_io_make_dir_recursively(
953                            svn_fs_x__path_txn_proto_revs(fs, scratch_pool),
954                            scratch_pool));
955
956  /* Create the 'current' file. */
957  SVN_ERR(svn_io_file_create_empty(svn_fs_x__path_current(fs, scratch_pool),
958                                   scratch_pool));
959  SVN_ERR(svn_fs_x__write_current(fs, 0, scratch_pool));
960
961  /* Create the 'uuid' file. */
962  SVN_ERR(svn_io_file_create_empty(svn_fs_x__path_lock(fs, scratch_pool),
963                                   scratch_pool));
964  SVN_ERR(svn_fs_x__set_uuid(fs, NULL, NULL, scratch_pool));
965
966  /* Create the fsfs.conf file. */
967  SVN_ERR(write_config(fs, scratch_pool));
968  SVN_ERR(read_config(ffd, fs->path, fs->pool, scratch_pool));
969
970  /* Add revision 0. */
971  SVN_ERR(write_revision_zero(fs, scratch_pool));
972
973  /* Create the min unpacked rev file. */
974  SVN_ERR(svn_io_file_create(
975                          svn_fs_x__path_min_unpacked_rev(fs, scratch_pool),
976                          "0\n", scratch_pool));
977
978  /* Create the txn-current file if the repository supports
979     the transaction sequence file. */
980  SVN_ERR(svn_io_file_create(svn_fs_x__path_txn_current(fs, scratch_pool),
981                             "0\n", scratch_pool));
982  SVN_ERR(svn_io_file_create_empty(
983                          svn_fs_x__path_txn_current_lock(fs, scratch_pool),
984                          scratch_pool));
985
986  /* Initialize the revprop caching info. */
987  SVN_ERR(svn_fs_x__reset_revprop_generation_file(fs, scratch_pool));
988
989  ffd->youngest_rev_cache = 0;
990  return SVN_NO_ERROR;
991}
992
993svn_error_t *
994svn_fs_x__create(svn_fs_t *fs,
995                 const char *path,
996                 apr_pool_t *scratch_pool)
997{
998  int format = SVN_FS_X__FORMAT_NUMBER;
999  svn_fs_x__data_t *ffd = fs->fsap_data;
1000
1001  fs->path = apr_pstrdup(fs->pool, path);
1002  /* See if compatibility with older versions was explicitly requested. */
1003  if (fs->config)
1004    {
1005      svn_version_t *compatible_version;
1006      SVN_ERR(svn_fs__compatible_version(&compatible_version, fs->config,
1007                                         scratch_pool));
1008
1009      /* select format number */
1010      switch(compatible_version->minor)
1011        {
1012          case 0:
1013          case 1:
1014          case 2:
1015          case 3:
1016          case 4:
1017          case 5:
1018          case 6:
1019          case 7:
1020          case 8: return svn_error_create(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
1021                  _("FSX is not compatible with Subversion prior to 1.9"));
1022
1023          default:format = SVN_FS_X__FORMAT_NUMBER;
1024        }
1025    }
1026
1027  /* Actual FS creation. */
1028  SVN_ERR(svn_fs_x__create_file_tree(fs, path, format,
1029                                     SVN_FS_X_DEFAULT_MAX_FILES_PER_DIR,
1030                                     scratch_pool));
1031
1032  /* This filesystem is ready.  Stamp it with a format number. */
1033  SVN_ERR(svn_fs_x__write_format(fs, FALSE, scratch_pool));
1034
1035  ffd->youngest_rev_cache = 0;
1036  return SVN_NO_ERROR;
1037}
1038
1039svn_error_t *
1040svn_fs_x__set_uuid(svn_fs_t *fs,
1041                   const char *uuid,
1042                   const char *instance_id,
1043                   apr_pool_t *scratch_pool)
1044{
1045  svn_fs_x__data_t *ffd = fs->fsap_data;
1046  const char *uuid_path = svn_fs_x__path_uuid(fs, scratch_pool);
1047  svn_stringbuf_t *contents = svn_stringbuf_create_empty(scratch_pool);
1048
1049  if (! uuid)
1050    uuid = svn_uuid_generate(scratch_pool);
1051
1052  if (! instance_id)
1053    instance_id = svn_uuid_generate(scratch_pool);
1054
1055  svn_stringbuf_appendcstr(contents, uuid);
1056  svn_stringbuf_appendcstr(contents, "\n");
1057  svn_stringbuf_appendcstr(contents, instance_id);
1058  svn_stringbuf_appendcstr(contents, "\n");
1059
1060  /* We use the permissions of the 'current' file, because the 'uuid'
1061     file does not exist during repository creation. */
1062  SVN_ERR(svn_io_write_atomic(uuid_path, contents->data, contents->len,
1063                              /* perms */
1064                              svn_fs_x__path_current(fs, scratch_pool),
1065                              scratch_pool));
1066
1067  fs->uuid = apr_pstrdup(fs->pool, uuid);
1068  ffd->instance_id = apr_pstrdup(fs->pool, instance_id);
1069
1070  return SVN_NO_ERROR;
1071}
1072
1073/** Node origin lazy cache. */
1074
1075/* If directory PATH does not exist, create it and give it the same
1076   permissions as FS_path.*/
1077svn_error_t *
1078svn_fs_x__ensure_dir_exists(const char *path,
1079                            const char *fs_path,
1080                            apr_pool_t *scratch_pool)
1081{
1082  svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, scratch_pool);
1083  if (err && APR_STATUS_IS_EEXIST(err->apr_err))
1084    {
1085      svn_error_clear(err);
1086      return SVN_NO_ERROR;
1087    }
1088  SVN_ERR(err);
1089
1090  /* We successfully created a new directory.  Dup the permissions
1091     from FS->path. */
1092  return svn_io_copy_perms(fs_path, path, scratch_pool);
1093}
1094
1095
1096/*** Revisions ***/
1097
1098svn_error_t *
1099svn_fs_x__revision_prop(svn_string_t **value_p,
1100                        svn_fs_t *fs,
1101                        svn_revnum_t rev,
1102                        const char *propname,
1103                        apr_pool_t *result_pool,
1104                        apr_pool_t *scratch_pool)
1105{
1106  apr_hash_t *table;
1107
1108  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1109  SVN_ERR(svn_fs_x__get_revision_proplist(&table, fs, rev, FALSE,
1110                                          scratch_pool, scratch_pool));
1111
1112  *value_p = svn_string_dup(svn_hash_gets(table, propname), result_pool);
1113
1114  return SVN_NO_ERROR;
1115}
1116
1117
1118/* Baton used for change_rev_prop_body below. */
1119typedef struct change_rev_prop_baton_t {
1120  svn_fs_t *fs;
1121  svn_revnum_t rev;
1122  const char *name;
1123  const svn_string_t *const *old_value_p;
1124  const svn_string_t *value;
1125} change_rev_prop_baton_t;
1126
1127/* The work-horse for svn_fs_x__change_rev_prop, called with the FS
1128   write lock.  This implements the svn_fs_x__with_write_lock()
1129   'body' callback type.  BATON is a 'change_rev_prop_baton_t *'. */
1130static svn_error_t *
1131change_rev_prop_body(void *baton,
1132                     apr_pool_t *scratch_pool)
1133{
1134  change_rev_prop_baton_t *cb = baton;
1135  apr_hash_t *table;
1136
1137  /* Read current revprop values from disk (never from cache).
1138     Even if somehow the cache got out of sync, we want to make sure that
1139     we read, update and write up-to-date data. */
1140  SVN_ERR(svn_fs_x__get_revision_proplist(&table, cb->fs, cb->rev, TRUE,
1141                                          scratch_pool, scratch_pool));
1142
1143  if (cb->old_value_p)
1144    {
1145      const svn_string_t *wanted_value = *cb->old_value_p;
1146      const svn_string_t *present_value = svn_hash_gets(table, cb->name);
1147      if ((!wanted_value != !present_value)
1148          || (wanted_value && present_value
1149              && !svn_string_compare(wanted_value, present_value)))
1150        {
1151          /* What we expected isn't what we found. */
1152          return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
1153                                   _("revprop '%s' has unexpected value in "
1154                                     "filesystem"),
1155                                   cb->name);
1156        }
1157      /* Fall through. */
1158    }
1159  svn_hash_sets(table, cb->name, cb->value);
1160
1161  return svn_fs_x__set_revision_proplist(cb->fs, cb->rev, table,
1162                                         scratch_pool);
1163}
1164
1165svn_error_t *
1166svn_fs_x__change_rev_prop(svn_fs_t *fs,
1167                          svn_revnum_t rev,
1168                          const char *name,
1169                          const svn_string_t *const *old_value_p,
1170                          const svn_string_t *value,
1171                          apr_pool_t *scratch_pool)
1172{
1173  change_rev_prop_baton_t cb;
1174
1175  SVN_ERR(svn_fs__check_fs(fs, TRUE));
1176
1177  cb.fs = fs;
1178  cb.rev = rev;
1179  cb.name = name;
1180  cb.old_value_p = old_value_p;
1181  cb.value = value;
1182
1183  return svn_fs_x__with_write_lock(fs, change_rev_prop_body, &cb,
1184                                   scratch_pool);
1185}
1186
1187
1188svn_error_t *
1189svn_fs_x__info_format(int *fs_format,
1190                      svn_version_t **supports_version,
1191                      svn_fs_t *fs,
1192                      apr_pool_t *result_pool,
1193                      apr_pool_t *scratch_pool)
1194{
1195  svn_fs_x__data_t *ffd = fs->fsap_data;
1196  *fs_format = ffd->format;
1197  *supports_version = apr_palloc(result_pool, sizeof(svn_version_t));
1198
1199  (*supports_version)->major = SVN_VER_MAJOR;
1200  (*supports_version)->minor = 9;
1201  (*supports_version)->patch = 0;
1202  (*supports_version)->tag = "";
1203
1204  switch (ffd->format)
1205    {
1206    case 1:
1207      break;
1208#ifdef SVN_DEBUG
1209# if SVN_FS_X__FORMAT_NUMBER != 1
1210#  error "Need to add a 'case' statement here"
1211# endif
1212#endif
1213    }
1214
1215  return SVN_NO_ERROR;
1216}
1217
1218svn_error_t *
1219svn_fs_x__info_config_files(apr_array_header_t **files,
1220                            svn_fs_t *fs,
1221                            apr_pool_t *result_pool,
1222                            apr_pool_t *scratch_pool)
1223{
1224  *files = apr_array_make(result_pool, 1, sizeof(const char *));
1225  APR_ARRAY_PUSH(*files, const char *) = svn_dirent_join(fs->path, PATH_CONFIG,
1226                                                         result_pool);
1227  return SVN_NO_ERROR;
1228}
1229