1251881Speter/* fs_fs.c --- filesystem operations specific to fs_fs
2251881Speter *
3251881Speter * ====================================================================
4251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
5251881Speter *    or more contributor license agreements.  See the NOTICE file
6251881Speter *    distributed with this work for additional information
7251881Speter *    regarding copyright ownership.  The ASF licenses this file
8251881Speter *    to you under the Apache License, Version 2.0 (the
9251881Speter *    "License"); you may not use this file except in compliance
10251881Speter *    with the License.  You may obtain a copy of the License at
11251881Speter *
12251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
13251881Speter *
14251881Speter *    Unless required by applicable law or agreed to in writing,
15251881Speter *    software distributed under the License is distributed on an
16251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17251881Speter *    KIND, either express or implied.  See the License for the
18251881Speter *    specific language governing permissions and limitations
19251881Speter *    under the License.
20251881Speter * ====================================================================
21251881Speter */
22251881Speter
23251881Speter#include <stdlib.h>
24251881Speter#include <stdio.h>
25251881Speter#include <string.h>
26251881Speter#include <ctype.h>
27251881Speter#include <assert.h>
28251881Speter#include <errno.h>
29251881Speter
30251881Speter#include <apr_general.h>
31251881Speter#include <apr_pools.h>
32251881Speter#include <apr_file_io.h>
33251881Speter#include <apr_uuid.h>
34251881Speter#include <apr_lib.h>
35251881Speter#include <apr_md5.h>
36251881Speter#include <apr_sha1.h>
37251881Speter#include <apr_strings.h>
38251881Speter#include <apr_thread_mutex.h>
39251881Speter
40251881Speter#include "svn_pools.h"
41251881Speter#include "svn_fs.h"
42251881Speter#include "svn_dirent_uri.h"
43251881Speter#include "svn_path.h"
44251881Speter#include "svn_hash.h"
45251881Speter#include "svn_props.h"
46251881Speter#include "svn_sorts.h"
47251881Speter#include "svn_string.h"
48251881Speter#include "svn_time.h"
49251881Speter#include "svn_mergeinfo.h"
50251881Speter#include "svn_config.h"
51251881Speter#include "svn_ctype.h"
52251881Speter#include "svn_version.h"
53251881Speter
54251881Speter#include "fs.h"
55251881Speter#include "tree.h"
56251881Speter#include "lock.h"
57251881Speter#include "key-gen.h"
58251881Speter#include "fs_fs.h"
59251881Speter#include "id.h"
60251881Speter#include "rep-cache.h"
61251881Speter#include "temp_serializer.h"
62251881Speter
63251881Speter#include "private/svn_string_private.h"
64251881Speter#include "private/svn_fs_util.h"
65251881Speter#include "private/svn_subr_private.h"
66251881Speter#include "private/svn_delta_private.h"
67251881Speter#include "../libsvn_fs/fs-loader.h"
68251881Speter
69251881Speter#include "svn_private_config.h"
70251881Speter#include "temp_serializer.h"
71251881Speter
72251881Speter/* An arbitrary maximum path length, so clients can't run us out of memory
73251881Speter * by giving us arbitrarily large paths. */
74251881Speter#define FSFS_MAX_PATH_LEN 4096
75251881Speter
76251881Speter/* The default maximum number of files per directory to store in the
77251881Speter   rev and revprops directory.  The number below is somewhat arbitrary,
78251881Speter   and can be overridden by defining the macro while compiling; the
79251881Speter   figure of 1000 is reasonable for VFAT filesystems, which are by far
80251881Speter   the worst performers in this area. */
81251881Speter#ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
82251881Speter#define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
83251881Speter#endif
84251881Speter
85251881Speter/* Begin deltification after a node history exceeded this this limit.
86251881Speter   Useful values are 4 to 64 with 16 being a good compromise between
87251881Speter   computational overhead and repository size savings.
88251881Speter   Should be a power of 2.
89251881Speter   Values < 2 will result in standard skip-delta behavior. */
90251881Speter#define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16
91251881Speter
92251881Speter/* Finding a deltification base takes operations proportional to the
93251881Speter   number of changes being skipped. To prevent exploding runtime
94251881Speter   during commits, limit the deltification range to this value.
95251881Speter   Should be a power of 2 minus one.
96251881Speter   Values < 1 disable deltification. */
97251881Speter#define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023
98251881Speter
99251881Speter/* Give writing processes 10 seconds to replace an existing revprop
100251881Speter   file with a new one. After that time, we assume that the writing
101251881Speter   process got aborted and that we have re-read revprops. */
102251881Speter#define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
103251881Speter
104251881Speter/* The following are names of atomics that will be used to communicate
105251881Speter * revprop updates across all processes on this machine. */
106251881Speter#define ATOMIC_REVPROP_GENERATION "rev-prop-generation"
107251881Speter#define ATOMIC_REVPROP_TIMEOUT    "rev-prop-timeout"
108251881Speter#define ATOMIC_REVPROP_NAMESPACE  "rev-prop-atomics"
109251881Speter
110251881Speter/* Following are defines that specify the textual elements of the
111251881Speter   native filesystem directories and revision files. */
112251881Speter
113251881Speter/* Headers used to describe node-revision in the revision file. */
114251881Speter#define HEADER_ID          "id"
115251881Speter#define HEADER_TYPE        "type"
116251881Speter#define HEADER_COUNT       "count"
117251881Speter#define HEADER_PROPS       "props"
118251881Speter#define HEADER_TEXT        "text"
119251881Speter#define HEADER_CPATH       "cpath"
120251881Speter#define HEADER_PRED        "pred"
121251881Speter#define HEADER_COPYFROM    "copyfrom"
122251881Speter#define HEADER_COPYROOT    "copyroot"
123251881Speter#define HEADER_FRESHTXNRT  "is-fresh-txn-root"
124251881Speter#define HEADER_MINFO_HERE  "minfo-here"
125251881Speter#define HEADER_MINFO_CNT   "minfo-cnt"
126251881Speter
127251881Speter/* Kinds that a change can be. */
128251881Speter#define ACTION_MODIFY      "modify"
129251881Speter#define ACTION_ADD         "add"
130251881Speter#define ACTION_DELETE      "delete"
131251881Speter#define ACTION_REPLACE     "replace"
132251881Speter#define ACTION_RESET       "reset"
133251881Speter
134251881Speter/* True and False flags. */
135251881Speter#define FLAG_TRUE          "true"
136251881Speter#define FLAG_FALSE         "false"
137251881Speter
138251881Speter/* Kinds that a node-rev can be. */
139251881Speter#define KIND_FILE          "file"
140251881Speter#define KIND_DIR           "dir"
141251881Speter
142251881Speter/* Kinds of representation. */
143251881Speter#define REP_PLAIN          "PLAIN"
144251881Speter#define REP_DELTA          "DELTA"
145251881Speter
146251881Speter/* Notes:
147251881Speter
148251881SpeterTo avoid opening and closing the rev-files all the time, it would
149251881Speterprobably be advantageous to keep each rev-file open for the
150251881Speterlifetime of the transaction object.  I'll leave that as a later
151251881Speteroptimization for now.
152251881Speter
153251881SpeterI didn't keep track of pool lifetimes at all in this code.  There
154251881Speterare likely some errors because of that.
155251881Speter
156251881Speter*/
157251881Speter
158251881Speter/* The vtable associated with an open transaction object. */
159251881Speterstatic txn_vtable_t txn_vtable = {
160251881Speter  svn_fs_fs__commit_txn,
161251881Speter  svn_fs_fs__abort_txn,
162251881Speter  svn_fs_fs__txn_prop,
163251881Speter  svn_fs_fs__txn_proplist,
164251881Speter  svn_fs_fs__change_txn_prop,
165251881Speter  svn_fs_fs__txn_root,
166251881Speter  svn_fs_fs__change_txn_props
167251881Speter};
168251881Speter
169251881Speter/* Declarations. */
170251881Speter
171251881Speterstatic svn_error_t *
172251881Speterread_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
173251881Speter                      const char *path,
174251881Speter                      apr_pool_t *pool);
175251881Speter
176251881Speterstatic svn_error_t *
177251881Speterupdate_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool);
178251881Speter
179251881Speterstatic svn_error_t *
180251881Speterget_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
181251881Speter
182251881Speterstatic svn_error_t *
183251881Speterverify_walker(representation_t *rep,
184251881Speter              void *baton,
185251881Speter              svn_fs_t *fs,
186251881Speter              apr_pool_t *scratch_pool);
187251881Speter
188251881Speter/* Pathname helper functions */
189251881Speter
190251881Speter/* Return TRUE is REV is packed in FS, FALSE otherwise. */
191251881Speterstatic svn_boolean_t
192251881Speteris_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
193251881Speter{
194251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
195251881Speter
196251881Speter  return (rev < ffd->min_unpacked_rev);
197251881Speter}
198251881Speter
199251881Speter/* Return TRUE is REV is packed in FS, FALSE otherwise. */
200251881Speterstatic svn_boolean_t
201251881Speteris_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
202251881Speter{
203251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
204251881Speter
205251881Speter  /* rev 0 will not be packed */
206251881Speter  return (rev < ffd->min_unpacked_rev)
207251881Speter      && (rev != 0)
208251881Speter      && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT);
209251881Speter}
210251881Speter
211251881Speterstatic const char *
212251881Speterpath_format(svn_fs_t *fs, apr_pool_t *pool)
213251881Speter{
214251881Speter  return svn_dirent_join(fs->path, PATH_FORMAT, pool);
215251881Speter}
216251881Speter
217251881Speterstatic APR_INLINE const char *
218251881Speterpath_uuid(svn_fs_t *fs, apr_pool_t *pool)
219251881Speter{
220251881Speter  return svn_dirent_join(fs->path, PATH_UUID, pool);
221251881Speter}
222251881Speter
223251881Speterconst char *
224251881Spetersvn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool)
225251881Speter{
226251881Speter  return svn_dirent_join(fs->path, PATH_CURRENT, pool);
227251881Speter}
228251881Speter
229251881Speterstatic APR_INLINE const char *
230251881Speterpath_txn_current(svn_fs_t *fs, apr_pool_t *pool)
231251881Speter{
232251881Speter  return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool);
233251881Speter}
234251881Speter
235251881Speterstatic APR_INLINE const char *
236251881Speterpath_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool)
237251881Speter{
238251881Speter  return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool);
239251881Speter}
240251881Speter
241251881Speterstatic APR_INLINE const char *
242251881Speterpath_lock(svn_fs_t *fs, apr_pool_t *pool)
243251881Speter{
244251881Speter  return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool);
245251881Speter}
246251881Speter
247251881Speterstatic const char *
248251881Speterpath_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
249251881Speter{
250251881Speter  return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool);
251251881Speter}
252251881Speter
253251881Speterstatic const char *
254251881Speterpath_rev_packed(svn_fs_t *fs, svn_revnum_t rev, const char *kind,
255251881Speter                apr_pool_t *pool)
256251881Speter{
257251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
258251881Speter
259251881Speter  assert(ffd->max_files_per_dir);
260251881Speter  assert(is_packed_rev(fs, rev));
261251881Speter
262251881Speter  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
263251881Speter                              apr_psprintf(pool,
264251881Speter                                           "%ld" PATH_EXT_PACKED_SHARD,
265251881Speter                                           rev / ffd->max_files_per_dir),
266251881Speter                              kind, NULL);
267251881Speter}
268251881Speter
269251881Speterstatic const char *
270251881Speterpath_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
271251881Speter{
272251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
273251881Speter
274251881Speter  assert(ffd->max_files_per_dir);
275251881Speter  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
276251881Speter                              apr_psprintf(pool, "%ld",
277251881Speter                                                 rev / ffd->max_files_per_dir),
278251881Speter                              NULL);
279251881Speter}
280251881Speter
281251881Speterstatic const char *
282251881Speterpath_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
283251881Speter{
284251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
285251881Speter
286251881Speter  assert(! is_packed_rev(fs, rev));
287251881Speter
288251881Speter  if (ffd->max_files_per_dir)
289251881Speter    {
290251881Speter      return svn_dirent_join(path_rev_shard(fs, rev, pool),
291251881Speter                             apr_psprintf(pool, "%ld", rev),
292251881Speter                             pool);
293251881Speter    }
294251881Speter
295251881Speter  return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
296251881Speter                              apr_psprintf(pool, "%ld", rev), NULL);
297251881Speter}
298251881Speter
299251881Spetersvn_error_t *
300251881Spetersvn_fs_fs__path_rev_absolute(const char **path,
301251881Speter                             svn_fs_t *fs,
302251881Speter                             svn_revnum_t rev,
303251881Speter                             apr_pool_t *pool)
304251881Speter{
305251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
306251881Speter
307251881Speter  if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT
308251881Speter      || ! is_packed_rev(fs, rev))
309251881Speter    {
310251881Speter      *path = path_rev(fs, rev, pool);
311251881Speter    }
312251881Speter  else
313251881Speter    {
314251881Speter      *path = path_rev_packed(fs, rev, PATH_PACKED, pool);
315251881Speter    }
316251881Speter
317251881Speter  return SVN_NO_ERROR;
318251881Speter}
319251881Speter
320251881Speterstatic const char *
321251881Speterpath_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
322251881Speter{
323251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
324251881Speter
325251881Speter  assert(ffd->max_files_per_dir);
326251881Speter  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
327251881Speter                              apr_psprintf(pool, "%ld",
328251881Speter                                           rev / ffd->max_files_per_dir),
329251881Speter                              NULL);
330251881Speter}
331251881Speter
332251881Speterstatic const char *
333251881Speterpath_revprops_pack_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
334251881Speter{
335251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
336251881Speter
337251881Speter  assert(ffd->max_files_per_dir);
338251881Speter  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
339251881Speter                              apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD,
340251881Speter                                           rev / ffd->max_files_per_dir),
341251881Speter                              NULL);
342251881Speter}
343251881Speter
344251881Speterstatic const char *
345251881Speterpath_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
346251881Speter{
347251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
348251881Speter
349251881Speter  if (ffd->max_files_per_dir)
350251881Speter    {
351251881Speter      return svn_dirent_join(path_revprops_shard(fs, rev, pool),
352251881Speter                             apr_psprintf(pool, "%ld", rev),
353251881Speter                             pool);
354251881Speter    }
355251881Speter
356251881Speter  return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
357251881Speter                              apr_psprintf(pool, "%ld", rev), NULL);
358251881Speter}
359251881Speter
360251881Speterstatic APR_INLINE const char *
361251881Speterpath_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
362251881Speter{
363251881Speter  SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL);
364251881Speter  return svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
365251881Speter                              apr_pstrcat(pool, txn_id, PATH_EXT_TXN,
366251881Speter                                          (char *)NULL),
367251881Speter                              NULL);
368251881Speter}
369251881Speter
370251881Speter/* Return the name of the sha1->rep mapping file in transaction TXN_ID
371251881Speter * within FS for the given SHA1 checksum.  Use POOL for allocations.
372251881Speter */
373251881Speterstatic APR_INLINE const char *
374251881Speterpath_txn_sha1(svn_fs_t *fs, const char *txn_id, svn_checksum_t *sha1,
375251881Speter              apr_pool_t *pool)
376251881Speter{
377251881Speter  return svn_dirent_join(path_txn_dir(fs, txn_id, pool),
378251881Speter                         svn_checksum_to_cstring(sha1, pool),
379251881Speter                         pool);
380251881Speter}
381251881Speter
382251881Speterstatic APR_INLINE const char *
383251881Speterpath_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
384251881Speter{
385251881Speter  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool);
386251881Speter}
387251881Speter
388251881Speterstatic APR_INLINE const char *
389251881Speterpath_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
390251881Speter{
391251881Speter  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool);
392251881Speter}
393251881Speter
394251881Speterstatic APR_INLINE const char *
395251881Speterpath_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
396251881Speter{
397251881Speter  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool);
398251881Speter}
399251881Speter
400251881Speterstatic APR_INLINE const char *
401251881Speterpath_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
402251881Speter{
403251881Speter  return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool);
404251881Speter}
405251881Speter
406251881Speter
407251881Speterstatic APR_INLINE const char *
408251881Speterpath_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
409251881Speter{
410251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
411251881Speter  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
412251881Speter    return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
413251881Speter                                apr_pstrcat(pool, txn_id, PATH_EXT_REV,
414251881Speter                                            (char *)NULL),
415251881Speter                                NULL);
416251881Speter  else
417251881Speter    return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool);
418251881Speter}
419251881Speter
420251881Speterstatic APR_INLINE const char *
421251881Speterpath_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
422251881Speter{
423251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
424251881Speter  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
425251881Speter    return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
426251881Speter                                apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK,
427251881Speter                                            (char *)NULL),
428251881Speter                                NULL);
429251881Speter  else
430251881Speter    return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK,
431251881Speter                           pool);
432251881Speter}
433251881Speter
434251881Speterstatic const char *
435251881Speterpath_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
436251881Speter{
437251881Speter  const char *txn_id = svn_fs_fs__id_txn_id(id);
438251881Speter  const char *node_id = svn_fs_fs__id_node_id(id);
439251881Speter  const char *copy_id = svn_fs_fs__id_copy_id(id);
440251881Speter  const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s",
441251881Speter                                  node_id, copy_id);
442251881Speter
443251881Speter  return svn_dirent_join(path_txn_dir(fs, txn_id, pool), name, pool);
444251881Speter}
445251881Speter
446251881Speterstatic APR_INLINE const char *
447251881Speterpath_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
448251881Speter{
449251881Speter  return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS,
450251881Speter                     (char *)NULL);
451251881Speter}
452251881Speter
453251881Speterstatic APR_INLINE const char *
454251881Speterpath_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
455251881Speter{
456251881Speter  return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool),
457251881Speter                     PATH_EXT_CHILDREN, (char *)NULL);
458251881Speter}
459251881Speter
460251881Speterstatic APR_INLINE const char *
461251881Speterpath_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool)
462251881Speter{
463251881Speter  size_t len = strlen(node_id);
464251881Speter  const char *node_id_minus_last_char =
465251881Speter    (len == 1) ? "0" : apr_pstrmemdup(pool, node_id, len - 1);
466251881Speter  return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR,
467251881Speter                              node_id_minus_last_char, NULL);
468251881Speter}
469251881Speter
470251881Speterstatic APR_INLINE const char *
471251881Speterpath_and_offset_of(apr_file_t *file, apr_pool_t *pool)
472251881Speter{
473251881Speter  const char *path;
474251881Speter  apr_off_t offset = 0;
475251881Speter
476251881Speter  if (apr_file_name_get(&path, file) != APR_SUCCESS)
477251881Speter    path = "(unknown)";
478251881Speter
479251881Speter  if (apr_file_seek(file, APR_CUR, &offset) != APR_SUCCESS)
480251881Speter    offset = -1;
481251881Speter
482251881Speter  return apr_psprintf(pool, "%s:%" APR_OFF_T_FMT, path, offset);
483251881Speter}
484251881Speter
485251881Speter
486251881Speter
487251881Speter/* Functions for working with shared transaction data. */
488251881Speter
489251881Speter/* Return the transaction object for transaction TXN_ID from the
490251881Speter   transaction list of filesystem FS (which must already be locked via the
491251881Speter   txn_list_lock mutex).  If the transaction does not exist in the list,
492251881Speter   then create a new transaction object and return it (if CREATE_NEW is
493251881Speter   true) or return NULL (otherwise). */
494251881Speterstatic fs_fs_shared_txn_data_t *
495251881Speterget_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new)
496251881Speter{
497251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
498251881Speter  fs_fs_shared_data_t *ffsd = ffd->shared;
499251881Speter  fs_fs_shared_txn_data_t *txn;
500251881Speter
501251881Speter  for (txn = ffsd->txns; txn; txn = txn->next)
502251881Speter    if (strcmp(txn->txn_id, txn_id) == 0)
503251881Speter      break;
504251881Speter
505251881Speter  if (txn || !create_new)
506251881Speter    return txn;
507251881Speter
508251881Speter  /* Use the transaction object from the (single-object) freelist,
509251881Speter     if one is available, or otherwise create a new object. */
510251881Speter  if (ffsd->free_txn)
511251881Speter    {
512251881Speter      txn = ffsd->free_txn;
513251881Speter      ffsd->free_txn = NULL;
514251881Speter    }
515251881Speter  else
516251881Speter    {
517251881Speter      apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
518251881Speter      txn = apr_palloc(subpool, sizeof(*txn));
519251881Speter      txn->pool = subpool;
520251881Speter    }
521251881Speter
522251881Speter  assert(strlen(txn_id) < sizeof(txn->txn_id));
523251881Speter  apr_cpystrn(txn->txn_id, txn_id, sizeof(txn->txn_id));
524251881Speter  txn->being_written = FALSE;
525251881Speter
526251881Speter  /* Link this transaction into the head of the list.  We will typically
527251881Speter     be dealing with only one active transaction at a time, so it makes
528251881Speter     sense for searches through the transaction list to look at the
529251881Speter     newest transactions first.  */
530251881Speter  txn->next = ffsd->txns;
531251881Speter  ffsd->txns = txn;
532251881Speter
533251881Speter  return txn;
534251881Speter}
535251881Speter
536251881Speter/* Free the transaction object for transaction TXN_ID, and remove it
537251881Speter   from the transaction list of filesystem FS (which must already be
538251881Speter   locked via the txn_list_lock mutex).  Do nothing if the transaction
539251881Speter   does not exist. */
540251881Speterstatic void
541251881Speterfree_shared_txn(svn_fs_t *fs, const char *txn_id)
542251881Speter{
543251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
544251881Speter  fs_fs_shared_data_t *ffsd = ffd->shared;
545251881Speter  fs_fs_shared_txn_data_t *txn, *prev = NULL;
546251881Speter
547251881Speter  for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
548251881Speter    if (strcmp(txn->txn_id, txn_id) == 0)
549251881Speter      break;
550251881Speter
551251881Speter  if (!txn)
552251881Speter    return;
553251881Speter
554251881Speter  if (prev)
555251881Speter    prev->next = txn->next;
556251881Speter  else
557251881Speter    ffsd->txns = txn->next;
558251881Speter
559251881Speter  /* As we typically will be dealing with one transaction after another,
560251881Speter     we will maintain a single-object free list so that we can hopefully
561251881Speter     keep reusing the same transaction object. */
562251881Speter  if (!ffsd->free_txn)
563251881Speter    ffsd->free_txn = txn;
564251881Speter  else
565251881Speter    svn_pool_destroy(txn->pool);
566251881Speter}
567251881Speter
568251881Speter
569251881Speter/* Obtain a lock on the transaction list of filesystem FS, call BODY
570251881Speter   with FS, BATON, and POOL, and then unlock the transaction list.
571251881Speter   Return what BODY returned. */
572251881Speterstatic svn_error_t *
573251881Speterwith_txnlist_lock(svn_fs_t *fs,
574251881Speter                  svn_error_t *(*body)(svn_fs_t *fs,
575251881Speter                                       const void *baton,
576251881Speter                                       apr_pool_t *pool),
577251881Speter                  const void *baton,
578251881Speter                  apr_pool_t *pool)
579251881Speter{
580251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
581251881Speter  fs_fs_shared_data_t *ffsd = ffd->shared;
582251881Speter
583251881Speter  SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
584251881Speter                       body(fs, baton, pool));
585251881Speter
586251881Speter  return SVN_NO_ERROR;
587251881Speter}
588251881Speter
589251881Speter
590251881Speter/* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
591251881Speterstatic svn_error_t *
592251881Speterget_lock_on_filesystem(const char *lock_filename,
593251881Speter                       apr_pool_t *pool)
594251881Speter{
595251881Speter  svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool);
596251881Speter
597251881Speter  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
598251881Speter    {
599251881Speter      /* No lock file?  No big deal; these are just empty files
600251881Speter         anyway.  Create it and try again. */
601251881Speter      svn_error_clear(err);
602251881Speter      err = NULL;
603251881Speter
604251881Speter      SVN_ERR(svn_io_file_create(lock_filename, "", pool));
605251881Speter      SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool));
606251881Speter    }
607251881Speter
608251881Speter  return svn_error_trace(err);
609251881Speter}
610251881Speter
611251881Speter/* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
612251881Speter   When registered with the pool holding the lock on the lock file,
613251881Speter   this makes sure the flag gets reset just before we release the lock. */
614251881Speterstatic apr_status_t
615251881Speterreset_lock_flag(void *baton_void)
616251881Speter{
617251881Speter  fs_fs_data_t *ffd = baton_void;
618251881Speter  ffd->has_write_lock = FALSE;
619251881Speter  return APR_SUCCESS;
620251881Speter}
621251881Speter
622251881Speter/* Obtain a write lock on the file LOCK_FILENAME (protecting with
623251881Speter   LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
624251881Speter   BATON and that subpool, destroy the subpool (releasing the write
625251881Speter   lock) and return what BODY returned.  If IS_GLOBAL_LOCK is set,
626251881Speter   set the HAS_WRITE_LOCK flag while we keep the write lock. */
627251881Speterstatic svn_error_t *
628251881Speterwith_some_lock_file(svn_fs_t *fs,
629251881Speter                    svn_error_t *(*body)(void *baton,
630251881Speter                                         apr_pool_t *pool),
631251881Speter                    void *baton,
632251881Speter                    const char *lock_filename,
633251881Speter                    svn_boolean_t is_global_lock,
634251881Speter                    apr_pool_t *pool)
635251881Speter{
636251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
637251881Speter  svn_error_t *err = get_lock_on_filesystem(lock_filename, subpool);
638251881Speter
639251881Speter  if (!err)
640251881Speter    {
641251881Speter      fs_fs_data_t *ffd = fs->fsap_data;
642251881Speter
643251881Speter      if (is_global_lock)
644251881Speter        {
645251881Speter          /* set the "got the lock" flag and register reset function */
646251881Speter          apr_pool_cleanup_register(subpool,
647251881Speter                                    ffd,
648251881Speter                                    reset_lock_flag,
649251881Speter                                    apr_pool_cleanup_null);
650251881Speter          ffd->has_write_lock = TRUE;
651251881Speter        }
652251881Speter
653251881Speter      /* nobody else will modify the repo state
654251881Speter         => read HEAD & pack info once */
655251881Speter      if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
656251881Speter        SVN_ERR(update_min_unpacked_rev(fs, pool));
657251881Speter      SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path,
658251881Speter                           pool));
659251881Speter      err = body(baton, subpool);
660251881Speter    }
661251881Speter
662251881Speter  svn_pool_destroy(subpool);
663251881Speter
664251881Speter  return svn_error_trace(err);
665251881Speter}
666251881Speter
667251881Spetersvn_error_t *
668251881Spetersvn_fs_fs__with_write_lock(svn_fs_t *fs,
669251881Speter                           svn_error_t *(*body)(void *baton,
670251881Speter                                                apr_pool_t *pool),
671251881Speter                           void *baton,
672251881Speter                           apr_pool_t *pool)
673251881Speter{
674251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
675251881Speter  fs_fs_shared_data_t *ffsd = ffd->shared;
676251881Speter
677251881Speter  SVN_MUTEX__WITH_LOCK(ffsd->fs_write_lock,
678251881Speter                       with_some_lock_file(fs, body, baton,
679251881Speter                                           path_lock(fs, pool),
680251881Speter                                           TRUE,
681251881Speter                                           pool));
682251881Speter
683251881Speter  return SVN_NO_ERROR;
684251881Speter}
685251881Speter
686251881Speter/* Run BODY (with BATON and POOL) while the txn-current file
687251881Speter   of FS is locked. */
688251881Speterstatic svn_error_t *
689251881Speterwith_txn_current_lock(svn_fs_t *fs,
690251881Speter                      svn_error_t *(*body)(void *baton,
691251881Speter                                           apr_pool_t *pool),
692251881Speter                      void *baton,
693251881Speter                      apr_pool_t *pool)
694251881Speter{
695251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
696251881Speter  fs_fs_shared_data_t *ffsd = ffd->shared;
697251881Speter
698251881Speter  SVN_MUTEX__WITH_LOCK(ffsd->txn_current_lock,
699251881Speter                       with_some_lock_file(fs, body, baton,
700251881Speter                                           path_txn_current_lock(fs, pool),
701251881Speter                                           FALSE,
702251881Speter                                           pool));
703251881Speter
704251881Speter  return SVN_NO_ERROR;
705251881Speter}
706251881Speter
707251881Speter/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
708251881Speter   which see. */
709251881Speterstruct unlock_proto_rev_baton
710251881Speter{
711251881Speter  const char *txn_id;
712251881Speter  void *lockcookie;
713251881Speter};
714251881Speter
715251881Speter/* Callback used in the implementation of unlock_proto_rev(). */
716251881Speterstatic svn_error_t *
717251881Speterunlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
718251881Speter{
719251881Speter  const struct unlock_proto_rev_baton *b = baton;
720251881Speter  const char *txn_id = b->txn_id;
721251881Speter  apr_file_t *lockfile = b->lockcookie;
722251881Speter  fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE);
723251881Speter  apr_status_t apr_err;
724251881Speter
725251881Speter  if (!txn)
726251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
727251881Speter                             _("Can't unlock unknown transaction '%s'"),
728251881Speter                             txn_id);
729251881Speter  if (!txn->being_written)
730251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
731251881Speter                             _("Can't unlock nonlocked transaction '%s'"),
732251881Speter                             txn_id);
733251881Speter
734251881Speter  apr_err = apr_file_unlock(lockfile);
735251881Speter  if (apr_err)
736251881Speter    return svn_error_wrap_apr
737251881Speter      (apr_err,
738251881Speter       _("Can't unlock prototype revision lockfile for transaction '%s'"),
739251881Speter       txn_id);
740251881Speter  apr_err = apr_file_close(lockfile);
741251881Speter  if (apr_err)
742251881Speter    return svn_error_wrap_apr
743251881Speter      (apr_err,
744251881Speter       _("Can't close prototype revision lockfile for transaction '%s'"),
745251881Speter       txn_id);
746251881Speter
747251881Speter  txn->being_written = FALSE;
748251881Speter
749251881Speter  return SVN_NO_ERROR;
750251881Speter}
751251881Speter
752251881Speter/* Unlock the prototype revision file for transaction TXN_ID in filesystem
753251881Speter   FS using cookie LOCKCOOKIE.  The original prototype revision file must
754251881Speter   have been closed _before_ calling this function.
755251881Speter
756251881Speter   Perform temporary allocations in POOL. */
757251881Speterstatic svn_error_t *
758251881Speterunlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie,
759251881Speter                 apr_pool_t *pool)
760251881Speter{
761251881Speter  struct unlock_proto_rev_baton b;
762251881Speter
763251881Speter  b.txn_id = txn_id;
764251881Speter  b.lockcookie = lockcookie;
765251881Speter  return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
766251881Speter}
767251881Speter
768251881Speter/* Same as unlock_proto_rev(), but requires that the transaction list
769251881Speter   lock is already held. */
770251881Speterstatic svn_error_t *
771251881Speterunlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id,
772251881Speter                             void *lockcookie,
773251881Speter                             apr_pool_t *pool)
774251881Speter{
775251881Speter  struct unlock_proto_rev_baton b;
776251881Speter
777251881Speter  b.txn_id = txn_id;
778251881Speter  b.lockcookie = lockcookie;
779251881Speter  return unlock_proto_rev_body(fs, &b, pool);
780251881Speter}
781251881Speter
782251881Speter/* A structure used by get_writable_proto_rev() and
783251881Speter   get_writable_proto_rev_body(), which see. */
784251881Speterstruct get_writable_proto_rev_baton
785251881Speter{
786251881Speter  apr_file_t **file;
787251881Speter  void **lockcookie;
788251881Speter  const char *txn_id;
789251881Speter};
790251881Speter
791251881Speter/* Callback used in the implementation of get_writable_proto_rev(). */
792251881Speterstatic svn_error_t *
793251881Speterget_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
794251881Speter{
795251881Speter  const struct get_writable_proto_rev_baton *b = baton;
796251881Speter  apr_file_t **file = b->file;
797251881Speter  void **lockcookie = b->lockcookie;
798251881Speter  const char *txn_id = b->txn_id;
799251881Speter  svn_error_t *err;
800251881Speter  fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE);
801251881Speter
802251881Speter  /* First, ensure that no thread in this process (including this one)
803251881Speter     is currently writing to this transaction's proto-rev file. */
804251881Speter  if (txn->being_written)
805251881Speter    return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
806251881Speter                             _("Cannot write to the prototype revision file "
807251881Speter                               "of transaction '%s' because a previous "
808251881Speter                               "representation is currently being written by "
809251881Speter                               "this process"),
810251881Speter                             txn_id);
811251881Speter
812251881Speter
813251881Speter  /* We know that no thread in this process is writing to the proto-rev
814251881Speter     file, and by extension, that no thread in this process is holding a
815251881Speter     lock on the prototype revision lock file.  It is therefore safe
816251881Speter     for us to attempt to lock this file, to see if any other process
817251881Speter     is holding a lock. */
818251881Speter
819251881Speter  {
820251881Speter    apr_file_t *lockfile;
821251881Speter    apr_status_t apr_err;
822251881Speter    const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool);
823251881Speter
824251881Speter    /* Open the proto-rev lockfile, creating it if necessary, as it may
825251881Speter       not exist if the transaction dates from before the lockfiles were
826251881Speter       introduced.
827251881Speter
828251881Speter       ### We'd also like to use something like svn_io_file_lock2(), but
829251881Speter           that forces us to create a subpool just to be able to unlock
830251881Speter           the file, which seems a waste. */
831251881Speter    SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
832251881Speter                             APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
833251881Speter
834251881Speter    apr_err = apr_file_lock(lockfile,
835251881Speter                            APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
836251881Speter    if (apr_err)
837251881Speter      {
838251881Speter        svn_error_clear(svn_io_file_close(lockfile, pool));
839251881Speter
840251881Speter        if (APR_STATUS_IS_EAGAIN(apr_err))
841251881Speter          return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
842251881Speter                                   _("Cannot write to the prototype revision "
843251881Speter                                     "file of transaction '%s' because a "
844251881Speter                                     "previous representation is currently "
845251881Speter                                     "being written by another process"),
846251881Speter                                   txn_id);
847251881Speter
848251881Speter        return svn_error_wrap_apr(apr_err,
849251881Speter                                  _("Can't get exclusive lock on file '%s'"),
850251881Speter                                  svn_dirent_local_style(lockfile_path, pool));
851251881Speter      }
852251881Speter
853251881Speter    *lockcookie = lockfile;
854251881Speter  }
855251881Speter
856251881Speter  /* We've successfully locked the transaction; mark it as such. */
857251881Speter  txn->being_written = TRUE;
858251881Speter
859251881Speter
860251881Speter  /* Now open the prototype revision file and seek to the end. */
861251881Speter  err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool),
862251881Speter                         APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
863251881Speter
864251881Speter  /* You might expect that we could dispense with the following seek
865251881Speter     and achieve the same thing by opening the file using APR_APPEND.
866251881Speter     Unfortunately, APR's buffered file implementation unconditionally
867251881Speter     places its initial file pointer at the start of the file (even for
868251881Speter     files opened with APR_APPEND), so we need this seek to reconcile
869251881Speter     the APR file pointer to the OS file pointer (since we need to be
870251881Speter     able to read the current file position later). */
871251881Speter  if (!err)
872251881Speter    {
873251881Speter      apr_off_t offset = 0;
874251881Speter      err = svn_io_file_seek(*file, APR_END, &offset, pool);
875251881Speter    }
876251881Speter
877251881Speter  if (err)
878251881Speter    {
879251881Speter      err = svn_error_compose_create(
880251881Speter              err,
881251881Speter              unlock_proto_rev_list_locked(fs, txn_id, *lockcookie, pool));
882251881Speter
883251881Speter      *lockcookie = NULL;
884251881Speter    }
885251881Speter
886251881Speter  return svn_error_trace(err);
887251881Speter}
888251881Speter
889251881Speter/* Get a handle to the prototype revision file for transaction TXN_ID in
890251881Speter   filesystem FS, and lock it for writing.  Return FILE, a file handle
891251881Speter   positioned at the end of the file, and LOCKCOOKIE, a cookie that
892251881Speter   should be passed to unlock_proto_rev() to unlock the file once FILE
893251881Speter   has been closed.
894251881Speter
895251881Speter   If the prototype revision file is already locked, return error
896251881Speter   SVN_ERR_FS_REP_BEING_WRITTEN.
897251881Speter
898251881Speter   Perform all allocations in POOL. */
899251881Speterstatic svn_error_t *
900251881Speterget_writable_proto_rev(apr_file_t **file,
901251881Speter                       void **lockcookie,
902251881Speter                       svn_fs_t *fs, const char *txn_id,
903251881Speter                       apr_pool_t *pool)
904251881Speter{
905251881Speter  struct get_writable_proto_rev_baton b;
906251881Speter
907251881Speter  b.file = file;
908251881Speter  b.lockcookie = lockcookie;
909251881Speter  b.txn_id = txn_id;
910251881Speter
911251881Speter  return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool);
912251881Speter}
913251881Speter
914251881Speter/* Callback used in the implementation of purge_shared_txn(). */
915251881Speterstatic svn_error_t *
916251881Speterpurge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
917251881Speter{
918251881Speter  const char *txn_id = baton;
919251881Speter
920251881Speter  free_shared_txn(fs, txn_id);
921251881Speter  svn_fs_fs__reset_txn_caches(fs);
922251881Speter
923251881Speter  return SVN_NO_ERROR;
924251881Speter}
925251881Speter
926251881Speter/* Purge the shared data for transaction TXN_ID in filesystem FS.
927251881Speter   Perform all allocations in POOL. */
928251881Speterstatic svn_error_t *
929251881Speterpurge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
930251881Speter{
931251881Speter  return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
932251881Speter}
933251881Speter
934251881Speter
935251881Speter
936251881Speter/* Fetch the current offset of FILE into *OFFSET_P. */
937251881Speterstatic svn_error_t *
938251881Speterget_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool)
939251881Speter{
940251881Speter  apr_off_t offset;
941251881Speter
942251881Speter  /* Note that, for buffered files, one (possibly surprising) side-effect
943251881Speter     of this call is to flush any unwritten data to disk. */
944251881Speter  offset = 0;
945251881Speter  SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
946251881Speter  *offset_p = offset;
947251881Speter
948251881Speter  return SVN_NO_ERROR;
949251881Speter}
950251881Speter
951251881Speter
952251881Speter/* Check that BUF, a nul-terminated buffer of text from file PATH,
953251881Speter   contains only digits at OFFSET and beyond, raising an error if not.
954251881Speter   TITLE contains a user-visible description of the file, usually the
955251881Speter   short file name.
956251881Speter
957251881Speter   Uses POOL for temporary allocation. */
958251881Speterstatic svn_error_t *
959251881Spetercheck_file_buffer_numeric(const char *buf, apr_off_t offset,
960251881Speter                          const char *path, const char *title,
961251881Speter                          apr_pool_t *pool)
962251881Speter{
963251881Speter  const char *p;
964251881Speter
965251881Speter  for (p = buf + offset; *p; p++)
966251881Speter    if (!svn_ctype_isdigit(*p))
967251881Speter      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
968251881Speter        _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
969251881Speter        title, svn_dirent_local_style(path, pool), *p, buf);
970251881Speter
971251881Speter  return SVN_NO_ERROR;
972251881Speter}
973251881Speter
974251881Speter/* Check that BUF, a nul-terminated buffer of text from format file PATH,
975251881Speter   contains only digits at OFFSET and beyond, raising an error if not.
976251881Speter
977251881Speter   Uses POOL for temporary allocation. */
978251881Speterstatic svn_error_t *
979251881Spetercheck_format_file_buffer_numeric(const char *buf, apr_off_t offset,
980251881Speter                                 const char *path, apr_pool_t *pool)
981251881Speter{
982251881Speter  return check_file_buffer_numeric(buf, offset, path, "Format", pool);
983251881Speter}
984251881Speter
985262253Speter/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
986262253Speter   number is not the same as a format number supported by this
987262253Speter   Subversion. */
988262253Speterstatic svn_error_t *
989262253Spetercheck_format(int format)
990262253Speter{
991262253Speter  /* Blacklist.  These formats may be either younger or older than
992262253Speter     SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */
993262253Speter  if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT)
994262253Speter    return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
995262253Speter                             _("Found format '%d', only created by "
996262253Speter                               "unreleased dev builds; see "
997262253Speter                               "http://subversion.apache.org"
998262253Speter                               "/docs/release-notes/1.7#revprop-packing"),
999262253Speter                             format);
1000262253Speter
1001262253Speter  /* We support all formats from 1-current simultaneously */
1002262253Speter  if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
1003262253Speter    return SVN_NO_ERROR;
1004262253Speter
1005262253Speter  return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
1006262253Speter     _("Expected FS format between '1' and '%d'; found format '%d'"),
1007262253Speter     SVN_FS_FS__FORMAT_NUMBER, format);
1008262253Speter}
1009262253Speter
1010251881Speter/* Read the format number and maximum number of files per directory
1011251881Speter   from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
1012251881Speter   respectively.
1013251881Speter
1014251881Speter   *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
1015251881Speter   will be set to zero if a linear scheme should be used.
1016251881Speter
1017251881Speter   Use POOL for temporary allocation. */
1018251881Speterstatic svn_error_t *
1019251881Speterread_format(int *pformat, int *max_files_per_dir,
1020251881Speter            const char *path, apr_pool_t *pool)
1021251881Speter{
1022251881Speter  svn_error_t *err;
1023251881Speter  svn_stream_t *stream;
1024251881Speter  svn_stringbuf_t *content;
1025251881Speter  svn_stringbuf_t *buf;
1026251881Speter  svn_boolean_t eos = FALSE;
1027251881Speter
1028251881Speter  err = svn_stringbuf_from_file2(&content, path, pool);
1029251881Speter  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1030251881Speter    {
1031251881Speter      /* Treat an absent format file as format 1.  Do not try to
1032251881Speter         create the format file on the fly, because the repository
1033251881Speter         might be read-only for us, or this might be a read-only
1034251881Speter         operation, and the spirit of FSFS is to make no changes
1035251881Speter         whatseover in read-only operations.  See thread starting at
1036251881Speter         http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
1037251881Speter         for more. */
1038251881Speter      svn_error_clear(err);
1039251881Speter      *pformat = 1;
1040251881Speter      *max_files_per_dir = 0;
1041251881Speter
1042251881Speter      return SVN_NO_ERROR;
1043251881Speter    }
1044251881Speter  SVN_ERR(err);
1045251881Speter
1046251881Speter  stream = svn_stream_from_stringbuf(content, pool);
1047251881Speter  SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
1048251881Speter  if (buf->len == 0 && eos)
1049251881Speter    {
1050251881Speter      /* Return a more useful error message. */
1051251881Speter      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
1052251881Speter                               _("Can't read first line of format file '%s'"),
1053251881Speter                               svn_dirent_local_style(path, pool));
1054251881Speter    }
1055251881Speter
1056251881Speter  /* Check that the first line contains only digits. */
1057251881Speter  SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool));
1058251881Speter  SVN_ERR(svn_cstring_atoi(pformat, buf->data));
1059251881Speter
1060262253Speter  /* Check that we support this format at all */
1061262253Speter  SVN_ERR(check_format(*pformat));
1062262253Speter
1063251881Speter  /* Set the default values for anything that can be set via an option. */
1064251881Speter  *max_files_per_dir = 0;
1065251881Speter
1066251881Speter  /* Read any options. */
1067251881Speter  while (!eos)
1068251881Speter    {
1069251881Speter      SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
1070251881Speter      if (buf->len == 0)
1071251881Speter        break;
1072251881Speter
1073251881Speter      if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT &&
1074251881Speter          strncmp(buf->data, "layout ", 7) == 0)
1075251881Speter        {
1076251881Speter          if (strcmp(buf->data + 7, "linear") == 0)
1077251881Speter            {
1078251881Speter              *max_files_per_dir = 0;
1079251881Speter              continue;
1080251881Speter            }
1081251881Speter
1082251881Speter          if (strncmp(buf->data + 7, "sharded ", 8) == 0)
1083251881Speter            {
1084251881Speter              /* Check that the argument is numeric. */
1085251881Speter              SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path, pool));
1086251881Speter              SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15));
1087251881Speter              continue;
1088251881Speter            }
1089251881Speter        }
1090251881Speter
1091251881Speter      return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
1092251881Speter         _("'%s' contains invalid filesystem format option '%s'"),
1093251881Speter         svn_dirent_local_style(path, pool), buf->data);
1094251881Speter    }
1095251881Speter
1096251881Speter  return SVN_NO_ERROR;
1097251881Speter}
1098251881Speter
1099251881Speter/* Write the format number and maximum number of files per directory
1100251881Speter   to a new format file in PATH, possibly expecting to overwrite a
1101251881Speter   previously existing file.
1102251881Speter
1103251881Speter   Use POOL for temporary allocation. */
1104251881Speterstatic svn_error_t *
1105251881Speterwrite_format(const char *path, int format, int max_files_per_dir,
1106251881Speter             svn_boolean_t overwrite, apr_pool_t *pool)
1107251881Speter{
1108251881Speter  svn_stringbuf_t *sb;
1109251881Speter
1110251881Speter  SVN_ERR_ASSERT(1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER);
1111251881Speter
1112251881Speter  sb = svn_stringbuf_createf(pool, "%d\n", format);
1113251881Speter
1114251881Speter  if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
1115251881Speter    {
1116251881Speter      if (max_files_per_dir)
1117251881Speter        svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n",
1118251881Speter                                                  max_files_per_dir));
1119251881Speter      else
1120251881Speter        svn_stringbuf_appendcstr(sb, "layout linear\n");
1121251881Speter    }
1122251881Speter
1123251881Speter  /* svn_io_write_version_file() does a load of magic to allow it to
1124251881Speter     replace version files that already exist.  We only need to do
1125251881Speter     that when we're allowed to overwrite an existing file. */
1126251881Speter  if (! overwrite)
1127251881Speter    {
1128251881Speter      /* Create the file */
1129251881Speter      SVN_ERR(svn_io_file_create(path, sb->data, pool));
1130251881Speter    }
1131251881Speter  else
1132251881Speter    {
1133251881Speter      const char *path_tmp;
1134251881Speter
1135251881Speter      SVN_ERR(svn_io_write_unique(&path_tmp,
1136251881Speter                                  svn_dirent_dirname(path, pool),
1137251881Speter                                  sb->data, sb->len,
1138251881Speter                                  svn_io_file_del_none, pool));
1139251881Speter
1140251881Speter      /* rename the temp file as the real destination */
1141251881Speter      SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
1142251881Speter    }
1143251881Speter
1144251881Speter  /* And set the perms to make it read only */
1145251881Speter  return svn_io_set_file_read_only(path, FALSE, pool);
1146251881Speter}
1147251881Speter
1148251881Spetersvn_boolean_t
1149251881Spetersvn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
1150251881Speter{
1151251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1152251881Speter  return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
1153251881Speter}
1154251881Speter
1155251881Speter/* Read the configuration information of the file system at FS_PATH
1156251881Speter * and set the respective values in FFD.  Use POOL for allocations.
1157251881Speter */
1158251881Speterstatic svn_error_t *
1159251881Speterread_config(fs_fs_data_t *ffd,
1160251881Speter            const char *fs_path,
1161251881Speter            apr_pool_t *pool)
1162251881Speter{
1163251881Speter  SVN_ERR(svn_config_read3(&ffd->config,
1164251881Speter                           svn_dirent_join(fs_path, PATH_CONFIG, pool),
1165251881Speter                           FALSE, FALSE, FALSE, pool));
1166251881Speter
1167251881Speter  /* Initialize ffd->rep_sharing_allowed. */
1168251881Speter  if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
1169251881Speter    SVN_ERR(svn_config_get_bool(ffd->config, &ffd->rep_sharing_allowed,
1170251881Speter                                CONFIG_SECTION_REP_SHARING,
1171251881Speter                                CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
1172251881Speter  else
1173251881Speter    ffd->rep_sharing_allowed = FALSE;
1174251881Speter
1175251881Speter  /* Initialize deltification settings in ffd. */
1176251881Speter  if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
1177251881Speter    {
1178251881Speter      SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_directories,
1179251881Speter                                  CONFIG_SECTION_DELTIFICATION,
1180251881Speter                                  CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
1181251881Speter                                  FALSE));
1182251881Speter      SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_properties,
1183251881Speter                                  CONFIG_SECTION_DELTIFICATION,
1184251881Speter                                  CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION,
1185251881Speter                                  FALSE));
1186251881Speter      SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_deltification_walk,
1187251881Speter                                   CONFIG_SECTION_DELTIFICATION,
1188251881Speter                                   CONFIG_OPTION_MAX_DELTIFICATION_WALK,
1189251881Speter                                   SVN_FS_FS_MAX_DELTIFICATION_WALK));
1190251881Speter      SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_linear_deltification,
1191251881Speter                                   CONFIG_SECTION_DELTIFICATION,
1192251881Speter                                   CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
1193251881Speter                                   SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
1194251881Speter    }
1195251881Speter  else
1196251881Speter    {
1197251881Speter      ffd->deltify_directories = FALSE;
1198251881Speter      ffd->deltify_properties = FALSE;
1199251881Speter      ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
1200251881Speter      ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
1201251881Speter    }
1202251881Speter
1203251881Speter  /* Initialize revprop packing settings in ffd. */
1204251881Speter  if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
1205251881Speter    {
1206251881Speter      SVN_ERR(svn_config_get_bool(ffd->config, &ffd->compress_packed_revprops,
1207251881Speter                                  CONFIG_SECTION_PACKED_REVPROPS,
1208251881Speter                                  CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
1209251881Speter                                  FALSE));
1210251881Speter      SVN_ERR(svn_config_get_int64(ffd->config, &ffd->revprop_pack_size,
1211251881Speter                                   CONFIG_SECTION_PACKED_REVPROPS,
1212251881Speter                                   CONFIG_OPTION_REVPROP_PACK_SIZE,
1213251881Speter                                   ffd->compress_packed_revprops
1214251881Speter                                       ? 0x100
1215251881Speter                                       : 0x40));
1216251881Speter
1217251881Speter      ffd->revprop_pack_size *= 1024;
1218251881Speter    }
1219251881Speter  else
1220251881Speter    {
1221251881Speter      ffd->revprop_pack_size = 0x10000;
1222251881Speter      ffd->compress_packed_revprops = FALSE;
1223251881Speter    }
1224251881Speter
1225251881Speter  return SVN_NO_ERROR;
1226251881Speter}
1227251881Speter
1228251881Speterstatic svn_error_t *
1229251881Speterwrite_config(svn_fs_t *fs,
1230251881Speter             apr_pool_t *pool)
1231251881Speter{
1232251881Speter#define NL APR_EOL_STR
1233251881Speter  static const char * const fsfs_conf_contents =
1234251881Speter"### This file controls the configuration of the FSFS filesystem."           NL
1235251881Speter""                                                                           NL
1236251881Speter"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]"                          NL
1237251881Speter"### These options name memcached servers used to cache internal FSFS"       NL
1238251881Speter"### data.  See http://www.danga.com/memcached/ for more information on"     NL
1239251881Speter"### memcached.  To use memcached with FSFS, run one or more memcached"      NL
1240251881Speter"### servers, and specify each of them as an option like so:"                NL
1241251881Speter"# first-server = 127.0.0.1:11211"                                           NL
1242251881Speter"# remote-memcached = mymemcached.corp.example.com:11212"                    NL
1243251881Speter"### The option name is ignored; the value is of the form HOST:PORT."        NL
1244251881Speter"### memcached servers can be shared between multiple repositories;"         NL
1245251881Speter"### however, if you do this, you *must* ensure that repositories have"      NL
1246251881Speter"### distinct UUIDs and paths, or else cached data from one repository"      NL
1247251881Speter"### might be used by another accidentally.  Note also that memcached has"   NL
1248251881Speter"### no authentication for reads or writes, so you must ensure that your"    NL
1249251881Speter"### memcached servers are only accessible by trusted users."                NL
1250251881Speter""                                                                           NL
1251251881Speter"[" CONFIG_SECTION_CACHES "]"                                                NL
1252251881Speter"### When a cache-related error occurs, normally Subversion ignores it"      NL
1253251881Speter"### and continues, logging an error if the server is appropriately"         NL
1254251881Speter"### configured (and ignoring it with file:// access).  To make"             NL
1255251881Speter"### Subversion never ignore cache errors, uncomment this line."             NL
1256251881Speter"# " CONFIG_OPTION_FAIL_STOP " = true"                                       NL
1257251881Speter""                                                                           NL
1258251881Speter"[" CONFIG_SECTION_REP_SHARING "]"                                           NL
1259251881Speter"### To conserve space, the filesystem can optionally avoid storing"         NL
1260251881Speter"### duplicate representations.  This comes at a slight cost in"             NL
1261251881Speter"### performance, as maintaining a database of shared representations can"   NL
1262251881Speter"### increase commit times.  The space savings are dependent upon the size"  NL
1263251881Speter"### of the repository, the number of objects it contains and the amount of" NL
1264251881Speter"### duplication between them, usually a function of the branching and"      NL
1265251881Speter"### merging process."                                                       NL
1266251881Speter"###"                                                                        NL
1267251881Speter"### The following parameter enables rep-sharing in the repository.  It can" NL
1268251881Speter"### be switched on and off at will, but for best space-saving results"      NL
1269251881Speter"### should be enabled consistently over the life of the repository."        NL
1270251881Speter"### 'svnadmin verify' will check the rep-cache regardless of this setting." NL
1271251881Speter"### rep-sharing is enabled by default."                                     NL
1272251881Speter"# " CONFIG_OPTION_ENABLE_REP_SHARING " = true"                              NL
1273251881Speter""                                                                           NL
1274251881Speter"[" CONFIG_SECTION_DELTIFICATION "]"                                         NL
1275251881Speter"### To conserve space, the filesystem stores data as differences against"   NL
1276251881Speter"### existing representations.  This comes at a slight cost in performance," NL
1277251881Speter"### as calculating differences can increase commit times.  Reading data"    NL
1278251881Speter"### will also create higher CPU load and the data will be fragmented."      NL
1279251881Speter"### Since deltification tends to save significant amounts of disk space,"   NL
1280251881Speter"### the overall I/O load can actually be lower."                            NL
1281251881Speter"###"                                                                        NL
1282251881Speter"### The options in this section allow for tuning the deltification"         NL
1283251881Speter"### strategy.  Their effects on data size and server performance may vary"  NL
1284251881Speter"### from one repository to another.  Versions prior to 1.8 will ignore"     NL
1285251881Speter"### this section."                                                          NL
1286251881Speter"###"                                                                        NL
1287251881Speter"### The following parameter enables deltification for directories. It can"  NL
1288251881Speter"### be switched on and off at will, but for best space-saving results"      NL
1289251881Speter"### should be enabled consistently over the life of the repository."        NL
1290251881Speter"### Repositories containing large directories will benefit greatly."        NL
1291251881Speter"### In rarely read repositories, the I/O overhead may be significant as"    NL
1292251881Speter"### cache hit rates will most likely be low"                                NL
1293251881Speter"### directory deltification is disabled by default."                        NL
1294251881Speter"# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = false"                       NL
1295251881Speter"###"                                                                        NL
1296251881Speter"### The following parameter enables deltification for properties on files"  NL
1297251881Speter"### and directories.  Overall, this is a minor tuning option but can save"  NL
1298251881Speter"### some disk space if you merge frequently or frequently change node"      NL
1299251881Speter"### properties.  You should not activate this if rep-sharing has been"      NL
1300251881Speter"### disabled because this may result in a net increase in repository size." NL
1301251881Speter"### property deltification is disabled by default."                         NL
1302251881Speter"# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = false"                     NL
1303251881Speter"###"                                                                        NL
1304251881Speter"### During commit, the server may need to walk the whole change history of" NL
1305251881Speter"### of a given node to find a suitable deltification base.  This linear"    NL
1306251881Speter"### process can impact commit times, svnadmin load and similar operations." NL
1307251881Speter"### This setting limits the depth of the deltification history.  If the"    NL
1308251881Speter"### threshold has been reached, the node will be stored as fulltext and a"  NL
1309251881Speter"### new deltification history begins."                                      NL
1310251881Speter"### Note, this is unrelated to svn log."                                    NL
1311251881Speter"### Very large values rarely provide significant additional savings but"    NL
1312251881Speter"### can impact performance greatly - in particular if directory"            NL
1313251881Speter"### deltification has been activated.  Very small values may be useful in"  NL
1314251881Speter"### repositories that are dominated by large, changing binaries."           NL
1315251881Speter"### Should be a power of two minus 1.  A value of 0 will effectively"       NL
1316251881Speter"### disable deltification."                                                 NL
1317251881Speter"### For 1.8, the default value is 1023; earlier versions have no limit."    NL
1318251881Speter"# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023"                          NL
1319251881Speter"###"                                                                        NL
1320251881Speter"### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL
1321251881Speter"### delta information where a simple delta against the latest version is"   NL
1322251881Speter"### often smaller.  By default, 1.8+ will therefore use skip deltas only"   NL
1323251881Speter"### after the linear chain of deltas has grown beyond the threshold"        NL
1324251881Speter"### specified by this setting."                                             NL
1325251881Speter"### Values up to 64 can result in some reduction in repository size for"    NL
1326251881Speter"### the cost of quickly increasing I/O and CPU costs. Similarly, smaller"   NL
1327251881Speter"### numbers can reduce those costs at the cost of more disk space.  For"    NL
1328251881Speter"### rarely read repositories or those containing larger binaries, this may" NL
1329251881Speter"### present a better trade-off."                                            NL
1330251881Speter"### Should be a power of two.  A value of 1 or smaller will cause the"      NL
1331251881Speter"### exclusive use of skip-deltas (as in pre-1.8)."                          NL
1332251881Speter"### For 1.8, the default value is 16; earlier versions use 1."              NL
1333251881Speter"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16"                          NL
1334251881Speter""                                                                           NL
1335251881Speter"[" CONFIG_SECTION_PACKED_REVPROPS "]"                                       NL
1336251881Speter"### This parameter controls the size (in kBytes) of packed revprop files."  NL
1337251881Speter"### Revprops of consecutive revisions will be concatenated into a single"   NL
1338251881Speter"### file up to but not exceeding the threshold given here.  However, each"  NL
1339251881Speter"### pack file may be much smaller and revprops of a single revision may be" NL
1340251881Speter"### much larger than the limit set here.  The threshold will be applied"    NL
1341251881Speter"### before optional compression takes place."                               NL
1342251881Speter"### Large values will reduce disk space usage at the expense of increased"  NL
1343251881Speter"### latency and CPU usage reading and changing individual revprops.  They"  NL
1344251881Speter"### become an advantage when revprop caching has been enabled because a"    NL
1345251881Speter"### lot of data can be read in one go.  Values smaller than 4 kByte will"   NL
1346251881Speter"### not improve latency any further and quickly render revprop packing"     NL
1347251881Speter"### ineffective."                                                           NL
1348251881Speter"### revprop-pack-size is 64 kBytes by default for non-compressed revprop"   NL
1349251881Speter"### pack files and 256 kBytes when compression has been enabled."           NL
1350251881Speter"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 64"                                 NL
1351251881Speter"###"                                                                        NL
1352251881Speter"### To save disk space, packed revprop files may be compressed.  Standard"  NL
1353251881Speter"### revprops tend to allow for very effective compression.  Reading and"    NL
1354251881Speter"### even more so writing, become significantly more CPU intensive.  With"   NL
1355251881Speter"### revprop caching enabled, the overhead can be offset by reduced I/O"     NL
1356251881Speter"### unless you often modify revprops after packing."                        NL
1357251881Speter"### Compressing packed revprops is disabled by default."                    NL
1358251881Speter"# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false"                       NL
1359251881Speter;
1360251881Speter#undef NL
1361251881Speter  return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1362251881Speter                            fsfs_conf_contents, pool);
1363251881Speter}
1364251881Speter
1365251881Speterstatic svn_error_t *
1366251881Speterread_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
1367251881Speter                      const char *path,
1368251881Speter                      apr_pool_t *pool)
1369251881Speter{
1370251881Speter  char buf[80];
1371251881Speter  apr_file_t *file;
1372251881Speter  apr_size_t len;
1373251881Speter
1374251881Speter  SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
1375251881Speter                           APR_OS_DEFAULT, pool));
1376251881Speter  len = sizeof(buf);
1377251881Speter  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
1378251881Speter  SVN_ERR(svn_io_file_close(file, pool));
1379251881Speter
1380251881Speter  *min_unpacked_rev = SVN_STR_TO_REV(buf);
1381251881Speter  return SVN_NO_ERROR;
1382251881Speter}
1383251881Speter
1384251881Speterstatic svn_error_t *
1385251881Speterupdate_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
1386251881Speter{
1387251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1388251881Speter
1389251881Speter  SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT);
1390251881Speter
1391251881Speter  return read_min_unpacked_rev(&ffd->min_unpacked_rev,
1392251881Speter                               path_min_unpacked_rev(fs, pool),
1393251881Speter                               pool);
1394251881Speter}
1395251881Speter
1396251881Spetersvn_error_t *
1397251881Spetersvn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
1398251881Speter{
1399251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1400251881Speter  apr_file_t *uuid_file;
1401251881Speter  int format, max_files_per_dir;
1402251881Speter  char buf[APR_UUID_FORMATTED_LENGTH + 2];
1403251881Speter  apr_size_t limit;
1404251881Speter
1405251881Speter  fs->path = apr_pstrdup(fs->pool, path);
1406251881Speter
1407251881Speter  /* Read the FS format number. */
1408251881Speter  SVN_ERR(read_format(&format, &max_files_per_dir,
1409251881Speter                      path_format(fs, pool), pool));
1410251881Speter
1411251881Speter  /* Now we've got a format number no matter what. */
1412251881Speter  ffd->format = format;
1413251881Speter  ffd->max_files_per_dir = max_files_per_dir;
1414251881Speter
1415251881Speter  /* Read in and cache the repository uuid. */
1416251881Speter  SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, pool),
1417251881Speter                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
1418251881Speter
1419251881Speter  limit = sizeof(buf);
1420251881Speter  SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, pool));
1421251881Speter  fs->uuid = apr_pstrdup(fs->pool, buf);
1422251881Speter
1423251881Speter  SVN_ERR(svn_io_file_close(uuid_file, pool));
1424251881Speter
1425251881Speter  /* Read the min unpacked revision. */
1426251881Speter  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1427251881Speter    SVN_ERR(update_min_unpacked_rev(fs, pool));
1428251881Speter
1429251881Speter  /* Read the configuration file. */
1430251881Speter  SVN_ERR(read_config(ffd, fs->path, pool));
1431251881Speter
1432251881Speter  return get_youngest(&(ffd->youngest_rev_cache), path, pool);
1433251881Speter}
1434251881Speter
1435251881Speter/* Wrapper around svn_io_file_create which ignores EEXIST. */
1436251881Speterstatic svn_error_t *
1437251881Spetercreate_file_ignore_eexist(const char *file,
1438251881Speter                          const char *contents,
1439251881Speter                          apr_pool_t *pool)
1440251881Speter{
1441251881Speter  svn_error_t *err = svn_io_file_create(file, contents, pool);
1442251881Speter  if (err && APR_STATUS_IS_EEXIST(err->apr_err))
1443251881Speter    {
1444251881Speter      svn_error_clear(err);
1445251881Speter      err = SVN_NO_ERROR;
1446251881Speter    }
1447251881Speter  return svn_error_trace(err);
1448251881Speter}
1449251881Speter
1450251881Speter/* forward declarations */
1451251881Speter
1452251881Speterstatic svn_error_t *
1453251881Speterpack_revprops_shard(const char *pack_file_dir,
1454251881Speter                    const char *shard_path,
1455251881Speter                    apr_int64_t shard,
1456251881Speter                    int max_files_per_dir,
1457251881Speter                    apr_off_t max_pack_size,
1458251881Speter                    int compression_level,
1459251881Speter                    svn_cancel_func_t cancel_func,
1460251881Speter                    void *cancel_baton,
1461251881Speter                    apr_pool_t *scratch_pool);
1462251881Speter
1463251881Speterstatic svn_error_t *
1464251881Speterdelete_revprops_shard(const char *shard_path,
1465251881Speter                      apr_int64_t shard,
1466251881Speter                      int max_files_per_dir,
1467251881Speter                      svn_cancel_func_t cancel_func,
1468251881Speter                      void *cancel_baton,
1469251881Speter                      apr_pool_t *scratch_pool);
1470251881Speter
1471251881Speter/* In the filesystem FS, pack all revprop shards up to min_unpacked_rev.
1472253734Speter *
1473253734Speter * NOTE: Keep the old non-packed shards around until after the format bump.
1474253734Speter * Otherwise, re-running upgrade will drop the packed revprop shard but
1475253734Speter * have no unpacked data anymore.  Call upgrade_cleanup_pack_revprops after
1476253734Speter * the bump.
1477253734Speter *
1478251881Speter * Use SCRATCH_POOL for temporary allocations.
1479251881Speter */
1480251881Speterstatic svn_error_t *
1481251881Speterupgrade_pack_revprops(svn_fs_t *fs,
1482251881Speter                      apr_pool_t *scratch_pool)
1483251881Speter{
1484251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1485251881Speter  const char *revprops_shard_path;
1486251881Speter  const char *revprops_pack_file_dir;
1487251881Speter  apr_int64_t shard;
1488251881Speter  apr_int64_t first_unpacked_shard
1489251881Speter    =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
1490251881Speter
1491251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1492251881Speter  const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
1493251881Speter                                              scratch_pool);
1494251881Speter  int compression_level = ffd->compress_packed_revprops
1495251881Speter                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
1496251881Speter                           : SVN_DELTA_COMPRESSION_LEVEL_NONE;
1497251881Speter
1498251881Speter  /* first, pack all revprops shards to match the packed revision shards */
1499251881Speter  for (shard = 0; shard < first_unpacked_shard; ++shard)
1500251881Speter    {
1501251881Speter      revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
1502251881Speter                   apr_psprintf(iterpool,
1503251881Speter                                "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
1504251881Speter                                shard),
1505251881Speter                   iterpool);
1506251881Speter      revprops_shard_path = svn_dirent_join(revsprops_dir,
1507251881Speter                       apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
1508251881Speter                       iterpool);
1509251881Speter
1510251881Speter      SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
1511251881Speter                                  shard, ffd->max_files_per_dir,
1512251881Speter                                  (int)(0.9 * ffd->revprop_pack_size),
1513251881Speter                                  compression_level,
1514251881Speter                                  NULL, NULL, iterpool));
1515251881Speter      svn_pool_clear(iterpool);
1516251881Speter    }
1517251881Speter
1518253734Speter  svn_pool_destroy(iterpool);
1519253734Speter
1520253734Speter  return SVN_NO_ERROR;
1521253734Speter}
1522253734Speter
1523253734Speter/* In the filesystem FS, remove all non-packed revprop shards up to
1524253734Speter * min_unpacked_rev.  Use SCRATCH_POOL for temporary allocations.
1525253734Speter * See upgrade_pack_revprops for more info.
1526253734Speter */
1527253734Speterstatic svn_error_t *
1528253734Speterupgrade_cleanup_pack_revprops(svn_fs_t *fs,
1529253734Speter                              apr_pool_t *scratch_pool)
1530253734Speter{
1531253734Speter  fs_fs_data_t *ffd = fs->fsap_data;
1532253734Speter  const char *revprops_shard_path;
1533253734Speter  apr_int64_t shard;
1534253734Speter  apr_int64_t first_unpacked_shard
1535253734Speter    =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
1536253734Speter
1537253734Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1538253734Speter  const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
1539253734Speter                                              scratch_pool);
1540253734Speter
1541251881Speter  /* delete the non-packed revprops shards afterwards */
1542251881Speter  for (shard = 0; shard < first_unpacked_shard; ++shard)
1543251881Speter    {
1544251881Speter      revprops_shard_path = svn_dirent_join(revsprops_dir,
1545251881Speter                       apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
1546251881Speter                       iterpool);
1547251881Speter      SVN_ERR(delete_revprops_shard(revprops_shard_path,
1548251881Speter                                    shard, ffd->max_files_per_dir,
1549251881Speter                                    NULL, NULL, iterpool));
1550251881Speter      svn_pool_clear(iterpool);
1551251881Speter    }
1552251881Speter
1553251881Speter  svn_pool_destroy(iterpool);
1554251881Speter
1555251881Speter  return SVN_NO_ERROR;
1556251881Speter}
1557251881Speter
1558251881Speterstatic svn_error_t *
1559251881Speterupgrade_body(void *baton, apr_pool_t *pool)
1560251881Speter{
1561251881Speter  svn_fs_t *fs = baton;
1562251881Speter  int format, max_files_per_dir;
1563251881Speter  const char *format_path = path_format(fs, pool);
1564251881Speter  svn_node_kind_t kind;
1565253734Speter  svn_boolean_t needs_revprop_shard_cleanup = FALSE;
1566251881Speter
1567251881Speter  /* Read the FS format number and max-files-per-dir setting. */
1568251881Speter  SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool));
1569251881Speter
1570251881Speter  /* If the config file does not exist, create one. */
1571251881Speter  SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1572251881Speter                            &kind, pool));
1573251881Speter  switch (kind)
1574251881Speter    {
1575251881Speter    case svn_node_none:
1576251881Speter      SVN_ERR(write_config(fs, pool));
1577251881Speter      break;
1578251881Speter    case svn_node_file:
1579251881Speter      break;
1580251881Speter    default:
1581251881Speter      return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
1582251881Speter                               _("'%s' is not a regular file."
1583251881Speter                                 " Please move it out of "
1584251881Speter                                 "the way and try again"),
1585251881Speter                               svn_dirent_join(fs->path, PATH_CONFIG, pool));
1586251881Speter    }
1587251881Speter
1588251881Speter  /* If we're already up-to-date, there's nothing else to be done here. */
1589251881Speter  if (format == SVN_FS_FS__FORMAT_NUMBER)
1590251881Speter    return SVN_NO_ERROR;
1591251881Speter
1592251881Speter  /* If our filesystem predates the existance of the 'txn-current
1593251881Speter     file', make that file and its corresponding lock file. */
1594251881Speter  if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1595251881Speter    {
1596251881Speter      SVN_ERR(create_file_ignore_eexist(path_txn_current(fs, pool), "0\n",
1597251881Speter                                        pool));
1598251881Speter      SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs, pool), "",
1599251881Speter                                        pool));
1600251881Speter    }
1601251881Speter
1602251881Speter  /* If our filesystem predates the existance of the 'txn-protorevs'
1603251881Speter     dir, make that directory.  */
1604251881Speter  if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1605251881Speter    {
1606251881Speter      /* We don't use path_txn_proto_rev() here because it expects
1607251881Speter         we've already bumped our format. */
1608251881Speter      SVN_ERR(svn_io_make_dir_recursively(
1609251881Speter          svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, pool), pool));
1610251881Speter    }
1611251881Speter
1612251881Speter  /* If our filesystem is new enough, write the min unpacked rev file. */
1613251881Speter  if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
1614251881Speter    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
1615251881Speter
1616253734Speter  /* If the file system supports revision packing but not revprop packing
1617253734Speter     *and* the FS has been sharded, pack the revprops up to the point that
1618253734Speter     revision data has been packed.  However, keep the non-packed revprop
1619253734Speter     files around until after the format bump */
1620251881Speter  if (   format >= SVN_FS_FS__MIN_PACKED_FORMAT
1621253734Speter      && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
1622253734Speter      && max_files_per_dir > 0)
1623253734Speter    {
1624253734Speter      needs_revprop_shard_cleanup = TRUE;
1625253734Speter      SVN_ERR(upgrade_pack_revprops(fs, pool));
1626253734Speter    }
1627251881Speter
1628251881Speter  /* Bump the format file. */
1629253734Speter  SVN_ERR(write_format(format_path, SVN_FS_FS__FORMAT_NUMBER,
1630253734Speter                       max_files_per_dir, TRUE, pool));
1631253734Speter
1632253734Speter  /* Now, it is safe to remove the redundant revprop files. */
1633253734Speter  if (needs_revprop_shard_cleanup)
1634253734Speter    SVN_ERR(upgrade_cleanup_pack_revprops(fs, pool));
1635253734Speter
1636253734Speter  /* Done */
1637253734Speter  return SVN_NO_ERROR;
1638251881Speter}
1639251881Speter
1640251881Speter
1641251881Spetersvn_error_t *
1642251881Spetersvn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool)
1643251881Speter{
1644251881Speter  return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool);
1645251881Speter}
1646251881Speter
1647251881Speter
1648251881Speter/* Functions for dealing with recoverable errors on mutable files
1649251881Speter *
1650251881Speter * Revprops, current, and txn-current files are mutable; that is, they
1651251881Speter * change as part of normal fsfs operation, in constrat to revs files, or
1652251881Speter * the format file, which are written once at create (or upgrade) time.
1653251881Speter * When more than one host writes to the same repository, we will
1654251881Speter * sometimes see these recoverable errors when accesssing these files.
1655251881Speter *
1656251881Speter * These errors all relate to NFS, and thus we only use this retry code if
1657251881Speter * ESTALE is defined.
1658251881Speter *
1659251881Speter ** ESTALE
1660251881Speter *
1661251881Speter * In NFS v3 and under, the server doesn't track opened files.  If you
1662251881Speter * unlink(2) or rename(2) a file held open by another process *on the
1663251881Speter * same host*, that host's kernel typically renames the file to
1664251881Speter * .nfsXXXX and automatically deletes that when it's no longer open,
1665251881Speter * but this behavior is not required.
1666251881Speter *
1667251881Speter * For obvious reasons, this does not work *across hosts*.  No one
1668251881Speter * knows about the opened file; not the server, and not the deleting
1669251881Speter * client.  So the file vanishes, and the reader gets stale NFS file
1670251881Speter * handle.
1671251881Speter *
1672251881Speter ** EIO, ENOENT
1673251881Speter *
1674251881Speter * Some client implementations (at least the 2.6.18.5 kernel that ships
1675251881Speter * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or
1676251881Speter * even EIO errors when trying to read these files that have been renamed
1677251881Speter * over on some other host.
1678251881Speter *
1679251881Speter ** Solution
1680251881Speter *
1681251881Speter * Try open and read of such files in try_stringbuf_from_file().  Call
1682251881Speter * this function within a loop of RECOVERABLE_RETRY_COUNT iterations
1683251881Speter * (though, realistically, the second try will succeed).
1684251881Speter */
1685251881Speter
1686251881Speter#define RECOVERABLE_RETRY_COUNT 10
1687251881Speter
1688251881Speter/* Read the file at PATH and return its content in *CONTENT. *CONTENT will
1689251881Speter * not be modified unless the whole file was read successfully.
1690251881Speter *
1691251881Speter * ESTALE, EIO and ENOENT will not cause this function to return an error
1692251881Speter * unless LAST_ATTEMPT has been set.  If MISSING is not NULL, indicate
1693251881Speter * missing files (ENOENT) there.
1694251881Speter *
1695251881Speter * Use POOL for allocations.
1696251881Speter */
1697251881Speterstatic svn_error_t *
1698251881Spetertry_stringbuf_from_file(svn_stringbuf_t **content,
1699251881Speter                        svn_boolean_t *missing,
1700251881Speter                        const char *path,
1701251881Speter                        svn_boolean_t last_attempt,
1702251881Speter                        apr_pool_t *pool)
1703251881Speter{
1704251881Speter  svn_error_t *err = svn_stringbuf_from_file2(content, path, pool);
1705251881Speter  if (missing)
1706251881Speter    *missing = FALSE;
1707251881Speter
1708251881Speter  if (err)
1709251881Speter    {
1710251881Speter      *content = NULL;
1711251881Speter
1712251881Speter      if (APR_STATUS_IS_ENOENT(err->apr_err))
1713251881Speter        {
1714251881Speter          if (!last_attempt)
1715251881Speter            {
1716251881Speter              svn_error_clear(err);
1717251881Speter              if (missing)
1718251881Speter                *missing = TRUE;
1719251881Speter              return SVN_NO_ERROR;
1720251881Speter            }
1721251881Speter        }
1722251881Speter#ifdef ESTALE
1723251881Speter      else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
1724251881Speter                || APR_TO_OS_ERROR(err->apr_err) == EIO)
1725251881Speter        {
1726251881Speter          if (!last_attempt)
1727251881Speter            {
1728251881Speter              svn_error_clear(err);
1729251881Speter              return SVN_NO_ERROR;
1730251881Speter            }
1731251881Speter        }
1732251881Speter#endif
1733251881Speter    }
1734251881Speter
1735251881Speter  return svn_error_trace(err);
1736251881Speter}
1737251881Speter
1738251881Speter/* Read the 'current' file FNAME and store the contents in *BUF.
1739251881Speter   Allocations are performed in POOL. */
1740251881Speterstatic svn_error_t *
1741251881Speterread_content(svn_stringbuf_t **content, const char *fname, apr_pool_t *pool)
1742251881Speter{
1743251881Speter  int i;
1744251881Speter  *content = NULL;
1745251881Speter
1746251881Speter  for (i = 0; !*content && (i < RECOVERABLE_RETRY_COUNT); ++i)
1747251881Speter    SVN_ERR(try_stringbuf_from_file(content, NULL,
1748251881Speter                                    fname, i + 1 < RECOVERABLE_RETRY_COUNT,
1749251881Speter                                    pool));
1750251881Speter
1751251881Speter  if (!*content)
1752251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1753251881Speter                             _("Can't read '%s'"),
1754251881Speter                             svn_dirent_local_style(fname, pool));
1755251881Speter
1756251881Speter  return SVN_NO_ERROR;
1757251881Speter}
1758251881Speter
1759251881Speter/* Find the youngest revision in a repository at path FS_PATH and
1760251881Speter   return it in *YOUNGEST_P.  Perform temporary allocations in
1761251881Speter   POOL. */
1762251881Speterstatic svn_error_t *
1763251881Speterget_youngest(svn_revnum_t *youngest_p,
1764251881Speter             const char *fs_path,
1765251881Speter             apr_pool_t *pool)
1766251881Speter{
1767251881Speter  svn_stringbuf_t *buf;
1768251881Speter  SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool),
1769251881Speter                       pool));
1770251881Speter
1771251881Speter  *youngest_p = SVN_STR_TO_REV(buf->data);
1772251881Speter
1773251881Speter  return SVN_NO_ERROR;
1774251881Speter}
1775251881Speter
1776251881Speter
1777251881Spetersvn_error_t *
1778251881Spetersvn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
1779251881Speter                        svn_fs_t *fs,
1780251881Speter                        apr_pool_t *pool)
1781251881Speter{
1782251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1783251881Speter
1784251881Speter  SVN_ERR(get_youngest(youngest_p, fs->path, pool));
1785251881Speter  ffd->youngest_rev_cache = *youngest_p;
1786251881Speter
1787251881Speter  return SVN_NO_ERROR;
1788251881Speter}
1789251881Speter
1790251881Speter/* Given a revision file FILE that has been pre-positioned at the
1791251881Speter   beginning of a Node-Rev header block, read in that header block and
1792251881Speter   store it in the apr_hash_t HEADERS.  All allocations will be from
1793251881Speter   POOL. */
1794251881Speterstatic svn_error_t * read_header_block(apr_hash_t **headers,
1795251881Speter                                       svn_stream_t *stream,
1796251881Speter                                       apr_pool_t *pool)
1797251881Speter{
1798251881Speter  *headers = apr_hash_make(pool);
1799251881Speter
1800251881Speter  while (1)
1801251881Speter    {
1802251881Speter      svn_stringbuf_t *header_str;
1803251881Speter      const char *name, *value;
1804251881Speter      apr_size_t i = 0;
1805251881Speter      svn_boolean_t eof;
1806251881Speter
1807251881Speter      SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
1808251881Speter
1809251881Speter      if (eof || header_str->len == 0)
1810251881Speter        break; /* end of header block */
1811251881Speter
1812251881Speter      while (header_str->data[i] != ':')
1813251881Speter        {
1814251881Speter          if (header_str->data[i] == '\0')
1815251881Speter            return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1816251881Speter                                     _("Found malformed header '%s' in "
1817251881Speter                                       "revision file"),
1818251881Speter                                     header_str->data);
1819251881Speter          i++;
1820251881Speter        }
1821251881Speter
1822251881Speter      /* Create a 'name' string and point to it. */
1823251881Speter      header_str->data[i] = '\0';
1824251881Speter      name = header_str->data;
1825251881Speter
1826251881Speter      /* Skip over the NULL byte and the space following it. */
1827251881Speter      i += 2;
1828251881Speter
1829251881Speter      if (i > header_str->len)
1830251881Speter        {
1831251881Speter          /* Restore the original line for the error. */
1832251881Speter          i -= 2;
1833251881Speter          header_str->data[i] = ':';
1834251881Speter          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1835251881Speter                                   _("Found malformed header '%s' in "
1836251881Speter                                     "revision file"),
1837251881Speter                                   header_str->data);
1838251881Speter        }
1839251881Speter
1840251881Speter      value = header_str->data + i;
1841251881Speter
1842251881Speter      /* header_str is safely in our pool, so we can use bits of it as
1843251881Speter         key and value. */
1844251881Speter      svn_hash_sets(*headers, name, value);
1845251881Speter    }
1846251881Speter
1847251881Speter  return SVN_NO_ERROR;
1848251881Speter}
1849251881Speter
1850251881Speter/* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer
1851251881Speter   than the current youngest revision or is simply not a valid
1852251881Speter   revision number, else return success.
1853251881Speter
1854251881Speter   FSFS is based around the concept that commits only take effect when
1855251881Speter   the number in "current" is bumped.  Thus if there happens to be a rev
1856251881Speter   or revprops file installed for a revision higher than the one recorded
1857251881Speter   in "current" (because a commit failed between installing the rev file
1858251881Speter   and bumping "current", or because an administrator rolled back the
1859251881Speter   repository by resetting "current" without deleting rev files, etc), it
1860251881Speter   ought to be completely ignored.  This function provides the check
1861251881Speter   by which callers can make that decision. */
1862251881Speterstatic svn_error_t *
1863251881Speterensure_revision_exists(svn_fs_t *fs,
1864251881Speter                       svn_revnum_t rev,
1865251881Speter                       apr_pool_t *pool)
1866251881Speter{
1867251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1868251881Speter
1869251881Speter  if (! SVN_IS_VALID_REVNUM(rev))
1870251881Speter    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1871251881Speter                             _("Invalid revision number '%ld'"), rev);
1872251881Speter
1873251881Speter
1874251881Speter  /* Did the revision exist the last time we checked the current
1875251881Speter     file? */
1876251881Speter  if (rev <= ffd->youngest_rev_cache)
1877251881Speter    return SVN_NO_ERROR;
1878251881Speter
1879251881Speter  SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool));
1880251881Speter
1881251881Speter  /* Check again. */
1882251881Speter  if (rev <= ffd->youngest_rev_cache)
1883251881Speter    return SVN_NO_ERROR;
1884251881Speter
1885251881Speter  return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1886251881Speter                           _("No such revision %ld"), rev);
1887251881Speter}
1888251881Speter
1889251881Spetersvn_error_t *
1890251881Spetersvn_fs_fs__revision_exists(svn_revnum_t rev,
1891251881Speter                           svn_fs_t *fs,
1892251881Speter                           apr_pool_t *pool)
1893251881Speter{
1894251881Speter  /* Different order of parameters. */
1895251881Speter  SVN_ERR(ensure_revision_exists(fs, rev, pool));
1896251881Speter  return SVN_NO_ERROR;
1897251881Speter}
1898251881Speter
1899251881Speter/* Open the correct revision file for REV.  If the filesystem FS has
1900251881Speter   been packed, *FILE will be set to the packed file; otherwise, set *FILE
1901251881Speter   to the revision file for REV.  Return SVN_ERR_FS_NO_SUCH_REVISION if the
1902251881Speter   file doesn't exist.
1903251881Speter
1904251881Speter   TODO: Consider returning an indication of whether this is a packed rev
1905251881Speter         file, so the caller need not rely on is_packed_rev() which in turn
1906251881Speter         relies on the cached FFD->min_unpacked_rev value not having changed
1907251881Speter         since the rev file was opened.
1908251881Speter
1909251881Speter   Use POOL for allocations. */
1910251881Speterstatic svn_error_t *
1911251881Speteropen_pack_or_rev_file(apr_file_t **file,
1912251881Speter                      svn_fs_t *fs,
1913251881Speter                      svn_revnum_t rev,
1914251881Speter                      apr_pool_t *pool)
1915251881Speter{
1916251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
1917251881Speter  svn_error_t *err;
1918251881Speter  const char *path;
1919251881Speter  svn_boolean_t retry = FALSE;
1920251881Speter
1921251881Speter  do
1922251881Speter    {
1923251881Speter      err = svn_fs_fs__path_rev_absolute(&path, fs, rev, pool);
1924251881Speter
1925251881Speter      /* open the revision file in buffered r/o mode */
1926251881Speter      if (! err)
1927251881Speter        err = svn_io_file_open(file, path,
1928251881Speter                              APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
1929251881Speter
1930251881Speter      if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1931251881Speter        {
1932251881Speter          if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1933251881Speter            {
1934251881Speter              /* Could not open the file. This may happen if the
1935251881Speter               * file once existed but got packed later. */
1936251881Speter              svn_error_clear(err);
1937251881Speter
1938251881Speter              /* if that was our 2nd attempt, leave it at that. */
1939251881Speter              if (retry)
1940251881Speter                return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1941251881Speter                                         _("No such revision %ld"), rev);
1942251881Speter
1943251881Speter              /* We failed for the first time. Refresh cache & retry. */
1944251881Speter              SVN_ERR(update_min_unpacked_rev(fs, pool));
1945251881Speter
1946251881Speter              retry = TRUE;
1947251881Speter            }
1948251881Speter          else
1949251881Speter            {
1950251881Speter              svn_error_clear(err);
1951251881Speter              return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1952251881Speter                                       _("No such revision %ld"), rev);
1953251881Speter            }
1954251881Speter        }
1955251881Speter      else
1956251881Speter        {
1957251881Speter          retry = FALSE;
1958251881Speter        }
1959251881Speter    }
1960251881Speter  while (retry);
1961251881Speter
1962251881Speter  return svn_error_trace(err);
1963251881Speter}
1964251881Speter
1965251881Speter/* Reads a line from STREAM and converts it to a 64 bit integer to be
1966251881Speter * returned in *RESULT.  If we encounter eof, set *HIT_EOF and leave
1967251881Speter * *RESULT unchanged.  If HIT_EOF is NULL, EOF causes an "corrupt FS"
1968251881Speter * error return.
1969251881Speter * SCRATCH_POOL is used for temporary allocations.
1970251881Speter */
1971251881Speterstatic svn_error_t *
1972251881Speterread_number_from_stream(apr_int64_t *result,
1973251881Speter                        svn_boolean_t *hit_eof,
1974251881Speter                        svn_stream_t *stream,
1975251881Speter                        apr_pool_t *scratch_pool)
1976251881Speter{
1977251881Speter  svn_stringbuf_t *sb;
1978251881Speter  svn_boolean_t eof;
1979251881Speter  svn_error_t *err;
1980251881Speter
1981251881Speter  SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
1982251881Speter  if (hit_eof)
1983251881Speter    *hit_eof = eof;
1984251881Speter  else
1985251881Speter    if (eof)
1986251881Speter      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
1987251881Speter
1988251881Speter  if (!eof)
1989251881Speter    {
1990251881Speter      err = svn_cstring_atoi64(result, sb->data);
1991251881Speter      if (err)
1992251881Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
1993251881Speter                                 _("Number '%s' invalid or too large"),
1994251881Speter                                 sb->data);
1995251881Speter    }
1996251881Speter
1997251881Speter  return SVN_NO_ERROR;
1998251881Speter}
1999251881Speter
2000251881Speter/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
2001251881Speter   Use POOL for temporary allocations. */
2002251881Speterstatic svn_error_t *
2003251881Speterget_packed_offset(apr_off_t *rev_offset,
2004251881Speter                  svn_fs_t *fs,
2005251881Speter                  svn_revnum_t rev,
2006251881Speter                  apr_pool_t *pool)
2007251881Speter{
2008251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
2009251881Speter  svn_stream_t *manifest_stream;
2010251881Speter  svn_boolean_t is_cached;
2011251881Speter  svn_revnum_t shard;
2012251881Speter  apr_int64_t shard_pos;
2013251881Speter  apr_array_header_t *manifest;
2014251881Speter  apr_pool_t *iterpool;
2015251881Speter
2016251881Speter  shard = rev / ffd->max_files_per_dir;
2017251881Speter
2018251881Speter  /* position of the shard within the manifest */
2019251881Speter  shard_pos = rev % ffd->max_files_per_dir;
2020251881Speter
2021251881Speter  /* fetch exactly that element into *rev_offset, if the manifest is found
2022251881Speter     in the cache */
2023251881Speter  SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached,
2024251881Speter                                 ffd->packed_offset_cache, &shard,
2025251881Speter                                 svn_fs_fs__get_sharded_offset, &shard_pos,
2026251881Speter                                 pool));
2027251881Speter
2028251881Speter  if (is_cached)
2029251881Speter      return SVN_NO_ERROR;
2030251881Speter
2031251881Speter  /* Open the manifest file. */
2032251881Speter  SVN_ERR(svn_stream_open_readonly(&manifest_stream,
2033251881Speter                                   path_rev_packed(fs, rev, PATH_MANIFEST,
2034251881Speter                                                   pool),
2035251881Speter                                   pool, pool));
2036251881Speter
2037251881Speter  /* While we're here, let's just read the entire manifest file into an array,
2038251881Speter     so we can cache the entire thing. */
2039251881Speter  iterpool = svn_pool_create(pool);
2040251881Speter  manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t));
2041251881Speter  while (1)
2042251881Speter    {
2043251881Speter      svn_boolean_t eof;
2044251881Speter      apr_int64_t val;
2045251881Speter
2046251881Speter      svn_pool_clear(iterpool);
2047251881Speter      SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
2048251881Speter      if (eof)
2049251881Speter        break;
2050251881Speter
2051251881Speter      APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
2052251881Speter    }
2053251881Speter  svn_pool_destroy(iterpool);
2054251881Speter
2055251881Speter  *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
2056251881Speter                              apr_off_t);
2057251881Speter
2058251881Speter  /* Close up shop and cache the array. */
2059251881Speter  SVN_ERR(svn_stream_close(manifest_stream));
2060251881Speter  return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool);
2061251881Speter}
2062251881Speter
2063251881Speter/* Open the revision file for revision REV in filesystem FS and store
2064251881Speter   the newly opened file in FILE.  Seek to location OFFSET before
2065251881Speter   returning.  Perform temporary allocations in POOL. */
2066251881Speterstatic svn_error_t *
2067251881Speteropen_and_seek_revision(apr_file_t **file,
2068251881Speter                       svn_fs_t *fs,
2069251881Speter                       svn_revnum_t rev,
2070251881Speter                       apr_off_t offset,
2071251881Speter                       apr_pool_t *pool)
2072251881Speter{
2073251881Speter  apr_file_t *rev_file;
2074251881Speter
2075251881Speter  SVN_ERR(ensure_revision_exists(fs, rev, pool));
2076251881Speter
2077251881Speter  SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, pool));
2078251881Speter
2079251881Speter  if (is_packed_rev(fs, rev))
2080251881Speter    {
2081251881Speter      apr_off_t rev_offset;
2082251881Speter
2083251881Speter      SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
2084251881Speter      offset += rev_offset;
2085251881Speter    }
2086251881Speter
2087251881Speter  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2088251881Speter
2089251881Speter  *file = rev_file;
2090251881Speter
2091251881Speter  return SVN_NO_ERROR;
2092251881Speter}
2093251881Speter
2094251881Speter/* Open the representation for a node-revision in transaction TXN_ID
2095251881Speter   in filesystem FS and store the newly opened file in FILE.  Seek to
2096251881Speter   location OFFSET before returning.  Perform temporary allocations in
2097251881Speter   POOL.  Only appropriate for file contents, nor props or directory
2098251881Speter   contents. */
2099251881Speterstatic svn_error_t *
2100251881Speteropen_and_seek_transaction(apr_file_t **file,
2101251881Speter                          svn_fs_t *fs,
2102251881Speter                          const char *txn_id,
2103251881Speter                          representation_t *rep,
2104251881Speter                          apr_pool_t *pool)
2105251881Speter{
2106251881Speter  apr_file_t *rev_file;
2107251881Speter  apr_off_t offset;
2108251881Speter
2109251881Speter  SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
2110251881Speter                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
2111251881Speter
2112251881Speter  offset = rep->offset;
2113251881Speter  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2114251881Speter
2115251881Speter  *file = rev_file;
2116251881Speter
2117251881Speter  return SVN_NO_ERROR;
2118251881Speter}
2119251881Speter
2120251881Speter/* Given a node-id ID, and a representation REP in filesystem FS, open
2121251881Speter   the correct file and seek to the correction location.  Store this
2122251881Speter   file in *FILE_P.  Perform any allocations in POOL. */
2123251881Speterstatic svn_error_t *
2124251881Speteropen_and_seek_representation(apr_file_t **file_p,
2125251881Speter                             svn_fs_t *fs,
2126251881Speter                             representation_t *rep,
2127251881Speter                             apr_pool_t *pool)
2128251881Speter{
2129251881Speter  if (! rep->txn_id)
2130251881Speter    return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
2131251881Speter                                  pool);
2132251881Speter  else
2133251881Speter    return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
2134251881Speter}
2135251881Speter
2136251881Speter/* Parse the description of a representation from STRING and store it
2137251881Speter   into *REP_P.  If the representation is mutable (the revision is
2138251881Speter   given as -1), then use TXN_ID for the representation's txn_id
2139251881Speter   field.  If MUTABLE_REP_TRUNCATED is true, then this representation
2140251881Speter   is for property or directory contents, and no information will be
2141251881Speter   expected except the "-1" revision number for a mutable
2142251881Speter   representation.  Allocate *REP_P in POOL. */
2143251881Speterstatic svn_error_t *
2144251881Speterread_rep_offsets_body(representation_t **rep_p,
2145251881Speter                      char *string,
2146251881Speter                      const char *txn_id,
2147251881Speter                      svn_boolean_t mutable_rep_truncated,
2148251881Speter                      apr_pool_t *pool)
2149251881Speter{
2150251881Speter  representation_t *rep;
2151251881Speter  char *str;
2152251881Speter  apr_int64_t val;
2153251881Speter
2154251881Speter  rep = apr_pcalloc(pool, sizeof(*rep));
2155251881Speter  *rep_p = rep;
2156251881Speter
2157251881Speter  str = svn_cstring_tokenize(" ", &string);
2158251881Speter  if (str == NULL)
2159251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2160251881Speter                            _("Malformed text representation offset line in node-rev"));
2161251881Speter
2162251881Speter
2163251881Speter  rep->revision = SVN_STR_TO_REV(str);
2164251881Speter  if (rep->revision == SVN_INVALID_REVNUM)
2165251881Speter    {
2166251881Speter      rep->txn_id = txn_id;
2167251881Speter      if (mutable_rep_truncated)
2168251881Speter        return SVN_NO_ERROR;
2169251881Speter    }
2170251881Speter
2171251881Speter  str = svn_cstring_tokenize(" ", &string);
2172251881Speter  if (str == NULL)
2173251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2174251881Speter                            _("Malformed text representation offset line in node-rev"));
2175251881Speter
2176251881Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
2177251881Speter  rep->offset = (apr_off_t)val;
2178251881Speter
2179251881Speter  str = svn_cstring_tokenize(" ", &string);
2180251881Speter  if (str == NULL)
2181251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2182251881Speter                            _("Malformed text representation offset line in node-rev"));
2183251881Speter
2184251881Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
2185251881Speter  rep->size = (svn_filesize_t)val;
2186251881Speter
2187251881Speter  str = svn_cstring_tokenize(" ", &string);
2188251881Speter  if (str == NULL)
2189251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2190251881Speter                            _("Malformed text representation offset line in node-rev"));
2191251881Speter
2192251881Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
2193251881Speter  rep->expanded_size = (svn_filesize_t)val;
2194251881Speter
2195251881Speter  /* Read in the MD5 hash. */
2196251881Speter  str = svn_cstring_tokenize(" ", &string);
2197251881Speter  if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
2198251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2199251881Speter                            _("Malformed text representation offset line in node-rev"));
2200251881Speter
2201251881Speter  SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str,
2202251881Speter                                 pool));
2203251881Speter
2204251881Speter  /* The remaining fields are only used for formats >= 4, so check that. */
2205251881Speter  str = svn_cstring_tokenize(" ", &string);
2206251881Speter  if (str == NULL)
2207251881Speter    return SVN_NO_ERROR;
2208251881Speter
2209251881Speter  /* Read the SHA1 hash. */
2210251881Speter  if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
2211251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2212251881Speter                            _("Malformed text representation offset line in node-rev"));
2213251881Speter
2214251881Speter  SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str,
2215251881Speter                                 pool));
2216251881Speter
2217251881Speter  /* Read the uniquifier. */
2218251881Speter  str = svn_cstring_tokenize(" ", &string);
2219251881Speter  if (str == NULL)
2220251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2221251881Speter                            _("Malformed text representation offset line in node-rev"));
2222251881Speter
2223251881Speter  rep->uniquifier = apr_pstrdup(pool, str);
2224251881Speter
2225251881Speter  return SVN_NO_ERROR;
2226251881Speter}
2227251881Speter
2228251881Speter/* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
2229251881Speter   and adding an error message. */
2230251881Speterstatic svn_error_t *
2231251881Speterread_rep_offsets(representation_t **rep_p,
2232251881Speter                 char *string,
2233251881Speter                 const svn_fs_id_t *noderev_id,
2234251881Speter                 svn_boolean_t mutable_rep_truncated,
2235251881Speter                 apr_pool_t *pool)
2236251881Speter{
2237251881Speter  svn_error_t *err;
2238251881Speter  const char *txn_id;
2239251881Speter
2240251881Speter  if (noderev_id)
2241251881Speter    txn_id = svn_fs_fs__id_txn_id(noderev_id);
2242251881Speter  else
2243251881Speter    txn_id = NULL;
2244251881Speter
2245251881Speter  err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated,
2246251881Speter                              pool);
2247251881Speter  if (err)
2248251881Speter    {
2249251881Speter      const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(noderev_id, pool);
2250251881Speter      const char *where;
2251251881Speter      where = apr_psprintf(pool,
2252251881Speter                           _("While reading representation offsets "
2253251881Speter                             "for node-revision '%s':"),
2254251881Speter                           noderev_id ? id_unparsed->data : "(null)");
2255251881Speter
2256251881Speter      return svn_error_quick_wrap(err, where);
2257251881Speter    }
2258251881Speter  else
2259251881Speter    return SVN_NO_ERROR;
2260251881Speter}
2261251881Speter
2262251881Speterstatic svn_error_t *
2263251881Spetererr_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
2264251881Speter{
2265251881Speter  svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool);
2266251881Speter  return svn_error_createf
2267251881Speter    (SVN_ERR_FS_ID_NOT_FOUND, 0,
2268251881Speter     _("Reference to non-existent node '%s' in filesystem '%s'"),
2269251881Speter     id_str->data, fs->path);
2270251881Speter}
2271251881Speter
2272251881Speter/* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev
2273251881Speter * caching has been enabled and the data can be found, IS_CACHED will
2274251881Speter * be set to TRUE. The noderev will be allocated from POOL.
2275251881Speter *
2276251881Speter * Non-permanent ids (e.g. ids within a TXN) will not be cached.
2277251881Speter */
2278251881Speterstatic svn_error_t *
2279251881Speterget_cached_node_revision_body(node_revision_t **noderev_p,
2280251881Speter                              svn_fs_t *fs,
2281251881Speter                              const svn_fs_id_t *id,
2282251881Speter                              svn_boolean_t *is_cached,
2283251881Speter                              apr_pool_t *pool)
2284251881Speter{
2285251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
2286251881Speter  if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id))
2287251881Speter    {
2288251881Speter      *is_cached = FALSE;
2289251881Speter    }
2290251881Speter  else
2291251881Speter    {
2292251881Speter      pair_cache_key_t key = { 0 };
2293251881Speter
2294251881Speter      key.revision = svn_fs_fs__id_rev(id);
2295251881Speter      key.second = svn_fs_fs__id_offset(id);
2296251881Speter      SVN_ERR(svn_cache__get((void **) noderev_p,
2297251881Speter                            is_cached,
2298251881Speter                            ffd->node_revision_cache,
2299251881Speter                            &key,
2300251881Speter                            pool));
2301251881Speter    }
2302251881Speter
2303251881Speter  return SVN_NO_ERROR;
2304251881Speter}
2305251881Speter
2306251881Speter/* If noderev caching has been enabled, store the NODEREV_P for the given ID
2307251881Speter * in FS' node revsion cache. SCRATCH_POOL is used for temporary allcations.
2308251881Speter *
2309251881Speter * Non-permanent ids (e.g. ids within a TXN) will not be cached.
2310251881Speter */
2311251881Speterstatic svn_error_t *
2312251881Speterset_cached_node_revision_body(node_revision_t *noderev_p,
2313251881Speter                              svn_fs_t *fs,
2314251881Speter                              const svn_fs_id_t *id,
2315251881Speter                              apr_pool_t *scratch_pool)
2316251881Speter{
2317251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
2318251881Speter
2319251881Speter  if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
2320251881Speter    {
2321251881Speter      pair_cache_key_t key = { 0 };
2322251881Speter
2323251881Speter      key.revision = svn_fs_fs__id_rev(id);
2324251881Speter      key.second = svn_fs_fs__id_offset(id);
2325251881Speter      return svn_cache__set(ffd->node_revision_cache,
2326251881Speter                            &key,
2327251881Speter                            noderev_p,
2328251881Speter                            scratch_pool);
2329251881Speter    }
2330251881Speter
2331251881Speter  return SVN_NO_ERROR;
2332251881Speter}
2333251881Speter
2334251881Speter/* Get the node-revision for the node ID in FS.
2335251881Speter   Set *NODEREV_P to the new node-revision structure, allocated in POOL.
2336251881Speter   See svn_fs_fs__get_node_revision, which wraps this and adds another
2337251881Speter   error. */
2338251881Speterstatic svn_error_t *
2339251881Speterget_node_revision_body(node_revision_t **noderev_p,
2340251881Speter                       svn_fs_t *fs,
2341251881Speter                       const svn_fs_id_t *id,
2342251881Speter                       apr_pool_t *pool)
2343251881Speter{
2344251881Speter  apr_file_t *revision_file;
2345251881Speter  svn_error_t *err;
2346251881Speter  svn_boolean_t is_cached = FALSE;
2347251881Speter
2348251881Speter  /* First, try a cache lookup. If that succeeds, we are done here. */
2349251881Speter  SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool));
2350251881Speter  if (is_cached)
2351251881Speter    return SVN_NO_ERROR;
2352251881Speter
2353251881Speter  if (svn_fs_fs__id_txn_id(id))
2354251881Speter    {
2355251881Speter      /* This is a transaction node-rev. */
2356251881Speter      err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
2357251881Speter                             APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
2358251881Speter    }
2359251881Speter  else
2360251881Speter    {
2361251881Speter      /* This is a revision node-rev. */
2362251881Speter      err = open_and_seek_revision(&revision_file, fs,
2363251881Speter                                   svn_fs_fs__id_rev(id),
2364251881Speter                                   svn_fs_fs__id_offset(id),
2365251881Speter                                   pool);
2366251881Speter    }
2367251881Speter
2368251881Speter  if (err)
2369251881Speter    {
2370251881Speter      if (APR_STATUS_IS_ENOENT(err->apr_err))
2371251881Speter        {
2372251881Speter          svn_error_clear(err);
2373251881Speter          return svn_error_trace(err_dangling_id(fs, id));
2374251881Speter        }
2375251881Speter
2376251881Speter      return svn_error_trace(err);
2377251881Speter    }
2378251881Speter
2379251881Speter  SVN_ERR(svn_fs_fs__read_noderev(noderev_p,
2380251881Speter                                  svn_stream_from_aprfile2(revision_file, FALSE,
2381251881Speter                                                           pool),
2382251881Speter                                  pool));
2383251881Speter
2384251881Speter  /* The noderev is not in cache, yet. Add it, if caching has been enabled. */
2385251881Speter  return set_cached_node_revision_body(*noderev_p, fs, id, pool);
2386251881Speter}
2387251881Speter
2388251881Spetersvn_error_t *
2389251881Spetersvn_fs_fs__read_noderev(node_revision_t **noderev_p,
2390251881Speter                        svn_stream_t *stream,
2391251881Speter                        apr_pool_t *pool)
2392251881Speter{
2393251881Speter  apr_hash_t *headers;
2394251881Speter  node_revision_t *noderev;
2395251881Speter  char *value;
2396251881Speter  const char *noderev_id;
2397251881Speter
2398251881Speter  SVN_ERR(read_header_block(&headers, stream, pool));
2399251881Speter
2400251881Speter  noderev = apr_pcalloc(pool, sizeof(*noderev));
2401251881Speter
2402251881Speter  /* Read the node-rev id. */
2403251881Speter  value = svn_hash_gets(headers, HEADER_ID);
2404251881Speter  if (value == NULL)
2405251881Speter      /* ### More information: filename/offset coordinates */
2406251881Speter      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2407251881Speter                              _("Missing id field in node-rev"));
2408251881Speter
2409251881Speter  SVN_ERR(svn_stream_close(stream));
2410251881Speter
2411251881Speter  noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
2412251881Speter  noderev_id = value; /* for error messages later */
2413251881Speter
2414251881Speter  /* Read the type. */
2415251881Speter  value = svn_hash_gets(headers, HEADER_TYPE);
2416251881Speter
2417251881Speter  if ((value == NULL) ||
2418251881Speter      (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
2419251881Speter    /* ### s/kind/type/ */
2420251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2421251881Speter                             _("Missing kind field in node-rev '%s'"),
2422251881Speter                             noderev_id);
2423251881Speter
2424251881Speter  noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file
2425251881Speter    : svn_node_dir;
2426251881Speter
2427251881Speter  /* Read the 'count' field. */
2428251881Speter  value = svn_hash_gets(headers, HEADER_COUNT);
2429251881Speter  if (value)
2430251881Speter    SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
2431251881Speter  else
2432251881Speter    noderev->predecessor_count = 0;
2433251881Speter
2434251881Speter  /* Get the properties location. */
2435251881Speter  value = svn_hash_gets(headers, HEADER_PROPS);
2436251881Speter  if (value)
2437251881Speter    {
2438251881Speter      SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
2439251881Speter                               noderev->id, TRUE, pool));
2440251881Speter    }
2441251881Speter
2442251881Speter  /* Get the data location. */
2443251881Speter  value = svn_hash_gets(headers, HEADER_TEXT);
2444251881Speter  if (value)
2445251881Speter    {
2446251881Speter      SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
2447251881Speter                               noderev->id,
2448251881Speter                               (noderev->kind == svn_node_dir), pool));
2449251881Speter    }
2450251881Speter
2451251881Speter  /* Get the created path. */
2452251881Speter  value = svn_hash_gets(headers, HEADER_CPATH);
2453251881Speter  if (value == NULL)
2454251881Speter    {
2455251881Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2456251881Speter                               _("Missing cpath field in node-rev '%s'"),
2457251881Speter                               noderev_id);
2458251881Speter    }
2459251881Speter  else
2460251881Speter    {
2461251881Speter      noderev->created_path = apr_pstrdup(pool, value);
2462251881Speter    }
2463251881Speter
2464251881Speter  /* Get the predecessor ID. */
2465251881Speter  value = svn_hash_gets(headers, HEADER_PRED);
2466251881Speter  if (value)
2467251881Speter    noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value),
2468251881Speter                                                  pool);
2469251881Speter
2470251881Speter  /* Get the copyroot. */
2471251881Speter  value = svn_hash_gets(headers, HEADER_COPYROOT);
2472251881Speter  if (value == NULL)
2473251881Speter    {
2474251881Speter      noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path);
2475251881Speter      noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
2476251881Speter    }
2477251881Speter  else
2478251881Speter    {
2479251881Speter      char *str;
2480251881Speter
2481251881Speter      str = svn_cstring_tokenize(" ", &value);
2482251881Speter      if (str == NULL)
2483251881Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2484251881Speter                                 _("Malformed copyroot line in node-rev '%s'"),
2485251881Speter                                 noderev_id);
2486251881Speter
2487251881Speter      noderev->copyroot_rev = SVN_STR_TO_REV(str);
2488251881Speter
2489251881Speter      if (*value == '\0')
2490251881Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2491251881Speter                                 _("Malformed copyroot line in node-rev '%s'"),
2492251881Speter                                 noderev_id);
2493251881Speter      noderev->copyroot_path = apr_pstrdup(pool, value);
2494251881Speter    }
2495251881Speter
2496251881Speter  /* Get the copyfrom. */
2497251881Speter  value = svn_hash_gets(headers, HEADER_COPYFROM);
2498251881Speter  if (value == NULL)
2499251881Speter    {
2500251881Speter      noderev->copyfrom_path = NULL;
2501251881Speter      noderev->copyfrom_rev = SVN_INVALID_REVNUM;
2502251881Speter    }
2503251881Speter  else
2504251881Speter    {
2505251881Speter      char *str = svn_cstring_tokenize(" ", &value);
2506251881Speter      if (str == NULL)
2507251881Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2508251881Speter                                 _("Malformed copyfrom line in node-rev '%s'"),
2509251881Speter                                 noderev_id);
2510251881Speter
2511251881Speter      noderev->copyfrom_rev = SVN_STR_TO_REV(str);
2512251881Speter
2513251881Speter      if (*value == 0)
2514251881Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2515251881Speter                                 _("Malformed copyfrom line in node-rev '%s'"),
2516251881Speter                                 noderev_id);
2517251881Speter      noderev->copyfrom_path = apr_pstrdup(pool, value);
2518251881Speter    }
2519251881Speter
2520251881Speter  /* Get whether this is a fresh txn root. */
2521251881Speter  value = svn_hash_gets(headers, HEADER_FRESHTXNRT);
2522251881Speter  noderev->is_fresh_txn_root = (value != NULL);
2523251881Speter
2524251881Speter  /* Get the mergeinfo count. */
2525251881Speter  value = svn_hash_gets(headers, HEADER_MINFO_CNT);
2526251881Speter  if (value)
2527251881Speter    SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
2528251881Speter  else
2529251881Speter    noderev->mergeinfo_count = 0;
2530251881Speter
2531251881Speter  /* Get whether *this* node has mergeinfo. */
2532251881Speter  value = svn_hash_gets(headers, HEADER_MINFO_HERE);
2533251881Speter  noderev->has_mergeinfo = (value != NULL);
2534251881Speter
2535251881Speter  *noderev_p = noderev;
2536251881Speter
2537251881Speter  return SVN_NO_ERROR;
2538251881Speter}
2539251881Speter
2540251881Spetersvn_error_t *
2541251881Spetersvn_fs_fs__get_node_revision(node_revision_t **noderev_p,
2542251881Speter                             svn_fs_t *fs,
2543251881Speter                             const svn_fs_id_t *id,
2544251881Speter                             apr_pool_t *pool)
2545251881Speter{
2546251881Speter  svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool);
2547251881Speter  if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
2548251881Speter    {
2549251881Speter      svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool);
2550251881Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
2551251881Speter                               "Corrupt node-revision '%s'",
2552251881Speter                               id_string->data);
2553251881Speter    }
2554251881Speter  return svn_error_trace(err);
2555251881Speter}
2556251881Speter
2557251881Speter
2558251881Speter/* Return a formatted string, compatible with filesystem format FORMAT,
2559251881Speter   that represents the location of representation REP.  If
2560251881Speter   MUTABLE_REP_TRUNCATED is given, the rep is for props or dir contents,
2561251881Speter   and only a "-1" revision number will be given for a mutable rep.
2562251881Speter   If MAY_BE_CORRUPT is true, guard for NULL when constructing the string.
2563251881Speter   Perform the allocation from POOL.  */
2564251881Speterstatic const char *
2565251881Speterrepresentation_string(representation_t *rep,
2566251881Speter                      int format,
2567251881Speter                      svn_boolean_t mutable_rep_truncated,
2568251881Speter                      svn_boolean_t may_be_corrupt,
2569251881Speter                      apr_pool_t *pool)
2570251881Speter{
2571251881Speter  if (rep->txn_id && mutable_rep_truncated)
2572251881Speter    return "-1";
2573251881Speter
2574251881Speter#define DISPLAY_MAYBE_NULL_CHECKSUM(checksum)          \
2575251881Speter  ((!may_be_corrupt || (checksum) != NULL)     \
2576251881Speter   ? svn_checksum_to_cstring_display((checksum), pool) \
2577251881Speter   : "(null)")
2578251881Speter
2579251881Speter  if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || rep->sha1_checksum == NULL)
2580251881Speter    return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
2581251881Speter                        " %" SVN_FILESIZE_T_FMT " %s",
2582251881Speter                        rep->revision, rep->offset, rep->size,
2583251881Speter                        rep->expanded_size,
2584251881Speter                        DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum));
2585251881Speter
2586251881Speter  return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
2587251881Speter                      " %" SVN_FILESIZE_T_FMT " %s %s %s",
2588251881Speter                      rep->revision, rep->offset, rep->size,
2589251881Speter                      rep->expanded_size,
2590251881Speter                      DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum),
2591251881Speter                      DISPLAY_MAYBE_NULL_CHECKSUM(rep->sha1_checksum),
2592251881Speter                      rep->uniquifier);
2593251881Speter
2594251881Speter#undef DISPLAY_MAYBE_NULL_CHECKSUM
2595251881Speter
2596251881Speter}
2597251881Speter
2598251881Speter
2599251881Spetersvn_error_t *
2600251881Spetersvn_fs_fs__write_noderev(svn_stream_t *outfile,
2601251881Speter                         node_revision_t *noderev,
2602251881Speter                         int format,
2603251881Speter                         svn_boolean_t include_mergeinfo,
2604251881Speter                         apr_pool_t *pool)
2605251881Speter{
2606251881Speter  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n",
2607251881Speter                            svn_fs_fs__id_unparse(noderev->id,
2608251881Speter                                                  pool)->data));
2609251881Speter
2610251881Speter  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n",
2611251881Speter                            (noderev->kind == svn_node_file) ?
2612251881Speter                            KIND_FILE : KIND_DIR));
2613251881Speter
2614251881Speter  if (noderev->predecessor_id)
2615251881Speter    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n",
2616251881Speter                              svn_fs_fs__id_unparse(noderev->predecessor_id,
2617251881Speter                                                    pool)->data));
2618251881Speter
2619251881Speter  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n",
2620251881Speter                            noderev->predecessor_count));
2621251881Speter
2622251881Speter  if (noderev->data_rep)
2623251881Speter    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n",
2624251881Speter                              representation_string(noderev->data_rep,
2625251881Speter                                                    format,
2626251881Speter                                                    (noderev->kind
2627251881Speter                                                     == svn_node_dir),
2628251881Speter                                                    FALSE,
2629251881Speter                                                    pool)));
2630251881Speter
2631251881Speter  if (noderev->prop_rep)
2632251881Speter    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
2633251881Speter                              representation_string(noderev->prop_rep, format,
2634251881Speter                                                    TRUE, FALSE, pool)));
2635251881Speter
2636251881Speter  SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n",
2637251881Speter                            noderev->created_path));
2638251881Speter
2639251881Speter  if (noderev->copyfrom_path)
2640251881Speter    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld"
2641251881Speter                              " %s\n",
2642251881Speter                              noderev->copyfrom_rev,
2643251881Speter                              noderev->copyfrom_path));
2644251881Speter
2645251881Speter  if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
2646251881Speter      (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
2647251881Speter    SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld"
2648251881Speter                              " %s\n",
2649251881Speter                              noderev->copyroot_rev,
2650251881Speter                              noderev->copyroot_path));
2651251881Speter
2652251881Speter  if (noderev->is_fresh_txn_root)
2653251881Speter    SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
2654251881Speter
2655251881Speter  if (include_mergeinfo)
2656251881Speter    {
2657251881Speter      if (noderev->mergeinfo_count > 0)
2658251881Speter        SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %"
2659251881Speter                                  APR_INT64_T_FMT "\n",
2660251881Speter                                  noderev->mergeinfo_count));
2661251881Speter
2662251881Speter      if (noderev->has_mergeinfo)
2663251881Speter        SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
2664251881Speter    }
2665251881Speter
2666251881Speter  return svn_stream_puts(outfile, "\n");
2667251881Speter}
2668251881Speter
2669251881Spetersvn_error_t *
2670251881Spetersvn_fs_fs__put_node_revision(svn_fs_t *fs,
2671251881Speter                             const svn_fs_id_t *id,
2672251881Speter                             node_revision_t *noderev,
2673251881Speter                             svn_boolean_t fresh_txn_root,
2674251881Speter                             apr_pool_t *pool)
2675251881Speter{
2676251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
2677251881Speter  apr_file_t *noderev_file;
2678251881Speter  const char *txn_id = svn_fs_fs__id_txn_id(id);
2679251881Speter
2680251881Speter  noderev->is_fresh_txn_root = fresh_txn_root;
2681251881Speter
2682251881Speter  if (! txn_id)
2683251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2684251881Speter                             _("Attempted to write to non-transaction '%s'"),
2685251881Speter                             svn_fs_fs__id_unparse(id, pool)->data);
2686251881Speter
2687251881Speter  SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool),
2688251881Speter                           APR_WRITE | APR_CREATE | APR_TRUNCATE
2689251881Speter                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
2690251881Speter
2691251881Speter  SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
2692251881Speter                                                            pool),
2693251881Speter                                   noderev, ffd->format,
2694251881Speter                                   svn_fs_fs__fs_supports_mergeinfo(fs),
2695251881Speter                                   pool));
2696251881Speter
2697251881Speter  SVN_ERR(svn_io_file_close(noderev_file, pool));
2698251881Speter
2699251881Speter  return SVN_NO_ERROR;
2700251881Speter}
2701251881Speter
2702251881Speter/* For the in-transaction NODEREV within FS, write the sha1->rep mapping
2703251881Speter * file in the respective transaction, if rep sharing has been enabled etc.
2704251881Speter * Use POOL for temporary allocations.
2705251881Speter */
2706251881Speterstatic svn_error_t *
2707251881Speterstore_sha1_rep_mapping(svn_fs_t *fs,
2708251881Speter                       node_revision_t *noderev,
2709251881Speter                       apr_pool_t *pool)
2710251881Speter{
2711251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
2712251881Speter
2713251881Speter  /* if rep sharing has been enabled and the noderev has a data rep and
2714251881Speter   * its SHA-1 is known, store the rep struct under its SHA1. */
2715251881Speter  if (   ffd->rep_sharing_allowed
2716251881Speter      && noderev->data_rep
2717251881Speter      && noderev->data_rep->sha1_checksum)
2718251881Speter    {
2719251881Speter      apr_file_t *rep_file;
2720251881Speter      const char *file_name = path_txn_sha1(fs,
2721251881Speter                                            svn_fs_fs__id_txn_id(noderev->id),
2722251881Speter                                            noderev->data_rep->sha1_checksum,
2723251881Speter                                            pool);
2724251881Speter      const char *rep_string = representation_string(noderev->data_rep,
2725251881Speter                                                     ffd->format,
2726251881Speter                                                     (noderev->kind
2727251881Speter                                                      == svn_node_dir),
2728251881Speter                                                     FALSE,
2729251881Speter                                                     pool);
2730251881Speter      SVN_ERR(svn_io_file_open(&rep_file, file_name,
2731251881Speter                               APR_WRITE | APR_CREATE | APR_TRUNCATE
2732251881Speter                               | APR_BUFFERED, APR_OS_DEFAULT, pool));
2733251881Speter
2734251881Speter      SVN_ERR(svn_io_file_write_full(rep_file, rep_string,
2735251881Speter                                     strlen(rep_string), NULL, pool));
2736251881Speter
2737251881Speter      SVN_ERR(svn_io_file_close(rep_file, pool));
2738251881Speter    }
2739251881Speter
2740251881Speter  return SVN_NO_ERROR;
2741251881Speter}
2742251881Speter
2743251881Speter
2744251881Speter/* This structure is used to hold the information associated with a
2745251881Speter   REP line. */
2746251881Speterstruct rep_args
2747251881Speter{
2748251881Speter  svn_boolean_t is_delta;
2749251881Speter  svn_boolean_t is_delta_vs_empty;
2750251881Speter
2751251881Speter  svn_revnum_t base_revision;
2752251881Speter  apr_off_t base_offset;
2753251881Speter  svn_filesize_t base_length;
2754251881Speter};
2755251881Speter
2756251881Speter/* Read the next line from file FILE and parse it as a text
2757251881Speter   representation entry.  Return the parsed entry in *REP_ARGS_P.
2758251881Speter   Perform all allocations in POOL. */
2759251881Speterstatic svn_error_t *
2760251881Speterread_rep_line(struct rep_args **rep_args_p,
2761251881Speter              apr_file_t *file,
2762251881Speter              apr_pool_t *pool)
2763251881Speter{
2764251881Speter  char buffer[160];
2765251881Speter  apr_size_t limit;
2766251881Speter  struct rep_args *rep_args;
2767251881Speter  char *str, *last_str = buffer;
2768251881Speter  apr_int64_t val;
2769251881Speter
2770251881Speter  limit = sizeof(buffer);
2771251881Speter  SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool));
2772251881Speter
2773251881Speter  rep_args = apr_pcalloc(pool, sizeof(*rep_args));
2774251881Speter  rep_args->is_delta = FALSE;
2775251881Speter
2776251881Speter  if (strcmp(buffer, REP_PLAIN) == 0)
2777251881Speter    {
2778251881Speter      *rep_args_p = rep_args;
2779251881Speter      return SVN_NO_ERROR;
2780251881Speter    }
2781251881Speter
2782251881Speter  if (strcmp(buffer, REP_DELTA) == 0)
2783251881Speter    {
2784251881Speter      /* This is a delta against the empty stream. */
2785251881Speter      rep_args->is_delta = TRUE;
2786251881Speter      rep_args->is_delta_vs_empty = TRUE;
2787251881Speter      *rep_args_p = rep_args;
2788251881Speter      return SVN_NO_ERROR;
2789251881Speter    }
2790251881Speter
2791251881Speter  rep_args->is_delta = TRUE;
2792251881Speter  rep_args->is_delta_vs_empty = FALSE;
2793251881Speter
2794251881Speter  /* We have hopefully a DELTA vs. a non-empty base revision. */
2795251881Speter  str = svn_cstring_tokenize(" ", &last_str);
2796251881Speter  if (! str || (strcmp(str, REP_DELTA) != 0))
2797251881Speter    goto error;
2798251881Speter
2799251881Speter  str = svn_cstring_tokenize(" ", &last_str);
2800251881Speter  if (! str)
2801251881Speter    goto error;
2802251881Speter  rep_args->base_revision = SVN_STR_TO_REV(str);
2803251881Speter
2804251881Speter  str = svn_cstring_tokenize(" ", &last_str);
2805251881Speter  if (! str)
2806251881Speter    goto error;
2807251881Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
2808251881Speter  rep_args->base_offset = (apr_off_t)val;
2809251881Speter
2810251881Speter  str = svn_cstring_tokenize(" ", &last_str);
2811251881Speter  if (! str)
2812251881Speter    goto error;
2813251881Speter  SVN_ERR(svn_cstring_atoi64(&val, str));
2814251881Speter  rep_args->base_length = (svn_filesize_t)val;
2815251881Speter
2816251881Speter  *rep_args_p = rep_args;
2817251881Speter  return SVN_NO_ERROR;
2818251881Speter
2819251881Speter error:
2820251881Speter  return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2821251881Speter                           _("Malformed representation header at %s"),
2822251881Speter                           path_and_offset_of(file, pool));
2823251881Speter}
2824251881Speter
2825251881Speter/* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID
2826251881Speter   of the header located at OFFSET and store it in *ID_P.  Allocate
2827251881Speter   temporary variables from POOL. */
2828251881Speterstatic svn_error_t *
2829251881Speterget_fs_id_at_offset(svn_fs_id_t **id_p,
2830251881Speter                    apr_file_t *rev_file,
2831251881Speter                    svn_fs_t *fs,
2832251881Speter                    svn_revnum_t rev,
2833251881Speter                    apr_off_t offset,
2834251881Speter                    apr_pool_t *pool)
2835251881Speter{
2836251881Speter  svn_fs_id_t *id;
2837251881Speter  apr_hash_t *headers;
2838251881Speter  const char *node_id_str;
2839251881Speter
2840251881Speter  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2841251881Speter
2842251881Speter  SVN_ERR(read_header_block(&headers,
2843251881Speter                            svn_stream_from_aprfile2(rev_file, TRUE, pool),
2844251881Speter                            pool));
2845251881Speter
2846251881Speter  /* In error messages, the offset is relative to the pack file,
2847251881Speter     not to the rev file. */
2848251881Speter
2849251881Speter  node_id_str = svn_hash_gets(headers, HEADER_ID);
2850251881Speter
2851251881Speter  if (node_id_str == NULL)
2852251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2853251881Speter                             _("Missing node-id in node-rev at r%ld "
2854251881Speter                             "(offset %s)"),
2855251881Speter                             rev,
2856251881Speter                             apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
2857251881Speter
2858251881Speter  id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
2859251881Speter
2860251881Speter  if (id == NULL)
2861251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2862251881Speter                             _("Corrupt node-id '%s' in node-rev at r%ld "
2863251881Speter                               "(offset %s)"),
2864251881Speter                             node_id_str, rev,
2865251881Speter                             apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
2866251881Speter
2867251881Speter  *id_p = id;
2868251881Speter
2869251881Speter  /* ### assert that the txn_id is REV/OFFSET ? */
2870251881Speter
2871251881Speter  return SVN_NO_ERROR;
2872251881Speter}
2873251881Speter
2874251881Speter
2875251881Speter/* Given an open revision file REV_FILE in FS for REV, locate the trailer that
2876251881Speter   specifies the offset to the root node-id and to the changed path
2877251881Speter   information.  Store the root node offset in *ROOT_OFFSET and the
2878251881Speter   changed path offset in *CHANGES_OFFSET.  If either of these
2879251881Speter   pointers is NULL, do nothing with it.
2880251881Speter
2881251881Speter   If PACKED is true, REV_FILE should be a packed shard file.
2882251881Speter   ### There is currently no such parameter.  This function assumes that
2883251881Speter       is_packed_rev(FS, REV) will indicate whether REV_FILE is a packed
2884251881Speter       file.  Therefore FS->fsap_data->min_unpacked_rev must not have been
2885251881Speter       refreshed since REV_FILE was opened if there is a possibility that
2886251881Speter       revision REV may have become packed since then.
2887251881Speter       TODO: Take an IS_PACKED parameter instead, in order to remove this
2888251881Speter       requirement.
2889251881Speter
2890251881Speter   Allocate temporary variables from POOL. */
2891251881Speterstatic svn_error_t *
2892251881Speterget_root_changes_offset(apr_off_t *root_offset,
2893251881Speter                        apr_off_t *changes_offset,
2894251881Speter                        apr_file_t *rev_file,
2895251881Speter                        svn_fs_t *fs,
2896251881Speter                        svn_revnum_t rev,
2897251881Speter                        apr_pool_t *pool)
2898251881Speter{
2899251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
2900251881Speter  apr_off_t offset;
2901251881Speter  apr_off_t rev_offset;
2902251881Speter  char buf[64];
2903251881Speter  int i, num_bytes;
2904251881Speter  const char *str;
2905251881Speter  apr_size_t len;
2906251881Speter  apr_seek_where_t seek_relative;
2907251881Speter
2908251881Speter  /* Determine where to seek to in the file.
2909251881Speter
2910251881Speter     If we've got a pack file, we want to seek to the end of the desired
2911251881Speter     revision.  But we don't track that, so we seek to the beginning of the
2912251881Speter     next revision.
2913251881Speter
2914251881Speter     Unless the next revision is in a different file, in which case, we can
2915251881Speter     just seek to the end of the pack file -- just like we do in the
2916251881Speter     non-packed case. */
2917251881Speter  if (is_packed_rev(fs, rev) && ((rev + 1) % ffd->max_files_per_dir != 0))
2918251881Speter    {
2919251881Speter      SVN_ERR(get_packed_offset(&offset, fs, rev + 1, pool));
2920251881Speter      seek_relative = APR_SET;
2921251881Speter    }
2922251881Speter  else
2923251881Speter    {
2924251881Speter      seek_relative = APR_END;
2925251881Speter      offset = 0;
2926251881Speter    }
2927251881Speter
2928251881Speter  /* Offset of the revision from the start of the pack file, if applicable. */
2929251881Speter  if (is_packed_rev(fs, rev))
2930251881Speter    SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
2931251881Speter  else
2932251881Speter    rev_offset = 0;
2933251881Speter
2934251881Speter  /* We will assume that the last line containing the two offsets
2935251881Speter     will never be longer than 64 characters. */
2936251881Speter  SVN_ERR(svn_io_file_seek(rev_file, seek_relative, &offset, pool));
2937251881Speter
2938251881Speter  offset -= sizeof(buf);
2939251881Speter  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2940251881Speter
2941251881Speter  /* Read in this last block, from which we will identify the last line. */
2942251881Speter  len = sizeof(buf);
2943251881Speter  SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
2944251881Speter
2945251881Speter  /* This cast should be safe since the maximum amount read, 64, will
2946251881Speter     never be bigger than the size of an int. */
2947251881Speter  num_bytes = (int) len;
2948251881Speter
2949251881Speter  /* The last byte should be a newline. */
2950251881Speter  if (buf[num_bytes - 1] != '\n')
2951251881Speter    {
2952251881Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2953251881Speter                               _("Revision file (r%ld) lacks trailing newline"),
2954251881Speter                               rev);
2955251881Speter    }
2956251881Speter
2957251881Speter  /* Look for the next previous newline. */
2958251881Speter  for (i = num_bytes - 2; i >= 0; i--)
2959251881Speter    {
2960251881Speter      if (buf[i] == '\n')
2961251881Speter        break;
2962251881Speter    }
2963251881Speter
2964251881Speter  if (i < 0)
2965251881Speter    {
2966251881Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2967251881Speter                               _("Final line in revision file (r%ld) longer "
2968251881Speter                                 "than 64 characters"),
2969251881Speter                               rev);
2970251881Speter    }
2971251881Speter
2972251881Speter  i++;
2973251881Speter  str = &buf[i];
2974251881Speter
2975251881Speter  /* find the next space */
2976251881Speter  for ( ; i < (num_bytes - 2) ; i++)
2977251881Speter    if (buf[i] == ' ')
2978251881Speter      break;
2979251881Speter
2980251881Speter  if (i == (num_bytes - 2))
2981251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2982251881Speter                             _("Final line in revision file r%ld missing space"),
2983251881Speter                             rev);
2984251881Speter
2985251881Speter  if (root_offset)
2986251881Speter    {
2987251881Speter      apr_int64_t val;
2988251881Speter
2989251881Speter      buf[i] = '\0';
2990251881Speter      SVN_ERR(svn_cstring_atoi64(&val, str));
2991251881Speter      *root_offset = rev_offset + (apr_off_t)val;
2992251881Speter    }
2993251881Speter
2994251881Speter  i++;
2995251881Speter  str = &buf[i];
2996251881Speter
2997251881Speter  /* find the next newline */
2998251881Speter  for ( ; i < num_bytes; i++)
2999251881Speter    if (buf[i] == '\n')
3000251881Speter      break;
3001251881Speter
3002251881Speter  if (changes_offset)
3003251881Speter    {
3004251881Speter      apr_int64_t val;
3005251881Speter
3006251881Speter      buf[i] = '\0';
3007251881Speter      SVN_ERR(svn_cstring_atoi64(&val, str));
3008251881Speter      *changes_offset = rev_offset + (apr_off_t)val;
3009251881Speter    }
3010251881Speter
3011251881Speter  return SVN_NO_ERROR;
3012251881Speter}
3013251881Speter
3014251881Speter/* Move a file into place from OLD_FILENAME in the transactions
3015251881Speter   directory to its final location NEW_FILENAME in the repository.  On
3016251881Speter   Unix, match the permissions of the new file to the permissions of
3017251881Speter   PERMS_REFERENCE.  Temporary allocations are from POOL.
3018251881Speter
3019251881Speter   This function almost duplicates svn_io_file_move(), but it tries to
3020251881Speter   guarantee a flush. */
3021251881Speterstatic svn_error_t *
3022251881Spetermove_into_place(const char *old_filename,
3023251881Speter                const char *new_filename,
3024251881Speter                const char *perms_reference,
3025251881Speter                apr_pool_t *pool)
3026251881Speter{
3027251881Speter  svn_error_t *err;
3028251881Speter
3029251881Speter  SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool));
3030251881Speter
3031251881Speter  /* Move the file into place. */
3032251881Speter  err = svn_io_file_rename(old_filename, new_filename, pool);
3033251881Speter  if (err && APR_STATUS_IS_EXDEV(err->apr_err))
3034251881Speter    {
3035251881Speter      apr_file_t *file;
3036251881Speter
3037251881Speter      /* Can't rename across devices; fall back to copying. */
3038251881Speter      svn_error_clear(err);
3039251881Speter      err = SVN_NO_ERROR;
3040251881Speter      SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
3041251881Speter
3042251881Speter      /* Flush the target of the copy to disk. */
3043251881Speter      SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
3044251881Speter                               APR_OS_DEFAULT, pool));
3045251881Speter      /* ### BH: Does this really guarantee a flush of the data written
3046251881Speter         ### via a completely different handle on all operating systems?
3047251881Speter         ###
3048251881Speter         ### Maybe we should perform the copy ourselves instead of making
3049251881Speter         ### apr do that and flush the real handle? */
3050251881Speter      SVN_ERR(svn_io_file_flush_to_disk(file, pool));
3051251881Speter      SVN_ERR(svn_io_file_close(file, pool));
3052251881Speter    }
3053251881Speter  if (err)
3054251881Speter    return svn_error_trace(err);
3055251881Speter
3056251881Speter#ifdef __linux__
3057251881Speter  {
3058251881Speter    /* Linux has the unusual feature that fsync() on a file is not
3059251881Speter       enough to ensure that a file's directory entries have been
3060251881Speter       flushed to disk; you have to fsync the directory as well.
3061251881Speter       On other operating systems, we'd only be asking for trouble
3062251881Speter       by trying to open and fsync a directory. */
3063251881Speter    const char *dirname;
3064251881Speter    apr_file_t *file;
3065251881Speter
3066251881Speter    dirname = svn_dirent_dirname(new_filename, pool);
3067251881Speter    SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
3068251881Speter                             pool));
3069251881Speter    SVN_ERR(svn_io_file_flush_to_disk(file, pool));
3070251881Speter    SVN_ERR(svn_io_file_close(file, pool));
3071251881Speter  }
3072251881Speter#endif
3073251881Speter
3074251881Speter  return SVN_NO_ERROR;
3075251881Speter}
3076251881Speter
3077251881Spetersvn_error_t *
3078251881Spetersvn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
3079251881Speter                        svn_fs_t *fs,
3080251881Speter                        svn_revnum_t rev,
3081251881Speter                        apr_pool_t *pool)
3082251881Speter{
3083251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
3084251881Speter  apr_file_t *revision_file;
3085251881Speter  apr_off_t root_offset;
3086251881Speter  svn_fs_id_t *root_id = NULL;
3087251881Speter  svn_boolean_t is_cached;
3088251881Speter
3089251881Speter  SVN_ERR(ensure_revision_exists(fs, rev, pool));
3090251881Speter
3091251881Speter  SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached,
3092251881Speter                         ffd->rev_root_id_cache, &rev, pool));
3093251881Speter  if (is_cached)
3094251881Speter    return SVN_NO_ERROR;
3095251881Speter
3096251881Speter  SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
3097251881Speter  SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, fs, rev,
3098251881Speter                                  pool));
3099251881Speter
3100251881Speter  SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev,
3101251881Speter                              root_offset, pool));
3102251881Speter
3103251881Speter  SVN_ERR(svn_io_file_close(revision_file, pool));
3104251881Speter
3105251881Speter  SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, pool));
3106251881Speter
3107251881Speter  *root_id_p = root_id;
3108251881Speter
3109251881Speter  return SVN_NO_ERROR;
3110251881Speter}
3111251881Speter
3112251881Speter/* Revprop caching management.
3113251881Speter *
3114251881Speter * Mechanism:
3115251881Speter * ----------
3116251881Speter *
3117251881Speter * Revprop caching needs to be activated and will be deactivated for the
3118251881Speter * respective FS instance if the necessary infrastructure could not be
3119251881Speter * initialized.  In deactivated mode, there is almost no runtime overhead
3120251881Speter * associated with revprop caching.  As long as no revprops are being read
3121251881Speter * or changed, revprop caching imposes no overhead.
3122251881Speter *
3123251881Speter * When activated, we cache revprops using (revision, generation) pairs
3124251881Speter * as keys with the generation being incremented upon every revprop change.
3125251881Speter * Since the cache is process-local, the generation needs to be tracked
3126251881Speter * for at least as long as the process lives but may be reset afterwards.
3127251881Speter *
3128251881Speter * To track the revprop generation, we use two-layer approach. On the lower
3129251881Speter * level, we use named atomics to have a system-wide consistent value for
3130251881Speter * the current revprop generation.  However, those named atomics will only
3131251881Speter * remain valid for as long as at least one process / thread in the system
3132251881Speter * accesses revprops in the respective repository.  The underlying shared
3133251881Speter * memory gets cleaned up afterwards.
3134251881Speter *
3135251881Speter * On the second level, we will use a persistent file to track the latest
3136251881Speter * revprop generation.  It will be written upon each revprop change but
3137251881Speter * only be read if we are the first process to initialize the named atomics
3138251881Speter * with that value.
3139251881Speter *
3140251881Speter * The overhead for the second and following accesses to revprops is
3141251881Speter * almost zero on most systems.
3142251881Speter *
3143251881Speter *
3144251881Speter * Tech aspects:
3145251881Speter * -------------
3146251881Speter *
3147251881Speter * A problem is that we need to provide a globally available file name to
3148251881Speter * back the SHM implementation on OSes that need it.  We can only assume
3149251881Speter * write access to some file within the respective repositories.  Because
3150251881Speter * a given server process may access thousands of repositories during its
3151251881Speter * lifetime, keeping the SHM data alive for all of them is also not an
3152251881Speter * option.
3153251881Speter *
3154251881Speter * So, we store the new revprop generation on disk as part of each
3155251881Speter * setrevprop call, i.e. this write will be serialized and the write order
3156251881Speter * be guaranteed by the repository write lock.
3157251881Speter *
3158251881Speter * The only racy situation occurs when the data is being read again by two
3159251881Speter * processes concurrently but in that situation, the first process to
3160251881Speter * finish that procedure is guaranteed to be the only one that initializes
3161251881Speter * the SHM data.  Since even writers will first go through that
3162251881Speter * initialization phase, they will never operate on stale data.
3163251881Speter */
3164251881Speter
3165251881Speter/* Read revprop generation as stored on disk for repository FS. The result
3166251881Speter * is returned in *CURRENT. Default to 2 if no such file is available.
3167251881Speter */
3168251881Speterstatic svn_error_t *
3169251881Speterread_revprop_generation_file(apr_int64_t *current,
3170251881Speter                             svn_fs_t *fs,
3171251881Speter                             apr_pool_t *pool)
3172251881Speter{
3173251881Speter  svn_error_t *err;
3174251881Speter  apr_file_t *file;
3175251881Speter  char buf[80];
3176251881Speter  apr_size_t len;
3177251881Speter  const char *path = path_revprop_generation(fs, pool);
3178251881Speter
3179251881Speter  err = svn_io_file_open(&file, path,
3180251881Speter                         APR_READ | APR_BUFFERED,
3181251881Speter                         APR_OS_DEFAULT, pool);
3182251881Speter  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3183251881Speter    {
3184251881Speter      svn_error_clear(err);
3185251881Speter      *current = 2;
3186251881Speter
3187251881Speter      return SVN_NO_ERROR;
3188251881Speter    }
3189251881Speter  SVN_ERR(err);
3190251881Speter
3191251881Speter  len = sizeof(buf);
3192251881Speter  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
3193251881Speter
3194251881Speter  /* Check that the first line contains only digits. */
3195251881Speter  SVN_ERR(check_file_buffer_numeric(buf, 0, path,
3196251881Speter                                    "Revprop Generation", pool));
3197251881Speter  SVN_ERR(svn_cstring_atoi64(current, buf));
3198251881Speter
3199251881Speter  return svn_io_file_close(file, pool);
3200251881Speter}
3201251881Speter
3202251881Speter/* Write the CURRENT revprop generation to disk for repository FS.
3203251881Speter */
3204251881Speterstatic svn_error_t *
3205251881Speterwrite_revprop_generation_file(svn_fs_t *fs,
3206251881Speter                              apr_int64_t current,
3207251881Speter                              apr_pool_t *pool)
3208251881Speter{
3209251881Speter  apr_file_t *file;
3210251881Speter  const char *tmp_path;
3211251881Speter
3212251881Speter  char buf[SVN_INT64_BUFFER_SIZE];
3213251881Speter  apr_size_t len = svn__i64toa(buf, current);
3214251881Speter  buf[len] = '\n';
3215251881Speter
3216251881Speter  SVN_ERR(svn_io_open_unique_file3(&file, &tmp_path, fs->path,
3217251881Speter                                   svn_io_file_del_none, pool, pool));
3218251881Speter  SVN_ERR(svn_io_file_write_full(file, buf, len + 1, NULL, pool));
3219251881Speter  SVN_ERR(svn_io_file_close(file, pool));
3220251881Speter
3221251881Speter  return move_into_place(tmp_path, path_revprop_generation(fs, pool),
3222251881Speter                         tmp_path, pool);
3223251881Speter}
3224251881Speter
3225251881Speter/* Make sure the revprop_namespace member in FS is set. */
3226251881Speterstatic svn_error_t *
3227251881Speterensure_revprop_namespace(svn_fs_t *fs)
3228251881Speter{
3229251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
3230251881Speter
3231251881Speter  return ffd->revprop_namespace == NULL
3232251881Speter    ? svn_atomic_namespace__create(&ffd->revprop_namespace,
3233251881Speter                                   svn_dirent_join(fs->path,
3234251881Speter                                                   ATOMIC_REVPROP_NAMESPACE,
3235251881Speter                                                   fs->pool),
3236251881Speter                                   fs->pool)
3237251881Speter    : SVN_NO_ERROR;
3238251881Speter}
3239251881Speter
3240251881Speter/* Make sure the revprop_namespace member in FS is set. */
3241251881Speterstatic svn_error_t *
3242251881Spetercleanup_revprop_namespace(svn_fs_t *fs)
3243251881Speter{
3244251881Speter  const char *name = svn_dirent_join(fs->path,
3245251881Speter                                     ATOMIC_REVPROP_NAMESPACE,
3246251881Speter                                     fs->pool);
3247251881Speter  return svn_error_trace(svn_atomic_namespace__cleanup(name, fs->pool));
3248251881Speter}
3249251881Speter
3250251881Speter/* Make sure the revprop_generation member in FS is set and, if necessary,
3251251881Speter * initialized with the latest value stored on disk.
3252251881Speter */
3253251881Speterstatic svn_error_t *
3254251881Speterensure_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
3255251881Speter{
3256251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
3257251881Speter
3258251881Speter  SVN_ERR(ensure_revprop_namespace(fs));
3259251881Speter  if (ffd->revprop_generation == NULL)
3260251881Speter    {
3261251881Speter      apr_int64_t current = 0;
3262251881Speter
3263251881Speter      SVN_ERR(svn_named_atomic__get(&ffd->revprop_generation,
3264251881Speter                                    ffd->revprop_namespace,
3265251881Speter                                    ATOMIC_REVPROP_GENERATION,
3266251881Speter                                    TRUE));
3267251881Speter
3268251881Speter      /* If the generation is at 0, we just created a new namespace
3269251881Speter       * (it would be at least 2 otherwise). Read the latest generation
3270251881Speter       * from disk and if we are the first one to initialize the atomic
3271251881Speter       * (i.e. is still 0), set it to the value just gotten.
3272251881Speter       */
3273251881Speter      SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
3274251881Speter      if (current == 0)
3275251881Speter        {
3276251881Speter          SVN_ERR(read_revprop_generation_file(&current, fs, pool));
3277251881Speter          SVN_ERR(svn_named_atomic__cmpxchg(NULL, current, 0,
3278251881Speter                                            ffd->revprop_generation));
3279251881Speter        }
3280251881Speter    }
3281251881Speter
3282251881Speter  return SVN_NO_ERROR;
3283251881Speter}
3284251881Speter
3285251881Speter/* Make sure the revprop_timeout member in FS is set. */
3286251881Speterstatic svn_error_t *
3287251881Speterensure_revprop_timeout(svn_fs_t *fs)
3288251881Speter{
3289251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
3290251881Speter
3291251881Speter  SVN_ERR(ensure_revprop_namespace(fs));
3292251881Speter  return ffd->revprop_timeout == NULL
3293251881Speter    ? svn_named_atomic__get(&ffd->revprop_timeout,
3294251881Speter                            ffd->revprop_namespace,
3295251881Speter                            ATOMIC_REVPROP_TIMEOUT,
3296251881Speter                            TRUE)
3297251881Speter    : SVN_NO_ERROR;
3298251881Speter}
3299251881Speter
3300251881Speter/* Create an error object with the given MESSAGE and pass it to the
3301251881Speter   WARNING member of FS. */
3302251881Speterstatic void
3303251881Speterlog_revprop_cache_init_warning(svn_fs_t *fs,
3304251881Speter                               svn_error_t *underlying_err,
3305251881Speter                               const char *message)
3306251881Speter{
3307251881Speter  svn_error_t *err = svn_error_createf(SVN_ERR_FS_REVPROP_CACHE_INIT_FAILURE,
3308251881Speter                                       underlying_err,
3309251881Speter                                       message, fs->path);
3310251881Speter
3311251881Speter  if (fs->warning)
3312251881Speter    (fs->warning)(fs->warning_baton, err);
3313251881Speter
3314251881Speter  svn_error_clear(err);
3315251881Speter}
3316251881Speter
3317251881Speter/* Test whether revprop cache and necessary infrastructure are
3318251881Speter   available in FS. */
3319251881Speterstatic svn_boolean_t
3320251881Speterhas_revprop_cache(svn_fs_t *fs, apr_pool_t *pool)
3321251881Speter{
3322251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
3323251881Speter  svn_error_t *error;
3324251881Speter
3325251881Speter  /* is the cache (still) enabled? */
3326251881Speter  if (ffd->revprop_cache == NULL)
3327251881Speter    return FALSE;
3328251881Speter
3329251881Speter  /* is it efficient? */
3330251881Speter  if (!svn_named_atomic__is_efficient())
3331251881Speter    {
3332251881Speter      /* access to it would be quite slow
3333251881Speter       * -> disable the revprop cache for good
3334251881Speter       */
3335251881Speter      ffd->revprop_cache = NULL;
3336251881Speter      log_revprop_cache_init_warning(fs, NULL,
3337251881Speter                                     "Revprop caching for '%s' disabled"
3338251881Speter                                     " because it would be inefficient.");
3339251881Speter
3340251881Speter      return FALSE;
3341251881Speter    }
3342251881Speter
3343251881Speter  /* try to access our SHM-backed infrastructure */
3344251881Speter  error = ensure_revprop_generation(fs, pool);
3345251881Speter  if (error)
3346251881Speter    {
3347251881Speter      /* failure -> disable revprop cache for good */
3348251881Speter
3349251881Speter      ffd->revprop_cache = NULL;
3350251881Speter      log_revprop_cache_init_warning(fs, error,
3351251881Speter                                     "Revprop caching for '%s' disabled "
3352251881Speter                                     "because SHM infrastructure for revprop "
3353251881Speter                                     "caching failed to initialize.");
3354251881Speter
3355251881Speter      return FALSE;
3356251881Speter    }
3357251881Speter
3358251881Speter  return TRUE;
3359251881Speter}
3360251881Speter
3361251881Speter/* Baton structure for revprop_generation_fixup. */
3362251881Spetertypedef struct revprop_generation_fixup_t
3363251881Speter{
3364251881Speter  /* revprop generation to read */
3365251881Speter  apr_int64_t *generation;
3366251881Speter
3367251881Speter  /* containing the revprop_generation member to query */
3368251881Speter  fs_fs_data_t *ffd;
3369251881Speter} revprop_generation_upgrade_t;
3370251881Speter
3371251881Speter/* If the revprop generation has an odd value, it means the original writer
3372251881Speter   of the revprop got killed. We don't know whether that process as able
3373251881Speter   to change the revprop data but we assume that it was. Therefore, we
3374251881Speter   increase the generation in that case to basically invalidate everyones
3375251881Speter   cache content.
3376251881Speter   Execute this onlx while holding the write lock to the repo in baton->FFD.
3377251881Speter */
3378251881Speterstatic svn_error_t *
3379251881Speterrevprop_generation_fixup(void *void_baton,
3380251881Speter                         apr_pool_t *pool)
3381251881Speter{
3382251881Speter  revprop_generation_upgrade_t *baton = void_baton;
3383251881Speter  assert(baton->ffd->has_write_lock);
3384251881Speter
3385251881Speter  /* Maybe, either the original revprop writer or some other reader has
3386251881Speter     already corrected / bumped the revprop generation.  Thus, we need
3387251881Speter     to read it again. */
3388251881Speter  SVN_ERR(svn_named_atomic__read(baton->generation,
3389251881Speter                                 baton->ffd->revprop_generation));
3390251881Speter
3391251881Speter  /* Cause everyone to re-read revprops upon their next access, if the
3392251881Speter     last revprop write did not complete properly. */
3393251881Speter  while (*baton->generation % 2)
3394251881Speter    SVN_ERR(svn_named_atomic__add(baton->generation,
3395251881Speter                                  1,
3396251881Speter                                  baton->ffd->revprop_generation));
3397251881Speter
3398251881Speter  return SVN_NO_ERROR;
3399251881Speter}
3400251881Speter
3401251881Speter/* Read the current revprop generation and return it in *GENERATION.
3402251881Speter   Also, detect aborted / crashed writers and recover from that.
3403251881Speter   Use the access object in FS to set the shared mem values. */
3404251881Speterstatic svn_error_t *
3405251881Speterread_revprop_generation(apr_int64_t *generation,
3406251881Speter                        svn_fs_t *fs,
3407251881Speter                        apr_pool_t *pool)
3408251881Speter{
3409251881Speter  apr_int64_t current = 0;
3410251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
3411251881Speter
3412251881Speter  /* read the current revprop generation number */
3413251881Speter  SVN_ERR(ensure_revprop_generation(fs, pool));
3414251881Speter  SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
3415251881Speter
3416251881Speter  /* is an unfinished revprop write under the way? */
3417251881Speter  if (current % 2)
3418251881Speter    {
3419251881Speter      apr_int64_t timeout = 0;
3420251881Speter
3421251881Speter      /* read timeout for the write operation */
3422251881Speter      SVN_ERR(ensure_revprop_timeout(fs));
3423251881Speter      SVN_ERR(svn_named_atomic__read(&timeout, ffd->revprop_timeout));
3424251881Speter
3425251881Speter      /* has the writer process been aborted,
3426251881Speter       * i.e. has the timeout been reached?
3427251881Speter       */
3428251881Speter      if (apr_time_now() > timeout)
3429251881Speter        {
3430251881Speter          revprop_generation_upgrade_t baton;
3431251881Speter          baton.generation = &current;
3432251881Speter          baton.ffd = ffd;
3433251881Speter
3434251881Speter          /* Ensure that the original writer process no longer exists by
3435251881Speter           * acquiring the write lock to this repository.  Then, fix up
3436251881Speter           * the revprop generation.
3437251881Speter           */
3438251881Speter          if (ffd->has_write_lock)
3439251881Speter            SVN_ERR(revprop_generation_fixup(&baton, pool));
3440251881Speter          else
3441251881Speter            SVN_ERR(svn_fs_fs__with_write_lock(fs, revprop_generation_fixup,
3442251881Speter                                               &baton, pool));
3443251881Speter        }
3444251881Speter    }
3445251881Speter
3446251881Speter  /* return the value we just got */
3447251881Speter  *generation = current;
3448251881Speter  return SVN_NO_ERROR;
3449251881Speter}
3450251881Speter
3451251881Speter/* Set the revprop generation to the next odd number to indicate that
3452251881Speter   there is a revprop write process under way. If that times out,
3453251881Speter   readers shall recover from that state & re-read revprops.
3454251881Speter   Use the access object in FS to set the shared mem value. */
3455251881Speterstatic svn_error_t *
3456251881Speterbegin_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
3457251881Speter{
3458251881Speter  apr_int64_t current;
3459251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
3460251881Speter
3461251881Speter  /* set the timeout for the write operation */
3462251881Speter  SVN_ERR(ensure_revprop_timeout(fs));
3463251881Speter  SVN_ERR(svn_named_atomic__write(NULL,
3464251881Speter                                  apr_time_now() + REVPROP_CHANGE_TIMEOUT,
3465251881Speter                                  ffd->revprop_timeout));
3466251881Speter
3467251881Speter  /* set the revprop generation to an odd value to indicate
3468251881Speter   * that a write is in progress
3469251881Speter   */
3470251881Speter  SVN_ERR(ensure_revprop_generation(fs, pool));
3471251881Speter  do
3472251881Speter    {
3473251881Speter      SVN_ERR(svn_named_atomic__add(&current,
3474251881Speter                                    1,
3475251881Speter                                    ffd->revprop_generation));
3476251881Speter    }
3477251881Speter  while (current % 2 == 0);
3478251881Speter
3479251881Speter  return SVN_NO_ERROR;
3480251881Speter}
3481251881Speter
3482251881Speter/* Set the revprop generation to the next even number to indicate that
3483251881Speter   a) readers shall re-read revprops, and
3484251881Speter   b) the write process has been completed (no recovery required)
3485251881Speter   Use the access object in FS to set the shared mem value. */
3486251881Speterstatic svn_error_t *
3487251881Speterend_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
3488251881Speter{
3489251881Speter  apr_int64_t current = 1;
3490251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
3491251881Speter
3492251881Speter  /* set the revprop generation to an even value to indicate
3493251881Speter   * that a write has been completed
3494251881Speter   */
3495251881Speter  SVN_ERR(ensure_revprop_generation(fs, pool));
3496251881Speter  do
3497251881Speter    {
3498251881Speter      SVN_ERR(svn_named_atomic__add(&current,
3499251881Speter                                    1,
3500251881Speter                                    ffd->revprop_generation));
3501251881Speter    }
3502251881Speter  while (current % 2);
3503251881Speter
3504251881Speter  /* Save the latest generation to disk. FS is currently in a "locked"
3505251881Speter   * state such that we can be sure the be the only ones to write that
3506251881Speter   * file.
3507251881Speter   */
3508251881Speter  return write_revprop_generation_file(fs, current, pool);
3509251881Speter}
3510251881Speter
3511251881Speter/* Container for all data required to access the packed revprop file
3512251881Speter * for a given REVISION.  This structure will be filled incrementally
3513251881Speter * by read_pack_revprops() its sub-routines.
3514251881Speter */
3515251881Spetertypedef struct packed_revprops_t
3516251881Speter{
3517251881Speter  /* revision number to read (not necessarily the first in the pack) */
3518251881Speter  svn_revnum_t revision;
3519251881Speter
3520251881Speter  /* current revprop generation. Used when populating the revprop cache */
3521251881Speter  apr_int64_t generation;
3522251881Speter
3523251881Speter  /* the actual revision properties */
3524251881Speter  apr_hash_t *properties;
3525251881Speter
3526251881Speter  /* their size when serialized to a single string
3527251881Speter   * (as found in PACKED_REVPROPS) */
3528251881Speter  apr_size_t serialized_size;
3529251881Speter
3530251881Speter
3531251881Speter  /* name of the pack file (without folder path) */
3532251881Speter  const char *filename;
3533251881Speter
3534251881Speter  /* packed shard folder path */
3535251881Speter  const char *folder;
3536251881Speter
3537251881Speter  /* sum of values in SIZES */
3538251881Speter  apr_size_t total_size;
3539251881Speter
3540262253Speter  /* first revision in the pack (>= MANIFEST_START) */
3541251881Speter  svn_revnum_t start_revision;
3542251881Speter
3543251881Speter  /* size of the revprops in PACKED_REVPROPS */
3544251881Speter  apr_array_header_t *sizes;
3545251881Speter
3546251881Speter  /* offset of the revprops in PACKED_REVPROPS */
3547251881Speter  apr_array_header_t *offsets;
3548251881Speter
3549251881Speter
3550251881Speter  /* concatenation of the serialized representation of all revprops
3551251881Speter   * in the pack, i.e. the pack content without header and compression */
3552251881Speter  svn_stringbuf_t *packed_revprops;
3553251881Speter
3554262253Speter  /* First revision covered by MANIFEST.
3555262253Speter   * Will equal the shard start revision or 1, for the 1st shard. */
3556262253Speter  svn_revnum_t manifest_start;
3557262253Speter
3558251881Speter  /* content of the manifest.
3559262253Speter   * Maps long(rev - MANIFEST_START) to const char* pack file name */
3560251881Speter  apr_array_header_t *manifest;
3561251881Speter} packed_revprops_t;
3562251881Speter
3563251881Speter/* Parse the serialized revprops in CONTENT and return them in *PROPERTIES.
3564251881Speter * Also, put them into the revprop cache, if activated, for future use.
3565251881Speter * Three more parameters are being used to update the revprop cache: FS is
3566251881Speter * our file system, the revprops belong to REVISION and the global revprop
3567251881Speter * GENERATION is used as well.
3568251881Speter *
3569251881Speter * The returned hash will be allocated in POOL, SCRATCH_POOL is being used
3570251881Speter * for temporary allocations.
3571251881Speter */
3572251881Speterstatic svn_error_t *
3573251881Speterparse_revprop(apr_hash_t **properties,
3574251881Speter              svn_fs_t *fs,
3575251881Speter              svn_revnum_t revision,
3576251881Speter              apr_int64_t generation,
3577251881Speter              svn_string_t *content,
3578251881Speter              apr_pool_t *pool,
3579251881Speter              apr_pool_t *scratch_pool)
3580251881Speter{
3581251881Speter  svn_stream_t *stream = svn_stream_from_string(content, scratch_pool);
3582251881Speter  *properties = apr_hash_make(pool);
3583251881Speter
3584251881Speter  SVN_ERR(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool));
3585251881Speter  if (has_revprop_cache(fs, pool))
3586251881Speter    {
3587251881Speter      fs_fs_data_t *ffd = fs->fsap_data;
3588251881Speter      pair_cache_key_t key = { 0 };
3589251881Speter
3590251881Speter      key.revision = revision;
3591251881Speter      key.second = generation;
3592251881Speter      SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, *properties,
3593251881Speter                             scratch_pool));
3594251881Speter    }
3595251881Speter
3596251881Speter  return SVN_NO_ERROR;
3597251881Speter}
3598251881Speter
3599251881Speter/* Read the non-packed revprops for revision REV in FS, put them into the
3600251881Speter * revprop cache if activated and return them in *PROPERTIES.  GENERATION
3601251881Speter * is the current revprop generation.
3602251881Speter *
3603251881Speter * If the data could not be read due to an otherwise recoverable error,
3604251881Speter * leave *PROPERTIES unchanged. No error will be returned in that case.
3605251881Speter *
3606251881Speter * Allocations will be done in POOL.
3607251881Speter */
3608251881Speterstatic svn_error_t *
3609251881Speterread_non_packed_revprop(apr_hash_t **properties,
3610251881Speter                        svn_fs_t *fs,
3611251881Speter                        svn_revnum_t rev,
3612251881Speter                        apr_int64_t generation,
3613251881Speter                        apr_pool_t *pool)
3614251881Speter{
3615251881Speter  svn_stringbuf_t *content = NULL;
3616251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
3617251881Speter  svn_boolean_t missing = FALSE;
3618251881Speter  int i;
3619251881Speter
3620251881Speter  for (i = 0; i < RECOVERABLE_RETRY_COUNT && !missing && !content; ++i)
3621251881Speter    {
3622251881Speter      svn_pool_clear(iterpool);
3623251881Speter      SVN_ERR(try_stringbuf_from_file(&content,
3624251881Speter                                      &missing,
3625251881Speter                                      path_revprops(fs, rev, iterpool),
3626251881Speter                                      i + 1 < RECOVERABLE_RETRY_COUNT,
3627251881Speter                                      iterpool));
3628251881Speter    }
3629251881Speter
3630251881Speter  if (content)
3631251881Speter    SVN_ERR(parse_revprop(properties, fs, rev, generation,
3632251881Speter                          svn_stringbuf__morph_into_string(content),
3633251881Speter                          pool, iterpool));
3634251881Speter
3635251881Speter  svn_pool_clear(iterpool);
3636251881Speter
3637251881Speter  return SVN_NO_ERROR;
3638251881Speter}
3639251881Speter
3640251881Speter/* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST
3641251881Speter * members. Use POOL for allocating results and SCRATCH_POOL for temporaries.
3642251881Speter */
3643251881Speterstatic svn_error_t *
3644251881Speterget_revprop_packname(svn_fs_t *fs,
3645251881Speter                     packed_revprops_t *revprops,
3646251881Speter                     apr_pool_t *pool,
3647251881Speter                     apr_pool_t *scratch_pool)
3648251881Speter{
3649251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
3650251881Speter  svn_stringbuf_t *content = NULL;
3651251881Speter  const char *manifest_file_path;
3652251881Speter  int idx;
3653251881Speter
3654251881Speter  /* read content of the manifest file */
3655251881Speter  revprops->folder = path_revprops_pack_shard(fs, revprops->revision, pool);
3656251881Speter  manifest_file_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
3657251881Speter
3658251881Speter  SVN_ERR(read_content(&content, manifest_file_path, pool));
3659251881Speter
3660251881Speter  /* parse the manifest. Every line is a file name */
3661251881Speter  revprops->manifest = apr_array_make(pool, ffd->max_files_per_dir,
3662251881Speter                                      sizeof(const char*));
3663262253Speter
3664262253Speter  /* Read all lines.  Since the last line ends with a newline, we will
3665262253Speter     end up with a valid but empty string after the last entry. */
3666262253Speter  while (content->data && *content->data)
3667251881Speter    {
3668251881Speter      APR_ARRAY_PUSH(revprops->manifest, const char*) = content->data;
3669251881Speter      content->data = strchr(content->data, '\n');
3670251881Speter      if (content->data)
3671251881Speter        {
3672251881Speter          *content->data = 0;
3673251881Speter          content->data++;
3674251881Speter        }
3675251881Speter    }
3676251881Speter
3677251881Speter  /* Index for our revision. Rev 0 is excluded from the first shard. */
3678262253Speter  revprops->manifest_start = revprops->revision
3679262253Speter                           - (revprops->revision % ffd->max_files_per_dir);
3680262253Speter  if (revprops->manifest_start == 0)
3681262253Speter    ++revprops->manifest_start;
3682262253Speter  idx = (int)(revprops->revision - revprops->manifest_start);
3683251881Speter
3684251881Speter  if (revprops->manifest->nelts <= idx)
3685251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3686262253Speter                             _("Packed revprop manifest for r%ld too "
3687251881Speter                               "small"), revprops->revision);
3688251881Speter
3689251881Speter  /* Now get the file name */
3690251881Speter  revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*);
3691251881Speter
3692251881Speter  return SVN_NO_ERROR;
3693251881Speter}
3694251881Speter
3695262253Speter/* Return TRUE, if revision R1 and R2 refer to the same shard in FS.
3696262253Speter */
3697262253Speterstatic svn_boolean_t
3698262253Spetersame_shard(svn_fs_t *fs,
3699262253Speter           svn_revnum_t r1,
3700262253Speter           svn_revnum_t r2)
3701262253Speter{
3702262253Speter  fs_fs_data_t *ffd = fs->fsap_data;
3703262253Speter  return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir);
3704262253Speter}
3705262253Speter
3706251881Speter/* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS,
3707251881Speter * fill the START_REVISION, SIZES, OFFSETS members. Also, make
3708251881Speter * PACKED_REVPROPS point to the first serialized revprop.
3709251881Speter *
3710251881Speter * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as
3711251881Speter * well as the SERIALIZED_SIZE member.  If revprop caching has been
3712251881Speter * enabled, parse all revprops in the pack and cache them.
3713251881Speter */
3714251881Speterstatic svn_error_t *
3715251881Speterparse_packed_revprops(svn_fs_t *fs,
3716251881Speter                      packed_revprops_t *revprops,
3717251881Speter                      apr_pool_t *pool,
3718251881Speter                      apr_pool_t *scratch_pool)
3719251881Speter{
3720251881Speter  svn_stream_t *stream;
3721251881Speter  apr_int64_t first_rev, count, i;
3722251881Speter  apr_off_t offset;
3723251881Speter  const char *header_end;
3724251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3725251881Speter
3726251881Speter  /* decompress (even if the data is only "stored", there is still a
3727251881Speter   * length header to remove) */
3728251881Speter  svn_string_t *compressed
3729251881Speter      = svn_stringbuf__morph_into_string(revprops->packed_revprops);
3730251881Speter  svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(pool);
3731253734Speter  SVN_ERR(svn__decompress(compressed, uncompressed, APR_SIZE_MAX));
3732251881Speter
3733251881Speter  /* read first revision number and number of revisions in the pack */
3734251881Speter  stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
3735251881Speter  SVN_ERR(read_number_from_stream(&first_rev, NULL, stream, iterpool));
3736251881Speter  SVN_ERR(read_number_from_stream(&count, NULL, stream, iterpool));
3737251881Speter
3738262253Speter  /* Check revision range for validity. */
3739262253Speter  if (   !same_shard(fs, revprops->revision, first_rev)
3740262253Speter      || !same_shard(fs, revprops->revision, first_rev + count - 1)
3741262253Speter      || count < 1)
3742262253Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3743262253Speter                             _("Revprop pack for revision r%ld"
3744262253Speter                               " contains revprops for r%ld .. r%ld"),
3745262253Speter                             revprops->revision,
3746262253Speter                             (svn_revnum_t)first_rev,
3747262253Speter                             (svn_revnum_t)(first_rev + count -1));
3748262253Speter
3749262253Speter  /* Since start & end are in the same shard, it is enough to just test
3750262253Speter   * the FIRST_REV for being actually packed.  That will also cover the
3751262253Speter   * special case of rev 0 never being packed. */
3752262253Speter  if (!is_packed_revprop(fs, first_rev))
3753262253Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3754262253Speter                             _("Revprop pack for revision r%ld"
3755262253Speter                               " starts at non-packed revisions r%ld"),
3756262253Speter                             revprops->revision, (svn_revnum_t)first_rev);
3757262253Speter
3758251881Speter  /* make PACKED_REVPROPS point to the first char after the header.
3759251881Speter   * This is where the serialized revprops are. */
3760251881Speter  header_end = strstr(uncompressed->data, "\n\n");
3761251881Speter  if (header_end == NULL)
3762251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3763251881Speter                            _("Header end not found"));
3764251881Speter
3765251881Speter  offset = header_end - uncompressed->data + 2;
3766251881Speter
3767251881Speter  revprops->packed_revprops = svn_stringbuf_create_empty(pool);
3768251881Speter  revprops->packed_revprops->data = uncompressed->data + offset;
3769251881Speter  revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
3770251881Speter  revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset);
3771251881Speter
3772251881Speter  /* STREAM still points to the first entry in the sizes list.
3773251881Speter   * Init / construct REVPROPS members. */
3774251881Speter  revprops->start_revision = (svn_revnum_t)first_rev;
3775251881Speter  revprops->sizes = apr_array_make(pool, (int)count, sizeof(offset));
3776251881Speter  revprops->offsets = apr_array_make(pool, (int)count, sizeof(offset));
3777251881Speter
3778251881Speter  /* Now parse, revision by revision, the size and content of each
3779251881Speter   * revisions' revprops. */
3780251881Speter  for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i)
3781251881Speter    {
3782251881Speter      apr_int64_t size;
3783251881Speter      svn_string_t serialized;
3784251881Speter      apr_hash_t *properties;
3785251881Speter      svn_revnum_t revision = (svn_revnum_t)(first_rev + i);
3786251881Speter
3787251881Speter      /* read & check the serialized size */
3788251881Speter      SVN_ERR(read_number_from_stream(&size, NULL, stream, iterpool));
3789251881Speter      if (size + offset > (apr_int64_t)revprops->packed_revprops->len)
3790251881Speter        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3791251881Speter                        _("Packed revprop size exceeds pack file size"));
3792251881Speter
3793251881Speter      /* Parse this revprops list, if necessary */
3794251881Speter      serialized.data = revprops->packed_revprops->data + offset;
3795251881Speter      serialized.len = (apr_size_t)size;
3796251881Speter
3797251881Speter      if (revision == revprops->revision)
3798251881Speter        {
3799251881Speter          SVN_ERR(parse_revprop(&revprops->properties, fs, revision,
3800251881Speter                                revprops->generation, &serialized,
3801251881Speter                                pool, iterpool));
3802251881Speter          revprops->serialized_size = serialized.len;
3803251881Speter        }
3804251881Speter      else
3805251881Speter        {
3806251881Speter          /* If revprop caching is enabled, parse any revprops.
3807251881Speter           * They will get cached as a side-effect of this. */
3808251881Speter          if (has_revprop_cache(fs, pool))
3809251881Speter            SVN_ERR(parse_revprop(&properties, fs, revision,
3810251881Speter                                  revprops->generation, &serialized,
3811251881Speter                                  iterpool, iterpool));
3812251881Speter        }
3813251881Speter
3814251881Speter      /* fill REVPROPS data structures */
3815251881Speter      APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len;
3816251881Speter      APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset;
3817251881Speter      revprops->total_size += serialized.len;
3818251881Speter
3819251881Speter      offset += serialized.len;
3820251881Speter
3821251881Speter      svn_pool_clear(iterpool);
3822251881Speter    }
3823251881Speter
3824251881Speter  return SVN_NO_ERROR;
3825251881Speter}
3826251881Speter
3827251881Speter/* In filesystem FS, read the packed revprops for revision REV into
3828251881Speter * *REVPROPS.  Use GENERATION to populate the revprop cache, if enabled.
3829251881Speter * Allocate data in POOL.
3830251881Speter */
3831251881Speterstatic svn_error_t *
3832251881Speterread_pack_revprop(packed_revprops_t **revprops,
3833251881Speter                  svn_fs_t *fs,
3834251881Speter                  svn_revnum_t rev,
3835251881Speter                  apr_int64_t generation,
3836251881Speter                  apr_pool_t *pool)
3837251881Speter{
3838251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
3839251881Speter  svn_boolean_t missing = FALSE;
3840251881Speter  svn_error_t *err;
3841251881Speter  packed_revprops_t *result;
3842251881Speter  int i;
3843251881Speter
3844251881Speter  /* someone insisted that REV is packed. Double-check if necessary */
3845251881Speter  if (!is_packed_revprop(fs, rev))
3846251881Speter     SVN_ERR(update_min_unpacked_rev(fs, iterpool));
3847251881Speter
3848251881Speter  if (!is_packed_revprop(fs, rev))
3849251881Speter    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
3850251881Speter                              _("No such packed revision %ld"), rev);
3851251881Speter
3852251881Speter  /* initialize the result data structure */
3853251881Speter  result = apr_pcalloc(pool, sizeof(*result));
3854251881Speter  result->revision = rev;
3855251881Speter  result->generation = generation;
3856251881Speter
3857251881Speter  /* try to read the packed revprops. This may require retries if we have
3858251881Speter   * concurrent writers. */
3859251881Speter  for (i = 0; i < RECOVERABLE_RETRY_COUNT && !result->packed_revprops; ++i)
3860251881Speter    {
3861251881Speter      const char *file_path;
3862251881Speter
3863251881Speter      /* there might have been concurrent writes.
3864251881Speter       * Re-read the manifest and the pack file.
3865251881Speter       */
3866251881Speter      SVN_ERR(get_revprop_packname(fs, result, pool, iterpool));
3867251881Speter      file_path  = svn_dirent_join(result->folder,
3868251881Speter                                   result->filename,
3869251881Speter                                   iterpool);
3870251881Speter      SVN_ERR(try_stringbuf_from_file(&result->packed_revprops,
3871251881Speter                                      &missing,
3872251881Speter                                      file_path,
3873251881Speter                                      i + 1 < RECOVERABLE_RETRY_COUNT,
3874251881Speter                                      pool));
3875251881Speter
3876251881Speter      /* If we could not find the file, there was a write.
3877251881Speter       * So, we should refresh our revprop generation info as well such
3878251881Speter       * that others may find data we will put into the cache.  They would
3879251881Speter       * consider it outdated, otherwise.
3880251881Speter       */
3881251881Speter      if (missing && has_revprop_cache(fs, pool))
3882251881Speter        SVN_ERR(read_revprop_generation(&result->generation, fs, pool));
3883251881Speter
3884251881Speter      svn_pool_clear(iterpool);
3885251881Speter    }
3886251881Speter
3887251881Speter  /* the file content should be available now */
3888251881Speter  if (!result->packed_revprops)
3889251881Speter    return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL,
3890262253Speter                  _("Failed to read revprop pack file for r%ld"), rev);
3891251881Speter
3892251881Speter  /* parse it. RESULT will be complete afterwards. */
3893251881Speter  err = parse_packed_revprops(fs, result, pool, iterpool);
3894251881Speter  svn_pool_destroy(iterpool);
3895251881Speter  if (err)
3896251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
3897262253Speter                  _("Revprop pack file for r%ld is corrupt"), rev);
3898251881Speter
3899251881Speter  *revprops = result;
3900251881Speter
3901251881Speter  return SVN_NO_ERROR;
3902251881Speter}
3903251881Speter
3904251881Speter/* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
3905251881Speter *
3906251881Speter * Allocations will be done in POOL.
3907251881Speter */
3908251881Speterstatic svn_error_t *
3909251881Speterget_revision_proplist(apr_hash_t **proplist_p,
3910251881Speter                      svn_fs_t *fs,
3911251881Speter                      svn_revnum_t rev,
3912251881Speter                      apr_pool_t *pool)
3913251881Speter{
3914251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
3915251881Speter  apr_int64_t generation = 0;
3916251881Speter
3917251881Speter  /* not found, yet */
3918251881Speter  *proplist_p = NULL;
3919251881Speter
3920251881Speter  /* should they be available at all? */
3921251881Speter  SVN_ERR(ensure_revision_exists(fs, rev, pool));
3922251881Speter
3923251881Speter  /* Try cache lookup first. */
3924251881Speter  if (has_revprop_cache(fs, pool))
3925251881Speter    {
3926251881Speter      svn_boolean_t is_cached;
3927251881Speter      pair_cache_key_t key = { 0 };
3928251881Speter
3929251881Speter      SVN_ERR(read_revprop_generation(&generation, fs, pool));
3930251881Speter
3931251881Speter      key.revision = rev;
3932251881Speter      key.second = generation;
3933251881Speter      SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
3934251881Speter                             ffd->revprop_cache, &key, pool));
3935251881Speter      if (is_cached)
3936251881Speter        return SVN_NO_ERROR;
3937251881Speter    }
3938251881Speter
3939251881Speter  /* if REV had not been packed when we began, try reading it from the
3940251881Speter   * non-packed shard.  If that fails, we will fall through to packed
3941251881Speter   * shard reads. */
3942251881Speter  if (!is_packed_revprop(fs, rev))
3943251881Speter    {
3944251881Speter      svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
3945251881Speter                                                 generation, pool);
3946251881Speter      if (err)
3947251881Speter        {
3948251881Speter          if (!APR_STATUS_IS_ENOENT(err->apr_err)
3949251881Speter              || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
3950251881Speter            return svn_error_trace(err);
3951251881Speter
3952251881Speter          svn_error_clear(err);
3953251881Speter          *proplist_p = NULL; /* in case read_non_packed_revprop changed it */
3954251881Speter        }
3955251881Speter    }
3956251881Speter
3957251881Speter  /* if revprop packing is available and we have not read the revprops, yet,
3958251881Speter   * try reading them from a packed shard.  If that fails, REV is most
3959251881Speter   * likely invalid (or its revprops highly contested). */
3960251881Speter  if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p)
3961251881Speter    {
3962251881Speter      packed_revprops_t *packed_revprops;
3963251881Speter      SVN_ERR(read_pack_revprop(&packed_revprops, fs, rev, generation, pool));
3964251881Speter      *proplist_p = packed_revprops->properties;
3965251881Speter    }
3966251881Speter
3967251881Speter  /* The revprops should have been there. Did we get them? */
3968251881Speter  if (!*proplist_p)
3969251881Speter    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
3970251881Speter                             _("Could not read revprops for revision %ld"),
3971251881Speter                             rev);
3972251881Speter
3973251881Speter  return SVN_NO_ERROR;
3974251881Speter}
3975251881Speter
3976251881Speter/* Serialize the revision property list PROPLIST of revision REV in
3977251881Speter * filesystem FS to a non-packed file.  Return the name of that temporary
3978251881Speter * file in *TMP_PATH and the file path that it must be moved to in
3979251881Speter * *FINAL_PATH.
3980251881Speter *
3981251881Speter * Use POOL for allocations.
3982251881Speter */
3983251881Speterstatic svn_error_t *
3984251881Speterwrite_non_packed_revprop(const char **final_path,
3985251881Speter                         const char **tmp_path,
3986251881Speter                         svn_fs_t *fs,
3987251881Speter                         svn_revnum_t rev,
3988251881Speter                         apr_hash_t *proplist,
3989251881Speter                         apr_pool_t *pool)
3990251881Speter{
3991251881Speter  svn_stream_t *stream;
3992251881Speter  *final_path = path_revprops(fs, rev, pool);
3993251881Speter
3994251881Speter  /* ### do we have a directory sitting around already? we really shouldn't
3995251881Speter     ### have to get the dirname here. */
3996251881Speter  SVN_ERR(svn_stream_open_unique(&stream, tmp_path,
3997251881Speter                                 svn_dirent_dirname(*final_path, pool),
3998251881Speter                                 svn_io_file_del_none, pool, pool));
3999251881Speter  SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4000251881Speter  SVN_ERR(svn_stream_close(stream));
4001251881Speter
4002251881Speter  return SVN_NO_ERROR;
4003251881Speter}
4004251881Speter
4005251881Speter/* After writing the new revprop file(s), call this function to move the
4006251881Speter * file at TMP_PATH to FINAL_PATH and give it the permissions from
4007251881Speter * PERMS_REFERENCE.
4008251881Speter *
4009251881Speter * If indicated in BUMP_GENERATION, increase FS' revprop generation.
4010251881Speter * Finally, delete all the temporary files given in FILES_TO_DELETE.
4011251881Speter * The latter may be NULL.
4012251881Speter *
4013251881Speter * Use POOL for temporary allocations.
4014251881Speter */
4015251881Speterstatic svn_error_t *
4016251881Speterswitch_to_new_revprop(svn_fs_t *fs,
4017251881Speter                      const char *final_path,
4018251881Speter                      const char *tmp_path,
4019251881Speter                      const char *perms_reference,
4020251881Speter                      apr_array_header_t *files_to_delete,
4021251881Speter                      svn_boolean_t bump_generation,
4022251881Speter                      apr_pool_t *pool)
4023251881Speter{
4024251881Speter  /* Now, we may actually be replacing revprops. Make sure that all other
4025251881Speter     threads and processes will know about this. */
4026251881Speter  if (bump_generation)
4027251881Speter    SVN_ERR(begin_revprop_change(fs, pool));
4028251881Speter
4029251881Speter  SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool));
4030251881Speter
4031251881Speter  /* Indicate that the update (if relevant) has been completed. */
4032251881Speter  if (bump_generation)
4033251881Speter    SVN_ERR(end_revprop_change(fs, pool));
4034251881Speter
4035251881Speter  /* Clean up temporary files, if necessary. */
4036251881Speter  if (files_to_delete)
4037251881Speter    {
4038251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
4039251881Speter      int i;
4040251881Speter
4041251881Speter      for (i = 0; i < files_to_delete->nelts; ++i)
4042251881Speter        {
4043251881Speter          const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
4044251881Speter          SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
4045251881Speter          svn_pool_clear(iterpool);
4046251881Speter        }
4047251881Speter
4048251881Speter      svn_pool_destroy(iterpool);
4049251881Speter    }
4050251881Speter  return SVN_NO_ERROR;
4051251881Speter}
4052251881Speter
4053251881Speter/* Write a pack file header to STREAM that starts at revision START_REVISION
4054251881Speter * and contains the indexes [START,END) of SIZES.
4055251881Speter */
4056251881Speterstatic svn_error_t *
4057251881Speterserialize_revprops_header(svn_stream_t *stream,
4058251881Speter                          svn_revnum_t start_revision,
4059251881Speter                          apr_array_header_t *sizes,
4060251881Speter                          int start,
4061251881Speter                          int end,
4062251881Speter                          apr_pool_t *pool)
4063251881Speter{
4064251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
4065251881Speter  int i;
4066251881Speter
4067251881Speter  SVN_ERR_ASSERT(start < end);
4068251881Speter
4069251881Speter  /* start revision and entry count */
4070251881Speter  SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision));
4071251881Speter  SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start));
4072251881Speter
4073251881Speter  /* the sizes array */
4074251881Speter  for (i = start; i < end; ++i)
4075251881Speter    {
4076251881Speter      apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
4077251881Speter      SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
4078251881Speter                                size));
4079251881Speter    }
4080251881Speter
4081251881Speter  /* the double newline char indicates the end of the header */
4082251881Speter  SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
4083251881Speter
4084251881Speter  svn_pool_clear(iterpool);
4085251881Speter  return SVN_NO_ERROR;
4086251881Speter}
4087251881Speter
4088251881Speter/* Writes the a pack file to FILE_STREAM.  It copies the serialized data
4089251881Speter * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
4090251881Speter *
4091251881Speter * The data for the latter is taken from NEW_SERIALIZED.  Note, that
4092251881Speter * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
4093251881Speter * taken in that case but only a subset of the old data will be copied.
4094251881Speter *
4095251881Speter * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
4096251881Speter * POOL is used for temporary allocations.
4097251881Speter */
4098251881Speterstatic svn_error_t *
4099251881Speterrepack_revprops(svn_fs_t *fs,
4100251881Speter                packed_revprops_t *revprops,
4101251881Speter                int start,
4102251881Speter                int end,
4103251881Speter                int changed_index,
4104251881Speter                svn_stringbuf_t *new_serialized,
4105251881Speter                apr_off_t new_total_size,
4106251881Speter                svn_stream_t *file_stream,
4107251881Speter                apr_pool_t *pool)
4108251881Speter{
4109251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
4110251881Speter  svn_stream_t *stream;
4111251881Speter  int i;
4112251881Speter
4113251881Speter  /* create data empty buffers and the stream object */
4114251881Speter  svn_stringbuf_t *uncompressed
4115251881Speter    = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool);
4116251881Speter  svn_stringbuf_t *compressed
4117251881Speter    = svn_stringbuf_create_empty(pool);
4118251881Speter  stream = svn_stream_from_stringbuf(uncompressed, pool);
4119251881Speter
4120251881Speter  /* write the header*/
4121251881Speter  SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
4122251881Speter                                    revprops->sizes, start, end, pool));
4123251881Speter
4124251881Speter  /* append the serialized revprops */
4125251881Speter  for (i = start; i < end; ++i)
4126251881Speter    if (i == changed_index)
4127251881Speter      {
4128251881Speter        SVN_ERR(svn_stream_write(stream,
4129251881Speter                                 new_serialized->data,
4130251881Speter                                 &new_serialized->len));
4131251881Speter      }
4132251881Speter    else
4133251881Speter      {
4134251881Speter        apr_size_t size
4135251881Speter            = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
4136251881Speter        apr_size_t offset
4137251881Speter            = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
4138251881Speter
4139251881Speter        SVN_ERR(svn_stream_write(stream,
4140251881Speter                                 revprops->packed_revprops->data + offset,
4141251881Speter                                 &size));
4142251881Speter      }
4143251881Speter
4144251881Speter  /* flush the stream buffer (if any) to our underlying data buffer */
4145251881Speter  SVN_ERR(svn_stream_close(stream));
4146251881Speter
4147251881Speter  /* compress / store the data */
4148251881Speter  SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
4149251881Speter                        compressed,
4150251881Speter                        ffd->compress_packed_revprops
4151251881Speter                          ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
4152251881Speter                          : SVN_DELTA_COMPRESSION_LEVEL_NONE));
4153251881Speter
4154251881Speter  /* finally, write the content to the target stream and close it */
4155251881Speter  SVN_ERR(svn_stream_write(file_stream, compressed->data, &compressed->len));
4156251881Speter  SVN_ERR(svn_stream_close(file_stream));
4157251881Speter
4158251881Speter  return SVN_NO_ERROR;
4159251881Speter}
4160251881Speter
4161262253Speter/* Allocate a new pack file name for revisions
4162262253Speter *     [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1]
4163251881Speter * of REVPROPS->MANIFEST.  Add the name of old file to FILES_TO_DELETE,
4164251881Speter * auto-create that array if necessary.  Return an open file stream to
4165251881Speter * the new file in *STREAM allocated in POOL.
4166251881Speter */
4167251881Speterstatic svn_error_t *
4168251881Speterrepack_stream_open(svn_stream_t **stream,
4169251881Speter                   svn_fs_t *fs,
4170251881Speter                   packed_revprops_t *revprops,
4171251881Speter                   int start,
4172251881Speter                   int end,
4173251881Speter                   apr_array_header_t **files_to_delete,
4174251881Speter                   apr_pool_t *pool)
4175251881Speter{
4176251881Speter  apr_int64_t tag;
4177251881Speter  const char *tag_string;
4178251881Speter  svn_string_t *new_filename;
4179251881Speter  int i;
4180251881Speter  apr_file_t *file;
4181262253Speter  int manifest_offset
4182262253Speter    = (int)(revprops->start_revision - revprops->manifest_start);
4183251881Speter
4184251881Speter  /* get the old (= current) file name and enlist it for later deletion */
4185262253Speter  const char *old_filename = APR_ARRAY_IDX(revprops->manifest,
4186262253Speter                                           start + manifest_offset,
4187262253Speter                                           const char*);
4188251881Speter
4189251881Speter  if (*files_to_delete == NULL)
4190251881Speter    *files_to_delete = apr_array_make(pool, 3, sizeof(const char*));
4191251881Speter
4192251881Speter  APR_ARRAY_PUSH(*files_to_delete, const char*)
4193251881Speter    = svn_dirent_join(revprops->folder, old_filename, pool);
4194251881Speter
4195251881Speter  /* increase the tag part, i.e. the counter after the dot */
4196251881Speter  tag_string = strchr(old_filename, '.');
4197251881Speter  if (tag_string == NULL)
4198251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4199251881Speter                             _("Packed file '%s' misses a tag"),
4200251881Speter                             old_filename);
4201251881Speter
4202251881Speter  SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
4203251881Speter  new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT,
4204251881Speter                                    revprops->start_revision + start,
4205251881Speter                                    ++tag);
4206251881Speter
4207251881Speter  /* update the manifest to point to the new file */
4208251881Speter  for (i = start; i < end; ++i)
4209262253Speter    APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
4210262253Speter      = new_filename->data;
4211251881Speter
4212251881Speter  /* create a file stream for the new file */
4213251881Speter  SVN_ERR(svn_io_file_open(&file, svn_dirent_join(revprops->folder,
4214251881Speter                                                  new_filename->data,
4215251881Speter                                                  pool),
4216251881Speter                           APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
4217251881Speter  *stream = svn_stream_from_aprfile2(file, FALSE, pool);
4218251881Speter
4219251881Speter  return SVN_NO_ERROR;
4220251881Speter}
4221251881Speter
4222251881Speter/* For revision REV in filesystem FS, set the revision properties to
4223251881Speter * PROPLIST.  Return a new file in *TMP_PATH that the caller shall move
4224251881Speter * to *FINAL_PATH to make the change visible.  Files to be deleted will
4225251881Speter * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
4226251881Speter * Use POOL for allocations.
4227251881Speter */
4228251881Speterstatic svn_error_t *
4229251881Speterwrite_packed_revprop(const char **final_path,
4230251881Speter                     const char **tmp_path,
4231251881Speter                     apr_array_header_t **files_to_delete,
4232251881Speter                     svn_fs_t *fs,
4233251881Speter                     svn_revnum_t rev,
4234251881Speter                     apr_hash_t *proplist,
4235251881Speter                     apr_pool_t *pool)
4236251881Speter{
4237251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
4238251881Speter  packed_revprops_t *revprops;
4239251881Speter  apr_int64_t generation = 0;
4240251881Speter  svn_stream_t *stream;
4241251881Speter  svn_stringbuf_t *serialized;
4242251881Speter  apr_off_t new_total_size;
4243251881Speter  int changed_index;
4244251881Speter
4245251881Speter  /* read the current revprop generation. This value will not change
4246251881Speter   * while we hold the global write lock to this FS. */
4247251881Speter  if (has_revprop_cache(fs, pool))
4248251881Speter    SVN_ERR(read_revprop_generation(&generation, fs, pool));
4249251881Speter
4250251881Speter  /* read contents of the current pack file */
4251251881Speter  SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool));
4252251881Speter
4253251881Speter  /* serialize the new revprops */
4254251881Speter  serialized = svn_stringbuf_create_empty(pool);
4255251881Speter  stream = svn_stream_from_stringbuf(serialized, pool);
4256251881Speter  SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4257251881Speter  SVN_ERR(svn_stream_close(stream));
4258251881Speter
4259251881Speter  /* calculate the size of the new data */
4260251881Speter  changed_index = (int)(rev - revprops->start_revision);
4261251881Speter  new_total_size = revprops->total_size - revprops->serialized_size
4262251881Speter                 + serialized->len
4263251881Speter                 + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
4264251881Speter
4265251881Speter  APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
4266251881Speter
4267251881Speter  /* can we put the new data into the same pack as the before? */
4268251881Speter  if (   new_total_size < ffd->revprop_pack_size
4269251881Speter      || revprops->sizes->nelts == 1)
4270251881Speter    {
4271251881Speter      /* simply replace the old pack file with new content as we do it
4272251881Speter       * in the non-packed case */
4273251881Speter
4274251881Speter      *final_path = svn_dirent_join(revprops->folder, revprops->filename,
4275251881Speter                                    pool);
4276251881Speter      SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
4277251881Speter                                     svn_io_file_del_none, pool, pool));
4278251881Speter      SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
4279251881Speter                              changed_index, serialized, new_total_size,
4280251881Speter                              stream, pool));
4281251881Speter    }
4282251881Speter  else
4283251881Speter    {
4284251881Speter      /* split the pack file into two of roughly equal size */
4285251881Speter      int right_count, left_count, i;
4286251881Speter
4287251881Speter      int left = 0;
4288251881Speter      int right = revprops->sizes->nelts - 1;
4289251881Speter      apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
4290251881Speter      apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
4291251881Speter
4292251881Speter      /* let left and right side grow such that their size difference
4293251881Speter       * is minimal after each step. */
4294251881Speter      while (left <= right)
4295251881Speter        if (  left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4296251881Speter            < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
4297251881Speter          {
4298251881Speter            left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4299251881Speter                      + SVN_INT64_BUFFER_SIZE;
4300251881Speter            ++left;
4301251881Speter          }
4302251881Speter        else
4303251881Speter          {
4304251881Speter            right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
4305251881Speter                        + SVN_INT64_BUFFER_SIZE;
4306251881Speter            --right;
4307251881Speter          }
4308251881Speter
4309251881Speter       /* since the items need much less than SVN_INT64_BUFFER_SIZE
4310251881Speter        * bytes to represent their length, the split may not be optimal */
4311251881Speter      left_count = left;
4312251881Speter      right_count = revprops->sizes->nelts - left;
4313251881Speter
4314251881Speter      /* if new_size is large, one side may exceed the pack size limit.
4315251881Speter       * In that case, split before and after the modified revprop.*/
4316251881Speter      if (   left_size > ffd->revprop_pack_size
4317251881Speter          || right_size > ffd->revprop_pack_size)
4318251881Speter        {
4319251881Speter          left_count = changed_index;
4320251881Speter          right_count = revprops->sizes->nelts - left_count - 1;
4321251881Speter        }
4322251881Speter
4323251881Speter      /* write the new, split files */
4324251881Speter      if (left_count)
4325251881Speter        {
4326251881Speter          SVN_ERR(repack_stream_open(&stream, fs, revprops, 0,
4327251881Speter                                     left_count, files_to_delete, pool));
4328251881Speter          SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
4329251881Speter                                  changed_index, serialized, new_total_size,
4330251881Speter                                  stream, pool));
4331251881Speter        }
4332251881Speter
4333251881Speter      if (left_count + right_count < revprops->sizes->nelts)
4334251881Speter        {
4335251881Speter          SVN_ERR(repack_stream_open(&stream, fs, revprops, changed_index,
4336251881Speter                                     changed_index + 1, files_to_delete,
4337251881Speter                                     pool));
4338251881Speter          SVN_ERR(repack_revprops(fs, revprops, changed_index,
4339251881Speter                                  changed_index + 1,
4340251881Speter                                  changed_index, serialized, new_total_size,
4341251881Speter                                  stream, pool));
4342251881Speter        }
4343251881Speter
4344251881Speter      if (right_count)
4345251881Speter        {
4346251881Speter          SVN_ERR(repack_stream_open(&stream, fs, revprops,
4347251881Speter                                     revprops->sizes->nelts - right_count,
4348251881Speter                                     revprops->sizes->nelts,
4349251881Speter                                     files_to_delete, pool));
4350251881Speter          SVN_ERR(repack_revprops(fs, revprops,
4351251881Speter                                  revprops->sizes->nelts - right_count,
4352251881Speter                                  revprops->sizes->nelts, changed_index,
4353251881Speter                                  serialized, new_total_size, stream,
4354251881Speter                                  pool));
4355251881Speter        }
4356251881Speter
4357251881Speter      /* write the new manifest */
4358251881Speter      *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
4359251881Speter      SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
4360251881Speter                                     svn_io_file_del_none, pool, pool));
4361251881Speter
4362251881Speter      for (i = 0; i < revprops->manifest->nelts; ++i)
4363251881Speter        {
4364251881Speter          const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
4365251881Speter                                               const char*);
4366251881Speter          SVN_ERR(svn_stream_printf(stream, pool, "%s\n", filename));
4367251881Speter        }
4368251881Speter
4369251881Speter      SVN_ERR(svn_stream_close(stream));
4370251881Speter    }
4371251881Speter
4372251881Speter  return SVN_NO_ERROR;
4373251881Speter}
4374251881Speter
4375251881Speter/* Set the revision property list of revision REV in filesystem FS to
4376251881Speter   PROPLIST.  Use POOL for temporary allocations. */
4377251881Speterstatic svn_error_t *
4378251881Speterset_revision_proplist(svn_fs_t *fs,
4379251881Speter                      svn_revnum_t rev,
4380251881Speter                      apr_hash_t *proplist,
4381251881Speter                      apr_pool_t *pool)
4382251881Speter{
4383251881Speter  svn_boolean_t is_packed;
4384251881Speter  svn_boolean_t bump_generation = FALSE;
4385251881Speter  const char *final_path;
4386251881Speter  const char *tmp_path;
4387251881Speter  const char *perms_reference;
4388251881Speter  apr_array_header_t *files_to_delete = NULL;
4389251881Speter
4390251881Speter  SVN_ERR(ensure_revision_exists(fs, rev, pool));
4391251881Speter
4392251881Speter  /* this info will not change while we hold the global FS write lock */
4393251881Speter  is_packed = is_packed_revprop(fs, rev);
4394251881Speter
4395251881Speter  /* Test whether revprops already exist for this revision.
4396251881Speter   * Only then will we need to bump the revprop generation. */
4397251881Speter  if (has_revprop_cache(fs, pool))
4398251881Speter    {
4399251881Speter      if (is_packed)
4400251881Speter        {
4401251881Speter          bump_generation = TRUE;
4402251881Speter        }
4403251881Speter      else
4404251881Speter        {
4405251881Speter          svn_node_kind_t kind;
4406251881Speter          SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind,
4407251881Speter                                    pool));
4408251881Speter          bump_generation = kind != svn_node_none;
4409251881Speter        }
4410251881Speter    }
4411251881Speter
4412251881Speter  /* Serialize the new revprop data */
4413251881Speter  if (is_packed)
4414251881Speter    SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
4415251881Speter                                 fs, rev, proplist, pool));
4416251881Speter  else
4417251881Speter    SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
4418251881Speter                                     fs, rev, proplist, pool));
4419251881Speter
4420251881Speter  /* We use the rev file of this revision as the perms reference,
4421251881Speter   * because when setting revprops for the first time, the revprop
4422251881Speter   * file won't exist and therefore can't serve as its own reference.
4423251881Speter   * (Whereas the rev file should already exist at this point.)
4424251881Speter   */
4425251881Speter  SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool));
4426251881Speter
4427251881Speter  /* Now, switch to the new revprop data. */
4428251881Speter  SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
4429251881Speter                                files_to_delete, bump_generation, pool));
4430251881Speter
4431251881Speter  return SVN_NO_ERROR;
4432251881Speter}
4433251881Speter
4434251881Spetersvn_error_t *
4435251881Spetersvn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
4436251881Speter                             svn_fs_t *fs,
4437251881Speter                             svn_revnum_t rev,
4438251881Speter                             apr_pool_t *pool)
4439251881Speter{
4440251881Speter  SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool));
4441251881Speter
4442251881Speter  return SVN_NO_ERROR;
4443251881Speter}
4444251881Speter
4445251881Speter/* Represents where in the current svndiff data block each
4446251881Speter   representation is. */
4447251881Speterstruct rep_state
4448251881Speter{
4449251881Speter  apr_file_t *file;
4450251881Speter                    /* The txdelta window cache to use or NULL. */
4451251881Speter  svn_cache__t *window_cache;
4452251881Speter                    /* Caches un-deltified windows. May be NULL. */
4453251881Speter  svn_cache__t *combined_cache;
4454251881Speter  apr_off_t start;  /* The starting offset for the raw
4455251881Speter                       svndiff/plaintext data minus header. */
4456251881Speter  apr_off_t off;    /* The current offset into the file. */
4457251881Speter  apr_off_t end;    /* The end offset of the raw data. */
4458251881Speter  int ver;          /* If a delta, what svndiff version? */
4459251881Speter  int chunk_index;
4460251881Speter};
4461251881Speter
4462251881Speter/* See create_rep_state, which wraps this and adds another error. */
4463251881Speterstatic svn_error_t *
4464251881Spetercreate_rep_state_body(struct rep_state **rep_state,
4465251881Speter                      struct rep_args **rep_args,
4466251881Speter                      apr_file_t **file_hint,
4467251881Speter                      svn_revnum_t *rev_hint,
4468251881Speter                      representation_t *rep,
4469251881Speter                      svn_fs_t *fs,
4470251881Speter                      apr_pool_t *pool)
4471251881Speter{
4472251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
4473251881Speter  struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
4474251881Speter  struct rep_args *ra;
4475251881Speter  unsigned char buf[4];
4476251881Speter
4477251881Speter  /* If the hint is
4478251881Speter   * - given,
4479251881Speter   * - refers to a valid revision,
4480251881Speter   * - refers to a packed revision,
4481251881Speter   * - as does the rep we want to read, and
4482251881Speter   * - refers to the same pack file as the rep
4483251881Speter   * ...
4484251881Speter   */
4485251881Speter  if (   file_hint && rev_hint && *file_hint
4486251881Speter      && SVN_IS_VALID_REVNUM(*rev_hint)
4487251881Speter      && *rev_hint < ffd->min_unpacked_rev
4488251881Speter      && rep->revision < ffd->min_unpacked_rev
4489251881Speter      && (   (*rev_hint / ffd->max_files_per_dir)
4490251881Speter          == (rep->revision / ffd->max_files_per_dir)))
4491251881Speter    {
4492251881Speter      /* ... we can re-use the same, already open file object
4493251881Speter       */
4494251881Speter      apr_off_t offset;
4495251881Speter      SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool));
4496251881Speter
4497251881Speter      offset += rep->offset;
4498251881Speter      SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
4499251881Speter
4500251881Speter      rs->file = *file_hint;
4501251881Speter    }
4502251881Speter  else
4503251881Speter    {
4504251881Speter      /* otherwise, create a new file object
4505251881Speter       */
4506251881Speter      SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
4507251881Speter    }
4508251881Speter
4509251881Speter  /* remember the current file, if suggested by the caller */
4510251881Speter  if (file_hint)
4511251881Speter    *file_hint = rs->file;
4512251881Speter  if (rev_hint)
4513251881Speter    *rev_hint = rep->revision;
4514251881Speter
4515251881Speter  /* continue constructing RS and RA */
4516251881Speter  rs->window_cache = ffd->txdelta_window_cache;
4517251881Speter  rs->combined_cache = ffd->combined_window_cache;
4518251881Speter
4519251881Speter  SVN_ERR(read_rep_line(&ra, rs->file, pool));
4520251881Speter  SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
4521251881Speter  rs->off = rs->start;
4522251881Speter  rs->end = rs->start + rep->size;
4523251881Speter  *rep_state = rs;
4524251881Speter  *rep_args = ra;
4525251881Speter
4526251881Speter  if (!ra->is_delta)
4527251881Speter    /* This is a plaintext, so just return the current rep_state. */
4528251881Speter    return SVN_NO_ERROR;
4529251881Speter
4530251881Speter  /* We are dealing with a delta, find out what version. */
4531251881Speter  SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf),
4532251881Speter                                 NULL, NULL, pool));
4533251881Speter  /* ### Layering violation */
4534251881Speter  if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
4535251881Speter    return svn_error_create
4536251881Speter      (SVN_ERR_FS_CORRUPT, NULL,
4537251881Speter       _("Malformed svndiff data in representation"));
4538251881Speter  rs->ver = buf[3];
4539251881Speter  rs->chunk_index = 0;
4540251881Speter  rs->off += 4;
4541251881Speter
4542251881Speter  return SVN_NO_ERROR;
4543251881Speter}
4544251881Speter
4545251881Speter/* Read the rep args for REP in filesystem FS and create a rep_state
4546251881Speter   for reading the representation.  Return the rep_state in *REP_STATE
4547251881Speter   and the rep args in *REP_ARGS, both allocated in POOL.
4548251881Speter
4549251881Speter   When reading multiple reps, i.e. a skip delta chain, you may provide
4550251881Speter   non-NULL FILE_HINT and REV_HINT.  (If FILE_HINT is not NULL, in the first
4551251881Speter   call it should be a pointer to NULL.)  The function will use these variables
4552251881Speter   to store the previous call results and tries to re-use them.  This may
4553251881Speter   result in significant savings in I/O for packed files.
4554251881Speter */
4555251881Speterstatic svn_error_t *
4556251881Spetercreate_rep_state(struct rep_state **rep_state,
4557251881Speter                 struct rep_args **rep_args,
4558251881Speter                 apr_file_t **file_hint,
4559251881Speter                 svn_revnum_t *rev_hint,
4560251881Speter                 representation_t *rep,
4561251881Speter                 svn_fs_t *fs,
4562251881Speter                 apr_pool_t *pool)
4563251881Speter{
4564251881Speter  svn_error_t *err = create_rep_state_body(rep_state, rep_args,
4565251881Speter                                           file_hint, rev_hint,
4566251881Speter                                           rep, fs, pool);
4567251881Speter  if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
4568251881Speter    {
4569251881Speter      fs_fs_data_t *ffd = fs->fsap_data;
4570251881Speter
4571251881Speter      /* ### This always returns "-1" for transaction reps, because
4572251881Speter         ### this particular bit of code doesn't know if the rep is
4573251881Speter         ### stored in the protorev or in the mutable area (for props
4574251881Speter         ### or dir contents).  It is pretty rare for FSFS to *read*
4575251881Speter         ### from the protorev file, though, so this is probably OK.
4576251881Speter         ### And anyone going to debug corruption errors is probably
4577251881Speter         ### going to jump straight to this comment anyway! */
4578251881Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
4579251881Speter                               "Corrupt representation '%s'",
4580251881Speter                               rep
4581251881Speter                               ? representation_string(rep, ffd->format, TRUE,
4582251881Speter                                                       TRUE, pool)
4583251881Speter                               : "(null)");
4584251881Speter    }
4585251881Speter  /* ### Call representation_string() ? */
4586251881Speter  return svn_error_trace(err);
4587251881Speter}
4588251881Speter
4589251881Speterstruct rep_read_baton
4590251881Speter{
4591251881Speter  /* The FS from which we're reading. */
4592251881Speter  svn_fs_t *fs;
4593251881Speter
4594251881Speter  /* If not NULL, this is the base for the first delta window in rs_list */
4595251881Speter  svn_stringbuf_t *base_window;
4596251881Speter
4597251881Speter  /* The state of all prior delta representations. */
4598251881Speter  apr_array_header_t *rs_list;
4599251881Speter
4600251881Speter  /* The plaintext state, if there is a plaintext. */
4601251881Speter  struct rep_state *src_state;
4602251881Speter
4603251881Speter  /* The index of the current delta chunk, if we are reading a delta. */
4604251881Speter  int chunk_index;
4605251881Speter
4606251881Speter  /* The buffer where we store undeltified data. */
4607251881Speter  char *buf;
4608251881Speter  apr_size_t buf_pos;
4609251881Speter  apr_size_t buf_len;
4610251881Speter
4611251881Speter  /* A checksum context for summing the data read in order to verify it.
4612251881Speter     Note: we don't need to use the sha1 checksum because we're only doing
4613251881Speter     data verification, for which md5 is perfectly safe.  */
4614251881Speter  svn_checksum_ctx_t *md5_checksum_ctx;
4615251881Speter
4616251881Speter  svn_boolean_t checksum_finalized;
4617251881Speter
4618251881Speter  /* The stored checksum of the representation we are reading, its
4619251881Speter     length, and the amount we've read so far.  Some of this
4620251881Speter     information is redundant with rs_list and src_state, but it's
4621251881Speter     convenient for the checksumming code to have it here. */
4622251881Speter  svn_checksum_t *md5_checksum;
4623251881Speter
4624251881Speter  svn_filesize_t len;
4625251881Speter  svn_filesize_t off;
4626251881Speter
4627251881Speter  /* The key for the fulltext cache for this rep, if there is a
4628251881Speter     fulltext cache. */
4629251881Speter  pair_cache_key_t fulltext_cache_key;
4630251881Speter  /* The text we've been reading, if we're going to cache it. */
4631251881Speter  svn_stringbuf_t *current_fulltext;
4632251881Speter
4633251881Speter  /* Used for temporary allocations during the read. */
4634251881Speter  apr_pool_t *pool;
4635251881Speter
4636251881Speter  /* Pool used to store file handles and other data that is persistant
4637251881Speter     for the entire stream read. */
4638251881Speter  apr_pool_t *filehandle_pool;
4639251881Speter};
4640251881Speter
4641251881Speter/* Combine the name of the rev file in RS with the given OFFSET to form
4642251881Speter * a cache lookup key.  Allocations will be made from POOL.  May return
4643251881Speter * NULL if the key cannot be constructed. */
4644251881Speterstatic const char*
4645251881Speterget_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool)
4646251881Speter{
4647251881Speter  const char *name;
4648251881Speter  const char *last_part;
4649251881Speter  const char *name_last;
4650251881Speter
4651251881Speter  /* the rev file name containing the txdelta window.
4652251881Speter   * If this fails we are in serious trouble anyways.
4653251881Speter   * And if nobody else detects the problems, the file content checksum
4654251881Speter   * comparison _will_ find them.
4655251881Speter   */
4656251881Speter  if (apr_file_name_get(&name, rs->file))
4657251881Speter    return NULL;
4658251881Speter
4659251881Speter  /* Handle packed files as well by scanning backwards until we find the
4660251881Speter   * revision or pack number. */
4661251881Speter  name_last = name + strlen(name) - 1;
4662251881Speter  while (! svn_ctype_isdigit(*name_last))
4663251881Speter    --name_last;
4664251881Speter
4665251881Speter  last_part = name_last;
4666251881Speter  while (svn_ctype_isdigit(*last_part))
4667251881Speter    --last_part;
4668251881Speter
4669251881Speter  /* We must differentiate between packed files (as of today, the number
4670251881Speter   * is being followed by a dot) and non-packed files (followed by \0).
4671251881Speter   * Otherwise, there might be overlaps in the numbering range if the
4672251881Speter   * repo gets packed after caching the txdeltas of non-packed revs.
4673251881Speter   * => add the first non-digit char to the packed number. */
4674251881Speter  if (name_last[1] != '\0')
4675251881Speter    ++name_last;
4676251881Speter
4677251881Speter  /* copy one char MORE than the actual number to mark packed files,
4678251881Speter   * i.e. packed revision file content uses different key space then
4679251881Speter   * non-packed ones: keys for packed rev file content ends with a dot
4680251881Speter   * for non-packed rev files they end with a digit. */
4681251881Speter  name = apr_pstrndup(pool, last_part + 1, name_last - last_part);
4682251881Speter  return svn_fs_fs__combine_number_and_string(offset, name, pool);
4683251881Speter}
4684251881Speter
4685251881Speter/* Read the WINDOW_P for the rep state RS from the current FSFS session's
4686251881Speter * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4687251881Speter * cache has been given. If a cache is available IS_CACHED will inform
4688251881Speter * the caller about the success of the lookup. Allocations (of the window
4689251881Speter * in particualar) will be made from POOL.
4690251881Speter *
4691251881Speter * If the information could be found, put RS and the position within the
4692251881Speter * rev file into the same state as if the data had just been read from it.
4693251881Speter */
4694251881Speterstatic svn_error_t *
4695251881Speterget_cached_window(svn_txdelta_window_t **window_p,
4696251881Speter                  struct rep_state *rs,
4697251881Speter                  svn_boolean_t *is_cached,
4698251881Speter                  apr_pool_t *pool)
4699251881Speter{
4700251881Speter  if (! rs->window_cache)
4701251881Speter    {
4702251881Speter      /* txdelta window has not been enabled */
4703251881Speter      *is_cached = FALSE;
4704251881Speter    }
4705251881Speter  else
4706251881Speter    {
4707251881Speter      /* ask the cache for the desired txdelta window */
4708251881Speter      svn_fs_fs__txdelta_cached_window_t *cached_window;
4709251881Speter      SVN_ERR(svn_cache__get((void **) &cached_window,
4710251881Speter                             is_cached,
4711251881Speter                             rs->window_cache,
4712251881Speter                             get_window_key(rs, rs->off, pool),
4713251881Speter                             pool));
4714251881Speter
4715251881Speter      if (*is_cached)
4716251881Speter        {
4717251881Speter          /* found it. Pass it back to the caller. */
4718251881Speter          *window_p = cached_window->window;
4719251881Speter
4720251881Speter          /* manipulate the RS as if we just read the data */
4721251881Speter          rs->chunk_index++;
4722251881Speter          rs->off = cached_window->end_offset;
4723251881Speter
4724251881Speter          /* manipulate the rev file as if we just read from it */
4725251881Speter          SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4726251881Speter        }
4727251881Speter    }
4728251881Speter
4729251881Speter  return SVN_NO_ERROR;
4730251881Speter}
4731251881Speter
4732251881Speter/* Store the WINDOW read at OFFSET for the rep state RS in the current
4733251881Speter * FSFS session's cache. This will be a no-op if no cache has been given.
4734251881Speter * Temporary allocations will be made from SCRATCH_POOL. */
4735251881Speterstatic svn_error_t *
4736251881Speterset_cached_window(svn_txdelta_window_t *window,
4737251881Speter                  struct rep_state *rs,
4738251881Speter                  apr_off_t offset,
4739251881Speter                  apr_pool_t *scratch_pool)
4740251881Speter{
4741251881Speter  if (rs->window_cache)
4742251881Speter    {
4743251881Speter      /* store the window and the first offset _past_ it */
4744251881Speter      svn_fs_fs__txdelta_cached_window_t cached_window;
4745251881Speter
4746251881Speter      cached_window.window = window;
4747251881Speter      cached_window.end_offset = rs->off;
4748251881Speter
4749251881Speter      /* but key it with the start offset because that is the known state
4750251881Speter       * when we will look it up */
4751251881Speter      return svn_cache__set(rs->window_cache,
4752251881Speter                            get_window_key(rs, offset, scratch_pool),
4753251881Speter                            &cached_window,
4754251881Speter                            scratch_pool);
4755251881Speter    }
4756251881Speter
4757251881Speter  return SVN_NO_ERROR;
4758251881Speter}
4759251881Speter
4760251881Speter/* Read the WINDOW_P for the rep state RS from the current FSFS session's
4761251881Speter * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4762251881Speter * cache has been given. If a cache is available IS_CACHED will inform
4763251881Speter * the caller about the success of the lookup. Allocations (of the window
4764251881Speter * in particualar) will be made from POOL.
4765251881Speter */
4766251881Speterstatic svn_error_t *
4767251881Speterget_cached_combined_window(svn_stringbuf_t **window_p,
4768251881Speter                           struct rep_state *rs,
4769251881Speter                           svn_boolean_t *is_cached,
4770251881Speter                           apr_pool_t *pool)
4771251881Speter{
4772251881Speter  if (! rs->combined_cache)
4773251881Speter    {
4774251881Speter      /* txdelta window has not been enabled */
4775251881Speter      *is_cached = FALSE;
4776251881Speter    }
4777251881Speter  else
4778251881Speter    {
4779251881Speter      /* ask the cache for the desired txdelta window */
4780251881Speter      return svn_cache__get((void **)window_p,
4781251881Speter                            is_cached,
4782251881Speter                            rs->combined_cache,
4783251881Speter                            get_window_key(rs, rs->start, pool),
4784251881Speter                            pool);
4785251881Speter    }
4786251881Speter
4787251881Speter  return SVN_NO_ERROR;
4788251881Speter}
4789251881Speter
4790251881Speter/* Store the WINDOW read at OFFSET for the rep state RS in the current
4791251881Speter * FSFS session's cache. This will be a no-op if no cache has been given.
4792251881Speter * Temporary allocations will be made from SCRATCH_POOL. */
4793251881Speterstatic svn_error_t *
4794251881Speterset_cached_combined_window(svn_stringbuf_t *window,
4795251881Speter                           struct rep_state *rs,
4796251881Speter                           apr_off_t offset,
4797251881Speter                           apr_pool_t *scratch_pool)
4798251881Speter{
4799251881Speter  if (rs->combined_cache)
4800251881Speter    {
4801251881Speter      /* but key it with the start offset because that is the known state
4802251881Speter       * when we will look it up */
4803251881Speter      return svn_cache__set(rs->combined_cache,
4804251881Speter                            get_window_key(rs, offset, scratch_pool),
4805251881Speter                            window,
4806251881Speter                            scratch_pool);
4807251881Speter    }
4808251881Speter
4809251881Speter  return SVN_NO_ERROR;
4810251881Speter}
4811251881Speter
4812251881Speter/* Build an array of rep_state structures in *LIST giving the delta
4813251881Speter   reps from first_rep to a plain-text or self-compressed rep.  Set
4814251881Speter   *SRC_STATE to the plain-text rep we find at the end of the chain,
4815251881Speter   or to NULL if the final delta representation is self-compressed.
4816251881Speter   The representation to start from is designated by filesystem FS, id
4817251881Speter   ID, and representation REP.
4818251881Speter   Also, set *WINDOW_P to the base window content for *LIST, if it
4819251881Speter   could be found in cache. Otherwise, *LIST will contain the base
4820251881Speter   representation for the whole delta chain.
4821251881Speter   Finally, return the expanded size of the representation in
4822251881Speter   *EXPANDED_SIZE. It will take care of cases where only the on-disk
4823251881Speter   size is known.  */
4824251881Speterstatic svn_error_t *
4825251881Speterbuild_rep_list(apr_array_header_t **list,
4826251881Speter               svn_stringbuf_t **window_p,
4827251881Speter               struct rep_state **src_state,
4828251881Speter               svn_filesize_t *expanded_size,
4829251881Speter               svn_fs_t *fs,
4830251881Speter               representation_t *first_rep,
4831251881Speter               apr_pool_t *pool)
4832251881Speter{
4833251881Speter  representation_t rep;
4834251881Speter  struct rep_state *rs = NULL;
4835251881Speter  struct rep_args *rep_args;
4836251881Speter  svn_boolean_t is_cached = FALSE;
4837251881Speter  apr_file_t *last_file = NULL;
4838251881Speter  svn_revnum_t last_revision;
4839251881Speter
4840251881Speter  *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
4841251881Speter  rep = *first_rep;
4842251881Speter
4843251881Speter  /* The value as stored in the data struct.
4844251881Speter     0 is either for unknown length or actually zero length. */
4845251881Speter  *expanded_size = first_rep->expanded_size;
4846251881Speter
4847251881Speter  /* for the top-level rep, we need the rep_args */
4848251881Speter  SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4849251881Speter                           &last_revision, &rep, fs, pool));
4850251881Speter
4851251881Speter  /* Unknown size or empty representation?
4852251881Speter     That implies the this being the first iteration.
4853251881Speter     Usually size equals on-disk size, except for empty,
4854251881Speter     compressed representations (delta, size = 4).
4855251881Speter     Please note that for all non-empty deltas have
4856251881Speter     a 4-byte header _plus_ some data. */
4857251881Speter  if (*expanded_size == 0)
4858251881Speter    if (! rep_args->is_delta || first_rep->size != 4)
4859251881Speter      *expanded_size = first_rep->size;
4860251881Speter
4861251881Speter  while (1)
4862251881Speter    {
4863251881Speter      /* fetch state, if that has not been done already */
4864251881Speter      if (!rs)
4865251881Speter        SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4866251881Speter                                &last_revision, &rep, fs, pool));
4867251881Speter
4868251881Speter      SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
4869251881Speter      if (is_cached)
4870251881Speter        {
4871251881Speter          /* We already have a reconstructed window in our cache.
4872251881Speter             Write a pseudo rep_state with the full length. */
4873251881Speter          rs->off = rs->start;
4874251881Speter          rs->end = rs->start + (*window_p)->len;
4875251881Speter          *src_state = rs;
4876251881Speter          return SVN_NO_ERROR;
4877251881Speter        }
4878251881Speter
4879251881Speter      if (!rep_args->is_delta)
4880251881Speter        {
4881251881Speter          /* This is a plaintext, so just return the current rep_state. */
4882251881Speter          *src_state = rs;
4883251881Speter          return SVN_NO_ERROR;
4884251881Speter        }
4885251881Speter
4886251881Speter      /* Push this rep onto the list.  If it's self-compressed, we're done. */
4887251881Speter      APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
4888251881Speter      if (rep_args->is_delta_vs_empty)
4889251881Speter        {
4890251881Speter          *src_state = NULL;
4891251881Speter          return SVN_NO_ERROR;
4892251881Speter        }
4893251881Speter
4894251881Speter      rep.revision = rep_args->base_revision;
4895251881Speter      rep.offset = rep_args->base_offset;
4896251881Speter      rep.size = rep_args->base_length;
4897251881Speter      rep.txn_id = NULL;
4898251881Speter
4899251881Speter      rs = NULL;
4900251881Speter    }
4901251881Speter}
4902251881Speter
4903251881Speter
4904251881Speter/* Create a rep_read_baton structure for node revision NODEREV in
4905251881Speter   filesystem FS and store it in *RB_P.  If FULLTEXT_CACHE_KEY is not
4906251881Speter   NULL, it is the rep's key in the fulltext cache, and a stringbuf
4907251881Speter   must be allocated to store the text.  Perform all allocations in
4908251881Speter   POOL.  If rep is mutable, it must be for file contents. */
4909251881Speterstatic svn_error_t *
4910251881Speterrep_read_get_baton(struct rep_read_baton **rb_p,
4911251881Speter                   svn_fs_t *fs,
4912251881Speter                   representation_t *rep,
4913251881Speter                   pair_cache_key_t fulltext_cache_key,
4914251881Speter                   apr_pool_t *pool)
4915251881Speter{
4916251881Speter  struct rep_read_baton *b;
4917251881Speter
4918251881Speter  b = apr_pcalloc(pool, sizeof(*b));
4919251881Speter  b->fs = fs;
4920251881Speter  b->base_window = NULL;
4921251881Speter  b->chunk_index = 0;
4922251881Speter  b->buf = NULL;
4923251881Speter  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
4924251881Speter  b->checksum_finalized = FALSE;
4925251881Speter  b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
4926251881Speter  b->len = rep->expanded_size;
4927251881Speter  b->off = 0;
4928251881Speter  b->fulltext_cache_key = fulltext_cache_key;
4929251881Speter  b->pool = svn_pool_create(pool);
4930251881Speter  b->filehandle_pool = svn_pool_create(pool);
4931251881Speter
4932251881Speter  SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
4933251881Speter                         &b->src_state, &b->len, fs, rep,
4934251881Speter                         b->filehandle_pool));
4935251881Speter
4936251881Speter  if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
4937251881Speter    b->current_fulltext = svn_stringbuf_create_ensure
4938251881Speter                            ((apr_size_t)b->len,
4939251881Speter                             b->filehandle_pool);
4940251881Speter  else
4941251881Speter    b->current_fulltext = NULL;
4942251881Speter
4943251881Speter  /* Save our output baton. */
4944251881Speter  *rb_p = b;
4945251881Speter
4946251881Speter  return SVN_NO_ERROR;
4947251881Speter}
4948251881Speter
4949251881Speter/* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
4950251881Speter   window into *NWIN. */
4951251881Speterstatic svn_error_t *
4952251881Speterread_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
4953251881Speter                  struct rep_state *rs, apr_pool_t *pool)
4954251881Speter{
4955251881Speter  svn_stream_t *stream;
4956251881Speter  svn_boolean_t is_cached;
4957251881Speter  apr_off_t old_offset;
4958251881Speter
4959251881Speter  SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
4960251881Speter
4961251881Speter  /* RS->FILE may be shared between RS instances -> make sure we point
4962251881Speter   * to the right data. */
4963251881Speter  SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4964251881Speter
4965251881Speter  /* Skip windows to reach the current chunk if we aren't there yet. */
4966251881Speter  while (rs->chunk_index < this_chunk)
4967251881Speter    {
4968251881Speter      SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
4969251881Speter      rs->chunk_index++;
4970251881Speter      SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4971251881Speter      if (rs->off >= rs->end)
4972251881Speter        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4973251881Speter                                _("Reading one svndiff window read "
4974251881Speter                                  "beyond the end of the "
4975251881Speter                                  "representation"));
4976251881Speter    }
4977251881Speter
4978251881Speter  /* Read the next window. But first, try to find it in the cache. */
4979251881Speter  SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool));
4980251881Speter  if (is_cached)
4981251881Speter    return SVN_NO_ERROR;
4982251881Speter
4983251881Speter  /* Actually read the next window. */
4984251881Speter  old_offset = rs->off;
4985251881Speter  stream = svn_stream_from_aprfile2(rs->file, TRUE, pool);
4986251881Speter  SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
4987251881Speter  rs->chunk_index++;
4988251881Speter  SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4989251881Speter
4990251881Speter  if (rs->off > rs->end)
4991251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4992251881Speter                            _("Reading one svndiff window read beyond "
4993251881Speter                              "the end of the representation"));
4994251881Speter
4995251881Speter  /* the window has not been cached before, thus cache it now
4996251881Speter   * (if caching is used for them at all) */
4997251881Speter  return set_cached_window(*nwin, rs, old_offset, pool);
4998251881Speter}
4999251881Speter
5000251881Speter/* Read SIZE bytes from the representation RS and return it in *NWIN. */
5001251881Speterstatic svn_error_t *
5002251881Speterread_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs,
5003251881Speter                  apr_size_t size, apr_pool_t *pool)
5004251881Speter{
5005251881Speter  /* RS->FILE may be shared between RS instances -> make sure we point
5006251881Speter   * to the right data. */
5007251881Speter  SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
5008251881Speter
5009251881Speter  /* Read the plain data. */
5010251881Speter  *nwin = svn_stringbuf_create_ensure(size, pool);
5011251881Speter  SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL,
5012251881Speter                                 pool));
5013251881Speter  (*nwin)->data[size] = 0;
5014251881Speter
5015251881Speter  /* Update RS. */
5016251881Speter  rs->off += (apr_off_t)size;
5017251881Speter
5018251881Speter  return SVN_NO_ERROR;
5019251881Speter}
5020251881Speter
5021251881Speter/* Get the undeltified window that is a result of combining all deltas
5022251881Speter   from the current desired representation identified in *RB with its
5023251881Speter   base representation.  Store the window in *RESULT. */
5024251881Speterstatic svn_error_t *
5025251881Speterget_combined_window(svn_stringbuf_t **result,
5026251881Speter                    struct rep_read_baton *rb)
5027251881Speter{
5028251881Speter  apr_pool_t *pool, *new_pool, *window_pool;
5029251881Speter  int i;
5030251881Speter  svn_txdelta_window_t *window;
5031251881Speter  apr_array_header_t *windows;
5032251881Speter  svn_stringbuf_t *source, *buf = rb->base_window;
5033251881Speter  struct rep_state *rs;
5034251881Speter
5035251881Speter  /* Read all windows that we need to combine. This is fine because
5036251881Speter     the size of each window is relatively small (100kB) and skip-
5037251881Speter     delta limits the number of deltas in a chain to well under 100.
5038251881Speter     Stop early if one of them does not depend on its predecessors. */
5039251881Speter  window_pool = svn_pool_create(rb->pool);
5040251881Speter  windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
5041251881Speter  for (i = 0; i < rb->rs_list->nelts; ++i)
5042251881Speter    {
5043251881Speter      rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5044251881Speter      SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool));
5045251881Speter
5046251881Speter      APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
5047251881Speter      if (window->src_ops == 0)
5048251881Speter        {
5049251881Speter          ++i;
5050251881Speter          break;
5051251881Speter        }
5052251881Speter    }
5053251881Speter
5054251881Speter  /* Combine in the windows from the other delta reps. */
5055251881Speter  pool = svn_pool_create(rb->pool);
5056251881Speter  for (--i; i >= 0; --i)
5057251881Speter    {
5058251881Speter
5059251881Speter      rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5060251881Speter      window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
5061251881Speter
5062251881Speter      /* Maybe, we've got a PLAIN start representation.  If we do, read
5063251881Speter         as much data from it as the needed for the txdelta window's source
5064251881Speter         view.
5065251881Speter         Note that BUF / SOURCE may only be NULL in the first iteration. */
5066251881Speter      source = buf;
5067251881Speter      if (source == NULL && rb->src_state != NULL)
5068251881Speter        SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len,
5069251881Speter                                  pool));
5070251881Speter
5071251881Speter      /* Combine this window with the current one. */
5072251881Speter      new_pool = svn_pool_create(rb->pool);
5073251881Speter      buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
5074251881Speter      buf->len = window->tview_len;
5075251881Speter
5076251881Speter      svn_txdelta_apply_instructions(window, source ? source->data : NULL,
5077251881Speter                                     buf->data, &buf->len);
5078251881Speter      if (buf->len != window->tview_len)
5079251881Speter        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5080251881Speter                                _("svndiff window length is "
5081251881Speter                                  "corrupt"));
5082251881Speter
5083251881Speter      /* Cache windows only if the whole rep content could be read as a
5084251881Speter         single chunk.  Only then will no other chunk need a deeper RS
5085251881Speter         list than the cached chunk. */
5086251881Speter      if ((rb->chunk_index == 0) && (rs->off == rs->end))
5087251881Speter        SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
5088251881Speter
5089251881Speter      /* Cycle pools so that we only need to hold three windows at a time. */
5090251881Speter      svn_pool_destroy(pool);
5091251881Speter      pool = new_pool;
5092251881Speter    }
5093251881Speter
5094251881Speter  svn_pool_destroy(window_pool);
5095251881Speter
5096251881Speter  *result = buf;
5097251881Speter  return SVN_NO_ERROR;
5098251881Speter}
5099251881Speter
5100251881Speter/* Returns whether or not the expanded fulltext of the file is cachable
5101251881Speter * based on its size SIZE.  The decision depends on the cache used by RB.
5102251881Speter */
5103251881Speterstatic svn_boolean_t
5104251881Speterfulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
5105251881Speter{
5106251881Speter  return (size < APR_SIZE_MAX)
5107251881Speter      && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
5108251881Speter}
5109251881Speter
5110251881Speter/* Close method used on streams returned by read_representation().
5111251881Speter */
5112251881Speterstatic svn_error_t *
5113251881Speterrep_read_contents_close(void *baton)
5114251881Speter{
5115251881Speter  struct rep_read_baton *rb = baton;
5116251881Speter
5117251881Speter  svn_pool_destroy(rb->pool);
5118251881Speter  svn_pool_destroy(rb->filehandle_pool);
5119251881Speter
5120251881Speter  return SVN_NO_ERROR;
5121251881Speter}
5122251881Speter
5123251881Speter/* Return the next *LEN bytes of the rep and store them in *BUF. */
5124251881Speterstatic svn_error_t *
5125251881Speterget_contents(struct rep_read_baton *rb,
5126251881Speter             char *buf,
5127251881Speter             apr_size_t *len)
5128251881Speter{
5129251881Speter  apr_size_t copy_len, remaining = *len;
5130251881Speter  char *cur = buf;
5131251881Speter  struct rep_state *rs;
5132251881Speter
5133251881Speter  /* Special case for when there are no delta reps, only a plain
5134251881Speter     text. */
5135251881Speter  if (rb->rs_list->nelts == 0)
5136251881Speter    {
5137251881Speter      copy_len = remaining;
5138251881Speter      rs = rb->src_state;
5139251881Speter
5140251881Speter      if (rb->base_window != NULL)
5141251881Speter        {
5142251881Speter          /* We got the desired rep directly from the cache.
5143251881Speter             This is where we need the pseudo rep_state created
5144251881Speter             by build_rep_list(). */
5145251881Speter          apr_size_t offset = (apr_size_t)(rs->off - rs->start);
5146251881Speter          if (copy_len + offset > rb->base_window->len)
5147251881Speter            copy_len = offset < rb->base_window->len
5148251881Speter                     ? rb->base_window->len - offset
5149251881Speter                     : 0ul;
5150251881Speter
5151251881Speter          memcpy (cur, rb->base_window->data + offset, copy_len);
5152251881Speter        }
5153251881Speter      else
5154251881Speter        {
5155251881Speter          if (((apr_off_t) copy_len) > rs->end - rs->off)
5156251881Speter            copy_len = (apr_size_t) (rs->end - rs->off);
5157251881Speter          SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
5158251881Speter                                         NULL, rb->pool));
5159251881Speter        }
5160251881Speter
5161251881Speter      rs->off += copy_len;
5162251881Speter      *len = copy_len;
5163251881Speter      return SVN_NO_ERROR;
5164251881Speter    }
5165251881Speter
5166251881Speter  while (remaining > 0)
5167251881Speter    {
5168251881Speter      /* If we have buffered data from a previous chunk, use that. */
5169251881Speter      if (rb->buf)
5170251881Speter        {
5171251881Speter          /* Determine how much to copy from the buffer. */
5172251881Speter          copy_len = rb->buf_len - rb->buf_pos;
5173251881Speter          if (copy_len > remaining)
5174251881Speter            copy_len = remaining;
5175251881Speter
5176251881Speter          /* Actually copy the data. */
5177251881Speter          memcpy(cur, rb->buf + rb->buf_pos, copy_len);
5178251881Speter          rb->buf_pos += copy_len;
5179251881Speter          cur += copy_len;
5180251881Speter          remaining -= copy_len;
5181251881Speter
5182251881Speter          /* If the buffer is all used up, clear it and empty the
5183251881Speter             local pool. */
5184251881Speter          if (rb->buf_pos == rb->buf_len)
5185251881Speter            {
5186251881Speter              svn_pool_clear(rb->pool);
5187251881Speter              rb->buf = NULL;
5188251881Speter            }
5189251881Speter        }
5190251881Speter      else
5191251881Speter        {
5192251881Speter          svn_stringbuf_t *sbuf = NULL;
5193251881Speter
5194251881Speter          rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
5195251881Speter          if (rs->off == rs->end)
5196251881Speter            break;
5197251881Speter
5198251881Speter          /* Get more buffered data by evaluating a chunk. */
5199251881Speter          SVN_ERR(get_combined_window(&sbuf, rb));
5200251881Speter
5201251881Speter          rb->chunk_index++;
5202251881Speter          rb->buf_len = sbuf->len;
5203251881Speter          rb->buf = sbuf->data;
5204251881Speter          rb->buf_pos = 0;
5205251881Speter        }
5206251881Speter    }
5207251881Speter
5208251881Speter  *len = cur - buf;
5209251881Speter
5210251881Speter  return SVN_NO_ERROR;
5211251881Speter}
5212251881Speter
5213251881Speter/* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
5214251881Speter   representation and store them in *BUF.  Sum as we read and verify
5215251881Speter   the MD5 sum at the end. */
5216251881Speterstatic svn_error_t *
5217251881Speterrep_read_contents(void *baton,
5218251881Speter                  char *buf,
5219251881Speter                  apr_size_t *len)
5220251881Speter{
5221251881Speter  struct rep_read_baton *rb = baton;
5222251881Speter
5223251881Speter  /* Get the next block of data. */
5224251881Speter  SVN_ERR(get_contents(rb, buf, len));
5225251881Speter
5226251881Speter  if (rb->current_fulltext)
5227251881Speter    svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
5228251881Speter
5229251881Speter  /* Perform checksumming.  We want to check the checksum as soon as
5230251881Speter     the last byte of data is read, in case the caller never performs
5231251881Speter     a short read, but we don't want to finalize the MD5 context
5232251881Speter     twice. */
5233251881Speter  if (!rb->checksum_finalized)
5234251881Speter    {
5235251881Speter      SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
5236251881Speter      rb->off += *len;
5237251881Speter      if (rb->off == rb->len)
5238251881Speter        {
5239251881Speter          svn_checksum_t *md5_checksum;
5240251881Speter
5241251881Speter          rb->checksum_finalized = TRUE;
5242251881Speter          SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
5243251881Speter                                     rb->pool));
5244251881Speter          if (!svn_checksum_match(md5_checksum, rb->md5_checksum))
5245251881Speter            return svn_error_create(SVN_ERR_FS_CORRUPT,
5246251881Speter                    svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum,
5247251881Speter                        rb->pool,
5248251881Speter                        _("Checksum mismatch while reading representation")),
5249251881Speter                    NULL);
5250251881Speter        }
5251251881Speter    }
5252251881Speter
5253251881Speter  if (rb->off == rb->len && rb->current_fulltext)
5254251881Speter    {
5255251881Speter      fs_fs_data_t *ffd = rb->fs->fsap_data;
5256251881Speter      SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
5257251881Speter                             rb->current_fulltext, rb->pool));
5258251881Speter      rb->current_fulltext = NULL;
5259251881Speter    }
5260251881Speter
5261251881Speter  return SVN_NO_ERROR;
5262251881Speter}
5263251881Speter
5264251881Speter
5265251881Speter/* Return a stream in *CONTENTS_P that will read the contents of a
5266251881Speter   representation stored at the location given by REP.  Appropriate
5267251881Speter   for any kind of immutable representation, but only for file
5268251881Speter   contents (not props or directory contents) in mutable
5269251881Speter   representations.
5270251881Speter
5271251881Speter   If REP is NULL, the representation is assumed to be empty, and the
5272251881Speter   empty stream is returned.
5273251881Speter*/
5274251881Speterstatic svn_error_t *
5275251881Speterread_representation(svn_stream_t **contents_p,
5276251881Speter                    svn_fs_t *fs,
5277251881Speter                    representation_t *rep,
5278251881Speter                    apr_pool_t *pool)
5279251881Speter{
5280251881Speter  if (! rep)
5281251881Speter    {
5282251881Speter      *contents_p = svn_stream_empty(pool);
5283251881Speter    }
5284251881Speter  else
5285251881Speter    {
5286251881Speter      fs_fs_data_t *ffd = fs->fsap_data;
5287251881Speter      pair_cache_key_t fulltext_cache_key = { 0 };
5288251881Speter      svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
5289251881Speter      struct rep_read_baton *rb;
5290251881Speter
5291251881Speter      fulltext_cache_key.revision = rep->revision;
5292251881Speter      fulltext_cache_key.second = rep->offset;
5293251881Speter      if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5294251881Speter          && fulltext_size_is_cachable(ffd, len))
5295251881Speter        {
5296251881Speter          svn_stringbuf_t *fulltext;
5297251881Speter          svn_boolean_t is_cached;
5298251881Speter          SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
5299251881Speter                                 ffd->fulltext_cache, &fulltext_cache_key,
5300251881Speter                                 pool));
5301251881Speter          if (is_cached)
5302251881Speter            {
5303251881Speter              *contents_p = svn_stream_from_stringbuf(fulltext, pool);
5304251881Speter              return SVN_NO_ERROR;
5305251881Speter            }
5306251881Speter        }
5307251881Speter      else
5308251881Speter        fulltext_cache_key.revision = SVN_INVALID_REVNUM;
5309251881Speter
5310251881Speter      SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
5311251881Speter
5312251881Speter      *contents_p = svn_stream_create(rb, pool);
5313251881Speter      svn_stream_set_read(*contents_p, rep_read_contents);
5314251881Speter      svn_stream_set_close(*contents_p, rep_read_contents_close);
5315251881Speter    }
5316251881Speter
5317251881Speter  return SVN_NO_ERROR;
5318251881Speter}
5319251881Speter
5320251881Spetersvn_error_t *
5321251881Spetersvn_fs_fs__get_contents(svn_stream_t **contents_p,
5322251881Speter                        svn_fs_t *fs,
5323251881Speter                        node_revision_t *noderev,
5324251881Speter                        apr_pool_t *pool)
5325251881Speter{
5326251881Speter  return read_representation(contents_p, fs, noderev->data_rep, pool);
5327251881Speter}
5328251881Speter
5329251881Speter/* Baton used when reading delta windows. */
5330251881Speterstruct delta_read_baton
5331251881Speter{
5332251881Speter  struct rep_state *rs;
5333251881Speter  svn_checksum_t *checksum;
5334251881Speter};
5335251881Speter
5336251881Speter/* This implements the svn_txdelta_next_window_fn_t interface. */
5337251881Speterstatic svn_error_t *
5338251881Speterdelta_read_next_window(svn_txdelta_window_t **window, void *baton,
5339251881Speter                       apr_pool_t *pool)
5340251881Speter{
5341251881Speter  struct delta_read_baton *drb = baton;
5342251881Speter
5343251881Speter  if (drb->rs->off == drb->rs->end)
5344251881Speter    {
5345251881Speter      *window = NULL;
5346251881Speter      return SVN_NO_ERROR;
5347251881Speter    }
5348251881Speter
5349251881Speter  return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool);
5350251881Speter}
5351251881Speter
5352251881Speter/* This implements the svn_txdelta_md5_digest_fn_t interface. */
5353251881Speterstatic const unsigned char *
5354251881Speterdelta_read_md5_digest(void *baton)
5355251881Speter{
5356251881Speter  struct delta_read_baton *drb = baton;
5357251881Speter
5358251881Speter  if (drb->checksum->kind == svn_checksum_md5)
5359251881Speter    return drb->checksum->digest;
5360251881Speter  else
5361251881Speter    return NULL;
5362251881Speter}
5363251881Speter
5364251881Spetersvn_error_t *
5365251881Spetersvn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
5366251881Speter                                 svn_fs_t *fs,
5367251881Speter                                 node_revision_t *source,
5368251881Speter                                 node_revision_t *target,
5369251881Speter                                 apr_pool_t *pool)
5370251881Speter{
5371251881Speter  svn_stream_t *source_stream, *target_stream;
5372251881Speter
5373251881Speter  /* Try a shortcut: if the target is stored as a delta against the source,
5374251881Speter     then just use that delta. */
5375251881Speter  if (source && source->data_rep && target->data_rep)
5376251881Speter    {
5377251881Speter      struct rep_state *rep_state;
5378251881Speter      struct rep_args *rep_args;
5379251881Speter
5380251881Speter      /* Read target's base rep if any. */
5381251881Speter      SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
5382251881Speter                               target->data_rep, fs, pool));
5383262253Speter
5384262253Speter      /* If that matches source, then use this delta as is.
5385262253Speter         Note that we want an actual delta here.  E.g. a self-delta would
5386262253Speter         not be good enough. */
5387251881Speter      if (rep_args->is_delta
5388262253Speter          && rep_args->base_revision == source->data_rep->revision
5389262253Speter          && rep_args->base_offset == source->data_rep->offset)
5390251881Speter        {
5391251881Speter          /* Create the delta read baton. */
5392251881Speter          struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
5393251881Speter          drb->rs = rep_state;
5394251881Speter          drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum,
5395251881Speter                                           pool);
5396251881Speter          *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
5397251881Speter                                                delta_read_md5_digest, pool);
5398251881Speter          return SVN_NO_ERROR;
5399251881Speter        }
5400251881Speter      else
5401251881Speter        SVN_ERR(svn_io_file_close(rep_state->file, pool));
5402251881Speter    }
5403251881Speter
5404251881Speter  /* Read both fulltexts and construct a delta. */
5405251881Speter  if (source)
5406251881Speter    SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
5407251881Speter  else
5408251881Speter    source_stream = svn_stream_empty(pool);
5409251881Speter  SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
5410251881Speter
5411251881Speter  /* Because source and target stream will already verify their content,
5412251881Speter   * there is no need to do this once more.  In particular if the stream
5413251881Speter   * content is being fetched from cache. */
5414251881Speter  svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
5415251881Speter
5416251881Speter  return SVN_NO_ERROR;
5417251881Speter}
5418251881Speter
5419251881Speter/* Baton for cache_access_wrapper. Wraps the original parameters of
5420251881Speter * svn_fs_fs__try_process_file_content().
5421251881Speter */
5422251881Spetertypedef struct cache_access_wrapper_baton_t
5423251881Speter{
5424251881Speter  svn_fs_process_contents_func_t func;
5425251881Speter  void* baton;
5426251881Speter} cache_access_wrapper_baton_t;
5427251881Speter
5428251881Speter/* Wrapper to translate between svn_fs_process_contents_func_t and
5429251881Speter * svn_cache__partial_getter_func_t.
5430251881Speter */
5431251881Speterstatic svn_error_t *
5432251881Spetercache_access_wrapper(void **out,
5433251881Speter                     const void *data,
5434251881Speter                     apr_size_t data_len,
5435251881Speter                     void *baton,
5436251881Speter                     apr_pool_t *pool)
5437251881Speter{
5438251881Speter  cache_access_wrapper_baton_t *wrapper_baton = baton;
5439251881Speter
5440251881Speter  SVN_ERR(wrapper_baton->func((const unsigned char *)data,
5441251881Speter                              data_len - 1, /* cache adds terminating 0 */
5442251881Speter                              wrapper_baton->baton,
5443251881Speter                              pool));
5444251881Speter
5445251881Speter  /* non-NULL value to signal the calling cache that all went well */
5446251881Speter  *out = baton;
5447251881Speter
5448251881Speter  return SVN_NO_ERROR;
5449251881Speter}
5450251881Speter
5451251881Spetersvn_error_t *
5452251881Spetersvn_fs_fs__try_process_file_contents(svn_boolean_t *success,
5453251881Speter                                     svn_fs_t *fs,
5454251881Speter                                     node_revision_t *noderev,
5455251881Speter                                     svn_fs_process_contents_func_t processor,
5456251881Speter                                     void* baton,
5457251881Speter                                     apr_pool_t *pool)
5458251881Speter{
5459251881Speter  representation_t *rep = noderev->data_rep;
5460251881Speter  if (rep)
5461251881Speter    {
5462251881Speter      fs_fs_data_t *ffd = fs->fsap_data;
5463251881Speter      pair_cache_key_t fulltext_cache_key = { 0 };
5464251881Speter
5465251881Speter      fulltext_cache_key.revision = rep->revision;
5466251881Speter      fulltext_cache_key.second = rep->offset;
5467251881Speter      if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5468251881Speter          && fulltext_size_is_cachable(ffd, rep->expanded_size))
5469251881Speter        {
5470251881Speter          cache_access_wrapper_baton_t wrapper_baton;
5471251881Speter          void *dummy = NULL;
5472251881Speter
5473251881Speter          wrapper_baton.func = processor;
5474251881Speter          wrapper_baton.baton = baton;
5475251881Speter          return svn_cache__get_partial(&dummy, success,
5476251881Speter                                        ffd->fulltext_cache,
5477251881Speter                                        &fulltext_cache_key,
5478251881Speter                                        cache_access_wrapper,
5479251881Speter                                        &wrapper_baton,
5480251881Speter                                        pool);
5481251881Speter        }
5482251881Speter    }
5483251881Speter
5484251881Speter  *success = FALSE;
5485251881Speter  return SVN_NO_ERROR;
5486251881Speter}
5487251881Speter
5488251881Speter/* Fetch the contents of a directory into ENTRIES.  Values are stored
5489251881Speter   as filename to string mappings; further conversion is necessary to
5490251881Speter   convert them into svn_fs_dirent_t values. */
5491251881Speterstatic svn_error_t *
5492251881Speterget_dir_contents(apr_hash_t *entries,
5493251881Speter                 svn_fs_t *fs,
5494251881Speter                 node_revision_t *noderev,
5495251881Speter                 apr_pool_t *pool)
5496251881Speter{
5497251881Speter  svn_stream_t *contents;
5498251881Speter
5499251881Speter  if (noderev->data_rep && noderev->data_rep->txn_id)
5500251881Speter    {
5501251881Speter      const char *filename = path_txn_node_children(fs, noderev->id, pool);
5502251881Speter
5503251881Speter      /* The representation is mutable.  Read the old directory
5504251881Speter         contents from the mutable children file, followed by the
5505251881Speter         changes we've made in this transaction. */
5506251881Speter      SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
5507251881Speter      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5508251881Speter      SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
5509251881Speter      SVN_ERR(svn_stream_close(contents));
5510251881Speter    }
5511251881Speter  else if (noderev->data_rep)
5512251881Speter    {
5513251881Speter      /* use a temporary pool for temp objects.
5514251881Speter       * Also undeltify content before parsing it. Otherwise, we could only
5515251881Speter       * parse it byte-by-byte.
5516251881Speter       */
5517251881Speter      apr_pool_t *text_pool = svn_pool_create(pool);
5518251881Speter      apr_size_t len = noderev->data_rep->expanded_size
5519251881Speter                     ? (apr_size_t)noderev->data_rep->expanded_size
5520251881Speter                     : (apr_size_t)noderev->data_rep->size;
5521251881Speter      svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
5522251881Speter      text->len = len;
5523251881Speter
5524251881Speter      /* The representation is immutable.  Read it normally. */
5525251881Speter      SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool));
5526251881Speter      SVN_ERR(svn_stream_read(contents, text->data, &text->len));
5527251881Speter      SVN_ERR(svn_stream_close(contents));
5528251881Speter
5529251881Speter      /* de-serialize hash */
5530251881Speter      contents = svn_stream_from_stringbuf(text, text_pool);
5531251881Speter      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5532251881Speter
5533251881Speter      svn_pool_destroy(text_pool);
5534251881Speter    }
5535251881Speter
5536251881Speter  return SVN_NO_ERROR;
5537251881Speter}
5538251881Speter
5539251881Speter
5540251881Speterstatic const char *
5541251881Speterunparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
5542251881Speter                  apr_pool_t *pool)
5543251881Speter{
5544251881Speter  return apr_psprintf(pool, "%s %s",
5545251881Speter                      (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
5546251881Speter                      svn_fs_fs__id_unparse(id, pool)->data);
5547251881Speter}
5548251881Speter
5549251881Speter/* Given a hash ENTRIES of dirent structions, return a hash in
5550251881Speter   *STR_ENTRIES_P, that has svn_string_t as the values in the format
5551251881Speter   specified by the fs_fs directory contents file.  Perform
5552251881Speter   allocations in POOL. */
5553251881Speterstatic svn_error_t *
5554251881Speterunparse_dir_entries(apr_hash_t **str_entries_p,
5555251881Speter                    apr_hash_t *entries,
5556251881Speter                    apr_pool_t *pool)
5557251881Speter{
5558251881Speter  apr_hash_index_t *hi;
5559251881Speter
5560251881Speter  /* For now, we use a our own hash function to ensure that we get a
5561251881Speter   * (largely) stable order when serializing the data.  It also gives
5562251881Speter   * us some performance improvement.
5563251881Speter   *
5564251881Speter   * ### TODO ###
5565251881Speter   * Use some sorted or other fixed order data container.
5566251881Speter   */
5567251881Speter  *str_entries_p = svn_hash__make(pool);
5568251881Speter
5569251881Speter  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
5570251881Speter    {
5571251881Speter      const void *key;
5572251881Speter      apr_ssize_t klen;
5573251881Speter      svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
5574251881Speter      const char *new_val;
5575251881Speter
5576251881Speter      apr_hash_this(hi, &key, &klen, NULL);
5577251881Speter      new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
5578251881Speter      apr_hash_set(*str_entries_p, key, klen,
5579251881Speter                   svn_string_create(new_val, pool));
5580251881Speter    }
5581251881Speter
5582251881Speter  return SVN_NO_ERROR;
5583251881Speter}
5584251881Speter
5585251881Speter
5586251881Speter/* Given a hash STR_ENTRIES with values as svn_string_t as specified
5587251881Speter   in an FSFS directory contents listing, return a hash of dirents in
5588251881Speter   *ENTRIES_P.  Perform allocations in POOL. */
5589251881Speterstatic svn_error_t *
5590251881Speterparse_dir_entries(apr_hash_t **entries_p,
5591251881Speter                  apr_hash_t *str_entries,
5592251881Speter                  const char *unparsed_id,
5593251881Speter                  apr_pool_t *pool)
5594251881Speter{
5595251881Speter  apr_hash_index_t *hi;
5596251881Speter
5597251881Speter  *entries_p = apr_hash_make(pool);
5598251881Speter
5599251881Speter  /* Translate the string dir entries into real entries. */
5600251881Speter  for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
5601251881Speter    {
5602251881Speter      const char *name = svn__apr_hash_index_key(hi);
5603251881Speter      svn_string_t *str_val = svn__apr_hash_index_val(hi);
5604251881Speter      char *str, *last_str;
5605251881Speter      svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
5606251881Speter
5607251881Speter      last_str = apr_pstrdup(pool, str_val->data);
5608251881Speter      dirent->name = apr_pstrdup(pool, name);
5609251881Speter
5610251881Speter      str = svn_cstring_tokenize(" ", &last_str);
5611251881Speter      if (str == NULL)
5612251881Speter        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5613251881Speter                                 _("Directory entry corrupt in '%s'"),
5614251881Speter                                 unparsed_id);
5615251881Speter
5616251881Speter      if (strcmp(str, KIND_FILE) == 0)
5617251881Speter        {
5618251881Speter          dirent->kind = svn_node_file;
5619251881Speter        }
5620251881Speter      else if (strcmp(str, KIND_DIR) == 0)
5621251881Speter        {
5622251881Speter          dirent->kind = svn_node_dir;
5623251881Speter        }
5624251881Speter      else
5625251881Speter        {
5626251881Speter          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5627251881Speter                                   _("Directory entry corrupt in '%s'"),
5628251881Speter                                   unparsed_id);
5629251881Speter        }
5630251881Speter
5631251881Speter      str = svn_cstring_tokenize(" ", &last_str);
5632251881Speter      if (str == NULL)
5633251881Speter          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5634251881Speter                                   _("Directory entry corrupt in '%s'"),
5635251881Speter                                   unparsed_id);
5636251881Speter
5637251881Speter      dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
5638251881Speter
5639251881Speter      svn_hash_sets(*entries_p, dirent->name, dirent);
5640251881Speter    }
5641251881Speter
5642251881Speter  return SVN_NO_ERROR;
5643251881Speter}
5644251881Speter
5645251881Speter/* Return the cache object in FS responsible to storing the directory
5646251881Speter * the NODEREV. If none exists, return NULL. */
5647251881Speterstatic svn_cache__t *
5648251881Speterlocate_dir_cache(svn_fs_t *fs,
5649251881Speter                 node_revision_t *noderev)
5650251881Speter{
5651251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
5652251881Speter  return svn_fs_fs__id_txn_id(noderev->id)
5653251881Speter      ? ffd->txn_dir_cache
5654251881Speter      : ffd->dir_cache;
5655251881Speter}
5656251881Speter
5657251881Spetersvn_error_t *
5658251881Spetersvn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
5659251881Speter                            svn_fs_t *fs,
5660251881Speter                            node_revision_t *noderev,
5661251881Speter                            apr_pool_t *pool)
5662251881Speter{
5663251881Speter  const char *unparsed_id = NULL;
5664251881Speter  apr_hash_t *unparsed_entries, *parsed_entries;
5665251881Speter
5666251881Speter  /* find the cache we may use */
5667251881Speter  svn_cache__t *cache = locate_dir_cache(fs, noderev);
5668251881Speter  if (cache)
5669251881Speter    {
5670251881Speter      svn_boolean_t found;
5671251881Speter
5672251881Speter      unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
5673251881Speter      SVN_ERR(svn_cache__get((void **) entries_p, &found, cache,
5674251881Speter                             unparsed_id, pool));
5675251881Speter      if (found)
5676251881Speter        return SVN_NO_ERROR;
5677251881Speter    }
5678251881Speter
5679251881Speter  /* Read in the directory hash. */
5680251881Speter  unparsed_entries = apr_hash_make(pool);
5681251881Speter  SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
5682251881Speter  SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
5683251881Speter                            unparsed_id, pool));
5684251881Speter
5685251881Speter  /* Update the cache, if we are to use one. */
5686251881Speter  if (cache)
5687251881Speter    SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool));
5688251881Speter
5689251881Speter  *entries_p = parsed_entries;
5690251881Speter  return SVN_NO_ERROR;
5691251881Speter}
5692251881Speter
5693251881Spetersvn_error_t *
5694251881Spetersvn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
5695251881Speter                                  svn_fs_t *fs,
5696251881Speter                                  node_revision_t *noderev,
5697251881Speter                                  const char *name,
5698251881Speter                                  apr_pool_t *result_pool,
5699251881Speter                                  apr_pool_t *scratch_pool)
5700251881Speter{
5701251881Speter  svn_boolean_t found = FALSE;
5702251881Speter
5703251881Speter  /* find the cache we may use */
5704251881Speter  svn_cache__t *cache = locate_dir_cache(fs, noderev);
5705251881Speter  if (cache)
5706251881Speter    {
5707251881Speter      const char *unparsed_id =
5708251881Speter        svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data;
5709251881Speter
5710251881Speter      /* Cache lookup. */
5711251881Speter      SVN_ERR(svn_cache__get_partial((void **)dirent,
5712251881Speter                                     &found,
5713251881Speter                                     cache,
5714251881Speter                                     unparsed_id,
5715251881Speter                                     svn_fs_fs__extract_dir_entry,
5716251881Speter                                     (void*)name,
5717251881Speter                                     result_pool));
5718251881Speter    }
5719251881Speter
5720251881Speter  /* fetch data from disk if we did not find it in the cache */
5721251881Speter  if (! found)
5722251881Speter    {
5723251881Speter      apr_hash_t *entries;
5724251881Speter      svn_fs_dirent_t *entry;
5725251881Speter      svn_fs_dirent_t *entry_copy = NULL;
5726251881Speter
5727251881Speter      /* read the dir from the file system. It will probably be put it
5728251881Speter         into the cache for faster lookup in future calls. */
5729251881Speter      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
5730251881Speter                                          scratch_pool));
5731251881Speter
5732251881Speter      /* find desired entry and return a copy in POOL, if found */
5733251881Speter      entry = svn_hash_gets(entries, name);
5734251881Speter      if (entry != NULL)
5735251881Speter        {
5736251881Speter          entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
5737251881Speter          entry_copy->name = apr_pstrdup(result_pool, entry->name);
5738251881Speter          entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool);
5739251881Speter          entry_copy->kind = entry->kind;
5740251881Speter        }
5741251881Speter
5742251881Speter      *dirent = entry_copy;
5743251881Speter    }
5744251881Speter
5745251881Speter  return SVN_NO_ERROR;
5746251881Speter}
5747251881Speter
5748251881Spetersvn_error_t *
5749251881Spetersvn_fs_fs__get_proplist(apr_hash_t **proplist_p,
5750251881Speter                        svn_fs_t *fs,
5751251881Speter                        node_revision_t *noderev,
5752251881Speter                        apr_pool_t *pool)
5753251881Speter{
5754251881Speter  apr_hash_t *proplist;
5755251881Speter  svn_stream_t *stream;
5756251881Speter
5757251881Speter  if (noderev->prop_rep && noderev->prop_rep->txn_id)
5758251881Speter    {
5759251881Speter      const char *filename = path_txn_node_props(fs, noderev->id, pool);
5760251881Speter      proplist = apr_hash_make(pool);
5761251881Speter
5762251881Speter      SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
5763251881Speter      SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5764251881Speter      SVN_ERR(svn_stream_close(stream));
5765251881Speter    }
5766251881Speter  else if (noderev->prop_rep)
5767251881Speter    {
5768251881Speter      fs_fs_data_t *ffd = fs->fsap_data;
5769251881Speter      representation_t *rep = noderev->prop_rep;
5770251881Speter      pair_cache_key_t key = { 0 };
5771251881Speter
5772251881Speter      key.revision = rep->revision;
5773251881Speter      key.second = rep->offset;
5774251881Speter      if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5775251881Speter        {
5776251881Speter          svn_boolean_t is_cached;
5777251881Speter          SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
5778251881Speter                                 ffd->properties_cache, &key, pool));
5779251881Speter          if (is_cached)
5780251881Speter            return SVN_NO_ERROR;
5781251881Speter        }
5782251881Speter
5783251881Speter      proplist = apr_hash_make(pool);
5784251881Speter      SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
5785251881Speter      SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5786251881Speter      SVN_ERR(svn_stream_close(stream));
5787251881Speter
5788251881Speter      if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5789251881Speter        SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
5790251881Speter    }
5791251881Speter  else
5792251881Speter    {
5793251881Speter      /* return an empty prop list if the node doesn't have any props */
5794251881Speter      proplist = apr_hash_make(pool);
5795251881Speter    }
5796251881Speter
5797251881Speter  *proplist_p = proplist;
5798251881Speter
5799251881Speter  return SVN_NO_ERROR;
5800251881Speter}
5801251881Speter
5802251881Spetersvn_error_t *
5803251881Spetersvn_fs_fs__file_length(svn_filesize_t *length,
5804251881Speter                       node_revision_t *noderev,
5805251881Speter                       apr_pool_t *pool)
5806251881Speter{
5807251881Speter  if (noderev->data_rep)
5808251881Speter    *length = noderev->data_rep->expanded_size;
5809251881Speter  else
5810251881Speter    *length = 0;
5811251881Speter
5812251881Speter  return SVN_NO_ERROR;
5813251881Speter}
5814251881Speter
5815251881Spetersvn_boolean_t
5816251881Spetersvn_fs_fs__noderev_same_rep_key(representation_t *a,
5817251881Speter                                representation_t *b)
5818251881Speter{
5819251881Speter  if (a == b)
5820251881Speter    return TRUE;
5821251881Speter
5822251881Speter  if (a == NULL || b == NULL)
5823251881Speter    return FALSE;
5824251881Speter
5825251881Speter  if (a->offset != b->offset)
5826251881Speter    return FALSE;
5827251881Speter
5828251881Speter  if (a->revision != b->revision)
5829251881Speter    return FALSE;
5830251881Speter
5831251881Speter  if (a->uniquifier == b->uniquifier)
5832251881Speter    return TRUE;
5833251881Speter
5834251881Speter  if (a->uniquifier == NULL || b->uniquifier == NULL)
5835251881Speter    return FALSE;
5836251881Speter
5837251881Speter  return strcmp(a->uniquifier, b->uniquifier) == 0;
5838251881Speter}
5839251881Speter
5840251881Spetersvn_error_t *
5841251881Spetersvn_fs_fs__file_checksum(svn_checksum_t **checksum,
5842251881Speter                         node_revision_t *noderev,
5843251881Speter                         svn_checksum_kind_t kind,
5844251881Speter                         apr_pool_t *pool)
5845251881Speter{
5846251881Speter  if (noderev->data_rep)
5847251881Speter    {
5848251881Speter      switch(kind)
5849251881Speter        {
5850251881Speter          case svn_checksum_md5:
5851251881Speter            *checksum = svn_checksum_dup(noderev->data_rep->md5_checksum,
5852251881Speter                                         pool);
5853251881Speter            break;
5854251881Speter          case svn_checksum_sha1:
5855251881Speter            *checksum = svn_checksum_dup(noderev->data_rep->sha1_checksum,
5856251881Speter                                         pool);
5857251881Speter            break;
5858251881Speter          default:
5859251881Speter            *checksum = NULL;
5860251881Speter        }
5861251881Speter    }
5862251881Speter  else
5863251881Speter    *checksum = NULL;
5864251881Speter
5865251881Speter  return SVN_NO_ERROR;
5866251881Speter}
5867251881Speter
5868251881Speterrepresentation_t *
5869251881Spetersvn_fs_fs__rep_copy(representation_t *rep,
5870251881Speter                    apr_pool_t *pool)
5871251881Speter{
5872251881Speter  representation_t *rep_new;
5873251881Speter
5874251881Speter  if (rep == NULL)
5875251881Speter    return NULL;
5876251881Speter
5877251881Speter  rep_new = apr_pcalloc(pool, sizeof(*rep_new));
5878251881Speter
5879251881Speter  memcpy(rep_new, rep, sizeof(*rep_new));
5880251881Speter  rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
5881251881Speter  rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
5882251881Speter  rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier);
5883251881Speter
5884251881Speter  return rep_new;
5885251881Speter}
5886251881Speter
5887251881Speter/* Merge the internal-use-only CHANGE into a hash of public-FS
5888251881Speter   svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
5889251881Speter   single summarical (is that real word?) change per path.  Also keep
5890251881Speter   the COPYFROM_CACHE up to date with new adds and replaces.  */
5891251881Speterstatic svn_error_t *
5892251881Speterfold_change(apr_hash_t *changes,
5893251881Speter            const change_t *change,
5894251881Speter            apr_hash_t *copyfrom_cache)
5895251881Speter{
5896251881Speter  apr_pool_t *pool = apr_hash_pool_get(changes);
5897251881Speter  svn_fs_path_change2_t *old_change, *new_change;
5898251881Speter  const char *path;
5899251881Speter  apr_size_t path_len = strlen(change->path);
5900251881Speter
5901251881Speter  if ((old_change = apr_hash_get(changes, change->path, path_len)))
5902251881Speter    {
5903251881Speter      /* This path already exists in the hash, so we have to merge
5904251881Speter         this change into the already existing one. */
5905251881Speter
5906251881Speter      /* Sanity check:  only allow NULL node revision ID in the
5907251881Speter         `reset' case. */
5908251881Speter      if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
5909251881Speter        return svn_error_create
5910251881Speter          (SVN_ERR_FS_CORRUPT, NULL,
5911251881Speter           _("Missing required node revision ID"));
5912251881Speter
5913251881Speter      /* Sanity check: we should be talking about the same node
5914251881Speter         revision ID as our last change except where the last change
5915251881Speter         was a deletion. */
5916251881Speter      if (change->noderev_id
5917251881Speter          && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
5918251881Speter          && (old_change->change_kind != svn_fs_path_change_delete))
5919251881Speter        return svn_error_create
5920251881Speter          (SVN_ERR_FS_CORRUPT, NULL,
5921251881Speter           _("Invalid change ordering: new node revision ID "
5922251881Speter             "without delete"));
5923251881Speter
5924251881Speter      /* Sanity check: an add, replacement, or reset must be the first
5925251881Speter         thing to follow a deletion. */
5926251881Speter      if ((old_change->change_kind == svn_fs_path_change_delete)
5927251881Speter          && (! ((change->kind == svn_fs_path_change_replace)
5928251881Speter                 || (change->kind == svn_fs_path_change_reset)
5929251881Speter                 || (change->kind == svn_fs_path_change_add))))
5930251881Speter        return svn_error_create
5931251881Speter          (SVN_ERR_FS_CORRUPT, NULL,
5932251881Speter           _("Invalid change ordering: non-add change on deleted path"));
5933251881Speter
5934251881Speter      /* Sanity check: an add can't follow anything except
5935251881Speter         a delete or reset.  */
5936251881Speter      if ((change->kind == svn_fs_path_change_add)
5937251881Speter          && (old_change->change_kind != svn_fs_path_change_delete)
5938251881Speter          && (old_change->change_kind != svn_fs_path_change_reset))
5939251881Speter        return svn_error_create
5940251881Speter          (SVN_ERR_FS_CORRUPT, NULL,
5941251881Speter           _("Invalid change ordering: add change on preexisting path"));
5942251881Speter
5943251881Speter      /* Now, merge that change in. */
5944251881Speter      switch (change->kind)
5945251881Speter        {
5946251881Speter        case svn_fs_path_change_reset:
5947251881Speter          /* A reset here will simply remove the path change from the
5948251881Speter             hash. */
5949251881Speter          old_change = NULL;
5950251881Speter          break;
5951251881Speter
5952251881Speter        case svn_fs_path_change_delete:
5953251881Speter          if (old_change->change_kind == svn_fs_path_change_add)
5954251881Speter            {
5955251881Speter              /* If the path was introduced in this transaction via an
5956251881Speter                 add, and we are deleting it, just remove the path
5957251881Speter                 altogether. */
5958251881Speter              old_change = NULL;
5959251881Speter            }
5960251881Speter          else
5961251881Speter            {
5962251881Speter              /* A deletion overrules all previous changes. */
5963251881Speter              old_change->change_kind = svn_fs_path_change_delete;
5964251881Speter              old_change->text_mod = change->text_mod;
5965251881Speter              old_change->prop_mod = change->prop_mod;
5966251881Speter              old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5967251881Speter              old_change->copyfrom_path = NULL;
5968251881Speter            }
5969251881Speter          break;
5970251881Speter
5971251881Speter        case svn_fs_path_change_add:
5972251881Speter        case svn_fs_path_change_replace:
5973251881Speter          /* An add at this point must be following a previous delete,
5974251881Speter             so treat it just like a replace. */
5975251881Speter          old_change->change_kind = svn_fs_path_change_replace;
5976251881Speter          old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
5977251881Speter                                                       pool);
5978251881Speter          old_change->text_mod = change->text_mod;
5979251881Speter          old_change->prop_mod = change->prop_mod;
5980251881Speter          if (change->copyfrom_rev == SVN_INVALID_REVNUM)
5981251881Speter            {
5982251881Speter              old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5983251881Speter              old_change->copyfrom_path = NULL;
5984251881Speter            }
5985251881Speter          else
5986251881Speter            {
5987251881Speter              old_change->copyfrom_rev = change->copyfrom_rev;
5988251881Speter              old_change->copyfrom_path = apr_pstrdup(pool,
5989251881Speter                                                      change->copyfrom_path);
5990251881Speter            }
5991251881Speter          break;
5992251881Speter
5993251881Speter        case svn_fs_path_change_modify:
5994251881Speter        default:
5995251881Speter          if (change->text_mod)
5996251881Speter            old_change->text_mod = TRUE;
5997251881Speter          if (change->prop_mod)
5998251881Speter            old_change->prop_mod = TRUE;
5999251881Speter          break;
6000251881Speter        }
6001251881Speter
6002251881Speter      /* Point our new_change to our (possibly modified) old_change. */
6003251881Speter      new_change = old_change;
6004251881Speter    }
6005251881Speter  else
6006251881Speter    {
6007251881Speter      /* This change is new to the hash, so make a new public change
6008251881Speter         structure from the internal one (in the hash's pool), and dup
6009251881Speter         the path into the hash's pool, too. */
6010251881Speter      new_change = apr_pcalloc(pool, sizeof(*new_change));
6011251881Speter      new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
6012251881Speter      new_change->change_kind = change->kind;
6013251881Speter      new_change->text_mod = change->text_mod;
6014251881Speter      new_change->prop_mod = change->prop_mod;
6015251881Speter      /* In FSFS, copyfrom_known is *always* true, since we've always
6016251881Speter       * stored copyfroms in changed paths lists. */
6017251881Speter      new_change->copyfrom_known = TRUE;
6018251881Speter      if (change->copyfrom_rev != SVN_INVALID_REVNUM)
6019251881Speter        {
6020251881Speter          new_change->copyfrom_rev = change->copyfrom_rev;
6021251881Speter          new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path);
6022251881Speter        }
6023251881Speter      else
6024251881Speter        {
6025251881Speter          new_change->copyfrom_rev = SVN_INVALID_REVNUM;
6026251881Speter          new_change->copyfrom_path = NULL;
6027251881Speter        }
6028251881Speter    }
6029251881Speter
6030251881Speter  if (new_change)
6031251881Speter    new_change->node_kind = change->node_kind;
6032251881Speter
6033251881Speter  /* Add (or update) this path.
6034251881Speter
6035251881Speter     Note: this key might already be present, and it would be nice to
6036251881Speter     re-use its value, but there is no way to fetch it. The API makes no
6037251881Speter     guarantees that this (new) key will not be retained. Thus, we (again)
6038251881Speter     copy the key into the target pool to ensure a proper lifetime.  */
6039251881Speter  path = apr_pstrmemdup(pool, change->path, path_len);
6040251881Speter  apr_hash_set(changes, path, path_len, new_change);
6041251881Speter
6042251881Speter  /* Update the copyfrom cache, if any. */
6043251881Speter  if (copyfrom_cache)
6044251881Speter    {
6045251881Speter      apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache);
6046251881Speter      const char *copyfrom_string = NULL, *copyfrom_key = path;
6047251881Speter      if (new_change)
6048251881Speter        {
6049251881Speter          if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev))
6050251881Speter            copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
6051251881Speter                                           new_change->copyfrom_rev,
6052251881Speter                                           new_change->copyfrom_path);
6053251881Speter          else
6054251881Speter            copyfrom_string = "";
6055251881Speter        }
6056251881Speter      /* We need to allocate a copy of the key in the copyfrom_pool if
6057251881Speter       * we're not doing a deletion and if it isn't already there. */
6058251881Speter      if (   copyfrom_string
6059251881Speter          && (   ! apr_hash_count(copyfrom_cache)
6060251881Speter              || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len)))
6061251881Speter        copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len);
6062251881Speter
6063251881Speter      apr_hash_set(copyfrom_cache, copyfrom_key, path_len,
6064251881Speter                   copyfrom_string);
6065251881Speter    }
6066251881Speter
6067251881Speter  return SVN_NO_ERROR;
6068251881Speter}
6069251881Speter
6070251881Speter/* The 256 is an arbitrary size large enough to hold the node id and the
6071251881Speter * various flags. */
6072251881Speter#define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
6073251881Speter
6074251881Speter/* Read the next entry in the changes record from file FILE and store
6075251881Speter   the resulting change in *CHANGE_P.  If there is no next record,
6076251881Speter   store NULL there.  Perform all allocations from POOL. */
6077251881Speterstatic svn_error_t *
6078251881Speterread_change(change_t **change_p,
6079251881Speter            apr_file_t *file,
6080251881Speter            apr_pool_t *pool)
6081251881Speter{
6082251881Speter  char buf[MAX_CHANGE_LINE_LEN];
6083251881Speter  apr_size_t len = sizeof(buf);
6084251881Speter  change_t *change;
6085251881Speter  char *str, *last_str = buf, *kind_str;
6086251881Speter  svn_error_t *err;
6087251881Speter
6088251881Speter  /* Default return value. */
6089251881Speter  *change_p = NULL;
6090251881Speter
6091251881Speter  err = svn_io_read_length_line(file, buf, &len, pool);
6092251881Speter
6093251881Speter  /* Check for a blank line. */
6094251881Speter  if (err || (len == 0))
6095251881Speter    {
6096251881Speter      if (err && APR_STATUS_IS_EOF(err->apr_err))
6097251881Speter        {
6098251881Speter          svn_error_clear(err);
6099251881Speter          return SVN_NO_ERROR;
6100251881Speter        }
6101251881Speter      if ((len == 0) && (! err))
6102251881Speter        return SVN_NO_ERROR;
6103251881Speter      return svn_error_trace(err);
6104251881Speter    }
6105251881Speter
6106251881Speter  change = apr_pcalloc(pool, sizeof(*change));
6107251881Speter
6108251881Speter  /* Get the node-id of the change. */
6109251881Speter  str = svn_cstring_tokenize(" ", &last_str);
6110251881Speter  if (str == NULL)
6111251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6112251881Speter                            _("Invalid changes line in rev-file"));
6113251881Speter
6114251881Speter  change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
6115251881Speter  if (change->noderev_id == NULL)
6116251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6117251881Speter                            _("Invalid changes line in rev-file"));
6118251881Speter
6119251881Speter  /* Get the change type. */
6120251881Speter  str = svn_cstring_tokenize(" ", &last_str);
6121251881Speter  if (str == NULL)
6122251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6123251881Speter                            _("Invalid changes line in rev-file"));
6124251881Speter
6125251881Speter  /* Don't bother to check the format number before looking for
6126251881Speter   * node-kinds: just read them if you find them. */
6127251881Speter  change->node_kind = svn_node_unknown;
6128251881Speter  kind_str = strchr(str, '-');
6129251881Speter  if (kind_str)
6130251881Speter    {
6131251881Speter      /* Cap off the end of "str" (the action). */
6132251881Speter      *kind_str = '\0';
6133251881Speter      kind_str++;
6134251881Speter      if (strcmp(kind_str, KIND_FILE) == 0)
6135251881Speter        change->node_kind = svn_node_file;
6136251881Speter      else if (strcmp(kind_str, KIND_DIR) == 0)
6137251881Speter        change->node_kind = svn_node_dir;
6138251881Speter      else
6139251881Speter        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6140251881Speter                                _("Invalid changes line in rev-file"));
6141251881Speter    }
6142251881Speter
6143251881Speter  if (strcmp(str, ACTION_MODIFY) == 0)
6144251881Speter    {
6145251881Speter      change->kind = svn_fs_path_change_modify;
6146251881Speter    }
6147251881Speter  else if (strcmp(str, ACTION_ADD) == 0)
6148251881Speter    {
6149251881Speter      change->kind = svn_fs_path_change_add;
6150251881Speter    }
6151251881Speter  else if (strcmp(str, ACTION_DELETE) == 0)
6152251881Speter    {
6153251881Speter      change->kind = svn_fs_path_change_delete;
6154251881Speter    }
6155251881Speter  else if (strcmp(str, ACTION_REPLACE) == 0)
6156251881Speter    {
6157251881Speter      change->kind = svn_fs_path_change_replace;
6158251881Speter    }
6159251881Speter  else if (strcmp(str, ACTION_RESET) == 0)
6160251881Speter    {
6161251881Speter      change->kind = svn_fs_path_change_reset;
6162251881Speter    }
6163251881Speter  else
6164251881Speter    {
6165251881Speter      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6166251881Speter                              _("Invalid change kind in rev file"));
6167251881Speter    }
6168251881Speter
6169251881Speter  /* Get the text-mod flag. */
6170251881Speter  str = svn_cstring_tokenize(" ", &last_str);
6171251881Speter  if (str == NULL)
6172251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6173251881Speter                            _("Invalid changes line in rev-file"));
6174251881Speter
6175251881Speter  if (strcmp(str, FLAG_TRUE) == 0)
6176251881Speter    {
6177251881Speter      change->text_mod = TRUE;
6178251881Speter    }
6179251881Speter  else if (strcmp(str, FLAG_FALSE) == 0)
6180251881Speter    {
6181251881Speter      change->text_mod = FALSE;
6182251881Speter    }
6183251881Speter  else
6184251881Speter    {
6185251881Speter      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6186251881Speter                              _("Invalid text-mod flag in rev-file"));
6187251881Speter    }
6188251881Speter
6189251881Speter  /* Get the prop-mod flag. */
6190251881Speter  str = svn_cstring_tokenize(" ", &last_str);
6191251881Speter  if (str == NULL)
6192251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6193251881Speter                            _("Invalid changes line in rev-file"));
6194251881Speter
6195251881Speter  if (strcmp(str, FLAG_TRUE) == 0)
6196251881Speter    {
6197251881Speter      change->prop_mod = TRUE;
6198251881Speter    }
6199251881Speter  else if (strcmp(str, FLAG_FALSE) == 0)
6200251881Speter    {
6201251881Speter      change->prop_mod = FALSE;
6202251881Speter    }
6203251881Speter  else
6204251881Speter    {
6205251881Speter      return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6206251881Speter                              _("Invalid prop-mod flag in rev-file"));
6207251881Speter    }
6208251881Speter
6209251881Speter  /* Get the changed path. */
6210251881Speter  change->path = apr_pstrdup(pool, last_str);
6211251881Speter
6212251881Speter
6213251881Speter  /* Read the next line, the copyfrom line. */
6214251881Speter  len = sizeof(buf);
6215251881Speter  SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
6216251881Speter
6217251881Speter  if (len == 0)
6218251881Speter    {
6219251881Speter      change->copyfrom_rev = SVN_INVALID_REVNUM;
6220251881Speter      change->copyfrom_path = NULL;
6221251881Speter    }
6222251881Speter  else
6223251881Speter    {
6224251881Speter      last_str = buf;
6225251881Speter      str = svn_cstring_tokenize(" ", &last_str);
6226251881Speter      if (! str)
6227251881Speter        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6228251881Speter                                _("Invalid changes line in rev-file"));
6229251881Speter      change->copyfrom_rev = SVN_STR_TO_REV(str);
6230251881Speter
6231251881Speter      if (! last_str)
6232251881Speter        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6233251881Speter                                _("Invalid changes line in rev-file"));
6234251881Speter
6235251881Speter      change->copyfrom_path = apr_pstrdup(pool, last_str);
6236251881Speter    }
6237251881Speter
6238251881Speter  *change_p = change;
6239251881Speter
6240251881Speter  return SVN_NO_ERROR;
6241251881Speter}
6242251881Speter
6243251881Speter/* Examine all the changed path entries in CHANGES and store them in
6244251881Speter   *CHANGED_PATHS.  Folding is done to remove redundant or unnecessary
6245251881Speter   *data.  Store a hash of paths to copyfrom "REV PATH" strings in
6246251881Speter   COPYFROM_HASH if it is non-NULL.  If PREFOLDED is true, assume that
6247251881Speter   the changed-path entries have already been folded (by
6248251881Speter   write_final_changed_path_info) and may be out of order, so we shouldn't
6249251881Speter   remove children of replaced or deleted directories.  Do all
6250251881Speter   allocations in POOL. */
6251251881Speterstatic svn_error_t *
6252251881Speterprocess_changes(apr_hash_t *changed_paths,
6253251881Speter                apr_hash_t *copyfrom_cache,
6254251881Speter                apr_array_header_t *changes,
6255251881Speter                svn_boolean_t prefolded,
6256251881Speter                apr_pool_t *pool)
6257251881Speter{
6258251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
6259251881Speter  int i;
6260251881Speter
6261251881Speter  /* Read in the changes one by one, folding them into our local hash
6262251881Speter     as necessary. */
6263251881Speter
6264251881Speter  for (i = 0; i < changes->nelts; ++i)
6265251881Speter    {
6266251881Speter      change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
6267251881Speter
6268251881Speter      SVN_ERR(fold_change(changed_paths, change, copyfrom_cache));
6269251881Speter
6270251881Speter      /* Now, if our change was a deletion or replacement, we have to
6271251881Speter         blow away any changes thus far on paths that are (or, were)
6272251881Speter         children of this path.
6273251881Speter         ### i won't bother with another iteration pool here -- at
6274251881Speter         most we talking about a few extra dups of paths into what
6275251881Speter         is already a temporary subpool.
6276251881Speter      */
6277251881Speter
6278251881Speter      if (((change->kind == svn_fs_path_change_delete)
6279251881Speter           || (change->kind == svn_fs_path_change_replace))
6280251881Speter          && ! prefolded)
6281251881Speter        {
6282251881Speter          apr_hash_index_t *hi;
6283251881Speter
6284251881Speter          /* a potential child path must contain at least 2 more chars
6285251881Speter             (the path separator plus at least one char for the name).
6286251881Speter             Also, we should not assume that all paths have been normalized
6287251881Speter             i.e. some might have trailing path separators.
6288251881Speter          */
6289251881Speter          apr_ssize_t change_path_len = strlen(change->path);
6290251881Speter          apr_ssize_t min_child_len = change_path_len == 0
6291251881Speter                                    ? 1
6292251881Speter                                    : change->path[change_path_len-1] == '/'
6293251881Speter                                        ? change_path_len + 1
6294251881Speter                                        : change_path_len + 2;
6295251881Speter
6296251881Speter          /* CAUTION: This is the inner loop of an O(n^2) algorithm.
6297251881Speter             The number of changes to process may be >> 1000.
6298251881Speter             Therefore, keep the inner loop as tight as possible.
6299251881Speter          */
6300251881Speter          for (hi = apr_hash_first(iterpool, changed_paths);
6301251881Speter               hi;
6302251881Speter               hi = apr_hash_next(hi))
6303251881Speter            {
6304251881Speter              /* KEY is the path. */
6305251881Speter              const void *path;
6306251881Speter              apr_ssize_t klen;
6307251881Speter              apr_hash_this(hi, &path, &klen, NULL);
6308251881Speter
6309251881Speter              /* If we come across a child of our path, remove it.
6310251881Speter                 Call svn_dirent_is_child only if there is a chance that
6311251881Speter                 this is actually a sub-path.
6312251881Speter               */
6313251881Speter              if (   klen >= min_child_len
6314251881Speter                  && svn_dirent_is_child(change->path, path, iterpool))
6315251881Speter                apr_hash_set(changed_paths, path, klen, NULL);
6316251881Speter            }
6317251881Speter        }
6318251881Speter
6319251881Speter      /* Clear the per-iteration subpool. */
6320251881Speter      svn_pool_clear(iterpool);
6321251881Speter    }
6322251881Speter
6323251881Speter  /* Destroy the per-iteration subpool. */
6324251881Speter  svn_pool_destroy(iterpool);
6325251881Speter
6326251881Speter  return SVN_NO_ERROR;
6327251881Speter}
6328251881Speter
6329251881Speter/* Fetch all the changes from FILE and store them in *CHANGES.  Do all
6330251881Speter   allocations in POOL. */
6331251881Speterstatic svn_error_t *
6332251881Speterread_all_changes(apr_array_header_t **changes,
6333251881Speter                 apr_file_t *file,
6334251881Speter                 apr_pool_t *pool)
6335251881Speter{
6336251881Speter  change_t *change;
6337251881Speter
6338251881Speter  /* pre-allocate enough room for most change lists
6339251881Speter     (will be auto-expanded as necessary) */
6340251881Speter  *changes = apr_array_make(pool, 30, sizeof(change_t *));
6341251881Speter
6342251881Speter  SVN_ERR(read_change(&change, file, pool));
6343251881Speter  while (change)
6344251881Speter    {
6345251881Speter      APR_ARRAY_PUSH(*changes, change_t*) = change;
6346251881Speter      SVN_ERR(read_change(&change, file, pool));
6347251881Speter    }
6348251881Speter
6349251881Speter  return SVN_NO_ERROR;
6350251881Speter}
6351251881Speter
6352251881Spetersvn_error_t *
6353251881Spetersvn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
6354251881Speter                             svn_fs_t *fs,
6355251881Speter                             const char *txn_id,
6356251881Speter                             apr_pool_t *pool)
6357251881Speter{
6358251881Speter  apr_file_t *file;
6359251881Speter  apr_hash_t *changed_paths = apr_hash_make(pool);
6360251881Speter  apr_array_header_t *changes;
6361251881Speter  apr_pool_t *scratch_pool = svn_pool_create(pool);
6362251881Speter
6363251881Speter  SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
6364251881Speter                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6365251881Speter
6366251881Speter  SVN_ERR(read_all_changes(&changes, file, scratch_pool));
6367251881Speter  SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool));
6368251881Speter  svn_pool_destroy(scratch_pool);
6369251881Speter
6370251881Speter  SVN_ERR(svn_io_file_close(file, pool));
6371251881Speter
6372251881Speter  *changed_paths_p = changed_paths;
6373251881Speter
6374251881Speter  return SVN_NO_ERROR;
6375251881Speter}
6376251881Speter
6377251881Speter/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
6378251881Speter * Allocate the result in POOL.
6379251881Speter */
6380251881Speterstatic svn_error_t *
6381251881Speterget_changes(apr_array_header_t **changes,
6382251881Speter            svn_fs_t *fs,
6383251881Speter            svn_revnum_t rev,
6384251881Speter            apr_pool_t *pool)
6385251881Speter{
6386251881Speter  apr_off_t changes_offset;
6387251881Speter  apr_file_t *revision_file;
6388251881Speter  svn_boolean_t found;
6389251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
6390251881Speter
6391251881Speter  /* try cache lookup first */
6392251881Speter
6393251881Speter  if (ffd->changes_cache)
6394251881Speter    {
6395251881Speter      SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
6396251881Speter                             &rev, pool));
6397251881Speter      if (found)
6398251881Speter        return SVN_NO_ERROR;
6399251881Speter    }
6400251881Speter
6401251881Speter  /* read changes from revision file */
6402251881Speter
6403251881Speter  SVN_ERR(ensure_revision_exists(fs, rev, pool));
6404251881Speter
6405251881Speter  SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
6406251881Speter
6407251881Speter  SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs,
6408251881Speter                                  rev, pool));
6409251881Speter
6410251881Speter  SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
6411251881Speter  SVN_ERR(read_all_changes(changes, revision_file, pool));
6412251881Speter
6413251881Speter  SVN_ERR(svn_io_file_close(revision_file, pool));
6414251881Speter
6415251881Speter  /* cache for future reference */
6416251881Speter
6417251881Speter  if (ffd->changes_cache)
6418251881Speter    SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool));
6419251881Speter
6420251881Speter  return SVN_NO_ERROR;
6421251881Speter}
6422251881Speter
6423251881Speter
6424251881Spetersvn_error_t *
6425251881Spetersvn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
6426251881Speter                         svn_fs_t *fs,
6427251881Speter                         svn_revnum_t rev,
6428251881Speter                         apr_hash_t *copyfrom_cache,
6429251881Speter                         apr_pool_t *pool)
6430251881Speter{
6431251881Speter  apr_hash_t *changed_paths;
6432251881Speter  apr_array_header_t *changes;
6433251881Speter  apr_pool_t *scratch_pool = svn_pool_create(pool);
6434251881Speter
6435251881Speter  SVN_ERR(get_changes(&changes, fs, rev, scratch_pool));
6436251881Speter
6437251881Speter  changed_paths = svn_hash__make(pool);
6438251881Speter
6439251881Speter  SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes,
6440251881Speter                          TRUE, pool));
6441251881Speter  svn_pool_destroy(scratch_pool);
6442251881Speter
6443251881Speter  *changed_paths_p = changed_paths;
6444251881Speter
6445251881Speter  return SVN_NO_ERROR;
6446251881Speter}
6447251881Speter
6448251881Speter/* Copy a revision node-rev SRC into the current transaction TXN_ID in
6449251881Speter   the filesystem FS.  This is only used to create the root of a transaction.
6450251881Speter   Allocations are from POOL.  */
6451251881Speterstatic svn_error_t *
6452251881Spetercreate_new_txn_noderev_from_rev(svn_fs_t *fs,
6453251881Speter                                const char *txn_id,
6454251881Speter                                svn_fs_id_t *src,
6455251881Speter                                apr_pool_t *pool)
6456251881Speter{
6457251881Speter  node_revision_t *noderev;
6458251881Speter  const char *node_id, *copy_id;
6459251881Speter
6460251881Speter  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
6461251881Speter
6462251881Speter  if (svn_fs_fs__id_txn_id(noderev->id))
6463251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6464251881Speter                            _("Copying from transactions not allowed"));
6465251881Speter
6466251881Speter  noderev->predecessor_id = noderev->id;
6467251881Speter  noderev->predecessor_count++;
6468251881Speter  noderev->copyfrom_path = NULL;
6469251881Speter  noderev->copyfrom_rev = SVN_INVALID_REVNUM;
6470251881Speter
6471251881Speter  /* For the transaction root, the copyroot never changes. */
6472251881Speter
6473251881Speter  node_id = svn_fs_fs__id_node_id(noderev->id);
6474251881Speter  copy_id = svn_fs_fs__id_copy_id(noderev->id);
6475251881Speter  noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6476251881Speter
6477251881Speter  return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
6478251881Speter}
6479251881Speter
6480251881Speter/* A structure used by get_and_increment_txn_key_body(). */
6481251881Speterstruct get_and_increment_txn_key_baton {
6482251881Speter  svn_fs_t *fs;
6483251881Speter  char *txn_id;
6484251881Speter  apr_pool_t *pool;
6485251881Speter};
6486251881Speter
6487251881Speter/* Callback used in the implementation of create_txn_dir().  This gets
6488251881Speter   the current base 36 value in PATH_TXN_CURRENT and increments it.
6489251881Speter   It returns the original value by the baton. */
6490251881Speterstatic svn_error_t *
6491251881Speterget_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
6492251881Speter{
6493251881Speter  struct get_and_increment_txn_key_baton *cb = baton;
6494251881Speter  const char *txn_current_filename = path_txn_current(cb->fs, pool);
6495251881Speter  const char *tmp_filename;
6496251881Speter  char next_txn_id[MAX_KEY_SIZE+3];
6497251881Speter  apr_size_t len;
6498251881Speter
6499251881Speter  svn_stringbuf_t *buf;
6500251881Speter  SVN_ERR(read_content(&buf, txn_current_filename, cb->pool));
6501251881Speter
6502251881Speter  /* remove trailing newlines */
6503251881Speter  svn_stringbuf_strip_whitespace(buf);
6504251881Speter  cb->txn_id = buf->data;
6505251881Speter  len = buf->len;
6506251881Speter
6507251881Speter  /* Increment the key and add a trailing \n to the string so the
6508251881Speter     txn-current file has a newline in it. */
6509251881Speter  svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
6510251881Speter  next_txn_id[len] = '\n';
6511251881Speter  ++len;
6512251881Speter  next_txn_id[len] = '\0';
6513251881Speter
6514251881Speter  SVN_ERR(svn_io_write_unique(&tmp_filename,
6515251881Speter                              svn_dirent_dirname(txn_current_filename, pool),
6516251881Speter                              next_txn_id, len, svn_io_file_del_none, pool));
6517251881Speter  SVN_ERR(move_into_place(tmp_filename, txn_current_filename,
6518251881Speter                          txn_current_filename, pool));
6519251881Speter
6520251881Speter  return SVN_NO_ERROR;
6521251881Speter}
6522251881Speter
6523251881Speter/* Create a unique directory for a transaction in FS based on revision
6524251881Speter   REV.  Return the ID for this transaction in *ID_P.  Use a sequence
6525251881Speter   value in the transaction ID to prevent reuse of transaction IDs. */
6526251881Speterstatic svn_error_t *
6527251881Spetercreate_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6528251881Speter               apr_pool_t *pool)
6529251881Speter{
6530251881Speter  struct get_and_increment_txn_key_baton cb;
6531251881Speter  const char *txn_dir;
6532251881Speter
6533251881Speter  /* Get the current transaction sequence value, which is a base-36
6534251881Speter     number, from the txn-current file, and write an
6535251881Speter     incremented value back out to the file.  Place the revision
6536251881Speter     number the transaction is based off into the transaction id. */
6537251881Speter  cb.pool = pool;
6538251881Speter  cb.fs = fs;
6539251881Speter  SVN_ERR(with_txn_current_lock(fs,
6540251881Speter                                get_and_increment_txn_key_body,
6541251881Speter                                &cb,
6542251881Speter                                pool));
6543251881Speter  *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
6544251881Speter
6545251881Speter  txn_dir = svn_dirent_join_many(pool,
6546251881Speter                                 fs->path,
6547251881Speter                                 PATH_TXNS_DIR,
6548251881Speter                                 apr_pstrcat(pool, *id_p, PATH_EXT_TXN,
6549251881Speter                                             (char *)NULL),
6550251881Speter                                 NULL);
6551251881Speter
6552251881Speter  return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
6553251881Speter}
6554251881Speter
6555251881Speter/* Create a unique directory for a transaction in FS based on revision
6556251881Speter   REV.  Return the ID for this transaction in *ID_P.  This
6557251881Speter   implementation is used in svn 1.4 and earlier repositories and is
6558251881Speter   kept in 1.5 and greater to support the --pre-1.4-compatible and
6559251881Speter   --pre-1.5-compatible repository creation options.  Reused
6560251881Speter   transaction IDs are possible with this implementation. */
6561251881Speterstatic svn_error_t *
6562251881Spetercreate_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6563251881Speter                       apr_pool_t *pool)
6564251881Speter{
6565251881Speter  unsigned int i;
6566251881Speter  apr_pool_t *subpool;
6567251881Speter  const char *unique_path, *prefix;
6568251881Speter
6569251881Speter  /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
6570251881Speter  prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
6571251881Speter                                apr_psprintf(pool, "%ld", rev), NULL);
6572251881Speter
6573251881Speter  subpool = svn_pool_create(pool);
6574251881Speter  for (i = 1; i <= 99999; i++)
6575251881Speter    {
6576251881Speter      svn_error_t *err;
6577251881Speter
6578251881Speter      svn_pool_clear(subpool);
6579251881Speter      unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
6580251881Speter      err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
6581251881Speter      if (! err)
6582251881Speter        {
6583251881Speter          /* We succeeded.  Return the basename minus the ".txn" extension. */
6584251881Speter          const char *name = svn_dirent_basename(unique_path, subpool);
6585251881Speter          *id_p = apr_pstrndup(pool, name,
6586251881Speter                               strlen(name) - strlen(PATH_EXT_TXN));
6587251881Speter          svn_pool_destroy(subpool);
6588251881Speter          return SVN_NO_ERROR;
6589251881Speter        }
6590251881Speter      if (! APR_STATUS_IS_EEXIST(err->apr_err))
6591251881Speter        return svn_error_trace(err);
6592251881Speter      svn_error_clear(err);
6593251881Speter    }
6594251881Speter
6595251881Speter  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
6596251881Speter                           NULL,
6597251881Speter                           _("Unable to create transaction directory "
6598251881Speter                             "in '%s' for revision %ld"),
6599251881Speter                           svn_dirent_local_style(fs->path, pool),
6600251881Speter                           rev);
6601251881Speter}
6602251881Speter
6603251881Spetersvn_error_t *
6604251881Spetersvn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
6605251881Speter                      svn_fs_t *fs,
6606251881Speter                      svn_revnum_t rev,
6607251881Speter                      apr_pool_t *pool)
6608251881Speter{
6609251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
6610251881Speter  svn_fs_txn_t *txn;
6611251881Speter  svn_fs_id_t *root_id;
6612251881Speter
6613251881Speter  txn = apr_pcalloc(pool, sizeof(*txn));
6614251881Speter
6615251881Speter  /* Get the txn_id. */
6616251881Speter  if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
6617251881Speter    SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
6618251881Speter  else
6619251881Speter    SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
6620251881Speter
6621251881Speter  txn->fs = fs;
6622251881Speter  txn->base_rev = rev;
6623251881Speter
6624251881Speter  txn->vtable = &txn_vtable;
6625251881Speter  *txn_p = txn;
6626251881Speter
6627251881Speter  /* Create a new root node for this transaction. */
6628251881Speter  SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
6629251881Speter  SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
6630251881Speter
6631251881Speter  /* Create an empty rev file. */
6632251881Speter  SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
6633251881Speter                             pool));
6634251881Speter
6635251881Speter  /* Create an empty rev-lock file. */
6636251881Speter  SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
6637251881Speter                             pool));
6638251881Speter
6639251881Speter  /* Create an empty changes file. */
6640251881Speter  SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
6641251881Speter                             pool));
6642251881Speter
6643251881Speter  /* Create the next-ids file. */
6644251881Speter  return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
6645251881Speter                            pool);
6646251881Speter}
6647251881Speter
6648251881Speter/* Store the property list for transaction TXN_ID in PROPLIST.
6649251881Speter   Perform temporary allocations in POOL. */
6650251881Speterstatic svn_error_t *
6651251881Speterget_txn_proplist(apr_hash_t *proplist,
6652251881Speter                 svn_fs_t *fs,
6653251881Speter                 const char *txn_id,
6654251881Speter                 apr_pool_t *pool)
6655251881Speter{
6656251881Speter  svn_stream_t *stream;
6657251881Speter
6658251881Speter  /* Check for issue #3696. (When we find and fix the cause, we can change
6659251881Speter   * this to an assertion.) */
6660251881Speter  if (txn_id == NULL)
6661251881Speter    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
6662251881Speter                            _("Internal error: a null transaction id was "
6663251881Speter                              "passed to get_txn_proplist()"));
6664251881Speter
6665251881Speter  /* Open the transaction properties file. */
6666251881Speter  SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
6667251881Speter                                   pool, pool));
6668251881Speter
6669251881Speter  /* Read in the property list. */
6670251881Speter  SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
6671251881Speter
6672251881Speter  return svn_stream_close(stream);
6673251881Speter}
6674251881Speter
6675251881Spetersvn_error_t *
6676251881Spetersvn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
6677251881Speter                           const char *name,
6678251881Speter                           const svn_string_t *value,
6679251881Speter                           apr_pool_t *pool)
6680251881Speter{
6681251881Speter  apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
6682251881Speter  svn_prop_t prop;
6683251881Speter
6684251881Speter  prop.name = name;
6685251881Speter  prop.value = value;
6686251881Speter  APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6687251881Speter
6688251881Speter  return svn_fs_fs__change_txn_props(txn, props, pool);
6689251881Speter}
6690251881Speter
6691251881Spetersvn_error_t *
6692251881Spetersvn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
6693251881Speter                            const apr_array_header_t *props,
6694251881Speter                            apr_pool_t *pool)
6695251881Speter{
6696251881Speter  const char *txn_prop_filename;
6697251881Speter  svn_stringbuf_t *buf;
6698251881Speter  svn_stream_t *stream;
6699251881Speter  apr_hash_t *txn_prop = apr_hash_make(pool);
6700251881Speter  int i;
6701251881Speter  svn_error_t *err;
6702251881Speter
6703251881Speter  err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
6704251881Speter  /* Here - and here only - we need to deal with the possibility that the
6705251881Speter     transaction property file doesn't yet exist.  The rest of the
6706251881Speter     implementation assumes that the file exists, but we're called to set the
6707251881Speter     initial transaction properties as the transaction is being created. */
6708251881Speter  if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
6709251881Speter    svn_error_clear(err);
6710251881Speter  else if (err)
6711251881Speter    return svn_error_trace(err);
6712251881Speter
6713251881Speter  for (i = 0; i < props->nelts; i++)
6714251881Speter    {
6715251881Speter      svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
6716251881Speter
6717251881Speter      svn_hash_sets(txn_prop, prop->name, prop->value);
6718251881Speter    }
6719251881Speter
6720251881Speter  /* Create a new version of the file and write out the new props. */
6721251881Speter  /* Open the transaction properties file. */
6722251881Speter  buf = svn_stringbuf_create_ensure(1024, pool);
6723251881Speter  stream = svn_stream_from_stringbuf(buf, pool);
6724251881Speter  SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool));
6725251881Speter  SVN_ERR(svn_stream_close(stream));
6726251881Speter  SVN_ERR(svn_io_write_unique(&txn_prop_filename,
6727251881Speter                              path_txn_dir(txn->fs, txn->id, pool),
6728251881Speter                              buf->data,
6729251881Speter                              buf->len,
6730251881Speter                              svn_io_file_del_none,
6731251881Speter                              pool));
6732251881Speter  return svn_io_file_rename(txn_prop_filename,
6733251881Speter                            path_txn_props(txn->fs, txn->id, pool),
6734251881Speter                            pool);
6735251881Speter}
6736251881Speter
6737251881Spetersvn_error_t *
6738251881Spetersvn_fs_fs__get_txn(transaction_t **txn_p,
6739251881Speter                   svn_fs_t *fs,
6740251881Speter                   const char *txn_id,
6741251881Speter                   apr_pool_t *pool)
6742251881Speter{
6743251881Speter  transaction_t *txn;
6744251881Speter  node_revision_t *noderev;
6745251881Speter  svn_fs_id_t *root_id;
6746251881Speter
6747251881Speter  txn = apr_pcalloc(pool, sizeof(*txn));
6748251881Speter  txn->proplist = apr_hash_make(pool);
6749251881Speter
6750251881Speter  SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
6751251881Speter  root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
6752251881Speter
6753251881Speter  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
6754251881Speter
6755251881Speter  txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
6756251881Speter  txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
6757251881Speter  txn->copies = NULL;
6758251881Speter
6759251881Speter  *txn_p = txn;
6760251881Speter
6761251881Speter  return SVN_NO_ERROR;
6762251881Speter}
6763251881Speter
6764251881Speter/* Write out the currently available next node_id NODE_ID and copy_id
6765251881Speter   COPY_ID for transaction TXN_ID in filesystem FS.  The next node-id is
6766251881Speter   used both for creating new unique nodes for the given transaction, as
6767251881Speter   well as uniquifying representations.  Perform temporary allocations in
6768251881Speter   POOL. */
6769251881Speterstatic svn_error_t *
6770251881Speterwrite_next_ids(svn_fs_t *fs,
6771251881Speter               const char *txn_id,
6772251881Speter               const char *node_id,
6773251881Speter               const char *copy_id,
6774251881Speter               apr_pool_t *pool)
6775251881Speter{
6776251881Speter  apr_file_t *file;
6777251881Speter  svn_stream_t *out_stream;
6778251881Speter
6779251881Speter  SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6780251881Speter                           APR_WRITE | APR_TRUNCATE,
6781251881Speter                           APR_OS_DEFAULT, pool));
6782251881Speter
6783251881Speter  out_stream = svn_stream_from_aprfile2(file, TRUE, pool);
6784251881Speter
6785251881Speter  SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
6786251881Speter
6787251881Speter  SVN_ERR(svn_stream_close(out_stream));
6788251881Speter  return svn_io_file_close(file, pool);
6789251881Speter}
6790251881Speter
6791251881Speter/* Find out what the next unique node-id and copy-id are for
6792251881Speter   transaction TXN_ID in filesystem FS.  Store the results in *NODE_ID
6793251881Speter   and *COPY_ID.  The next node-id is used both for creating new unique
6794251881Speter   nodes for the given transaction, as well as uniquifying representations.
6795251881Speter   Perform all allocations in POOL. */
6796251881Speterstatic svn_error_t *
6797251881Speterread_next_ids(const char **node_id,
6798251881Speter              const char **copy_id,
6799251881Speter              svn_fs_t *fs,
6800251881Speter              const char *txn_id,
6801251881Speter              apr_pool_t *pool)
6802251881Speter{
6803251881Speter  apr_file_t *file;
6804251881Speter  char buf[MAX_KEY_SIZE*2+3];
6805251881Speter  apr_size_t limit;
6806251881Speter  char *str, *last_str = buf;
6807251881Speter
6808251881Speter  SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6809251881Speter                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6810251881Speter
6811251881Speter  limit = sizeof(buf);
6812251881Speter  SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
6813251881Speter
6814251881Speter  SVN_ERR(svn_io_file_close(file, pool));
6815251881Speter
6816251881Speter  /* Parse this into two separate strings. */
6817251881Speter
6818251881Speter  str = svn_cstring_tokenize(" ", &last_str);
6819251881Speter  if (! str)
6820251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6821251881Speter                            _("next-id file corrupt"));
6822251881Speter
6823251881Speter  *node_id = apr_pstrdup(pool, str);
6824251881Speter
6825251881Speter  str = svn_cstring_tokenize(" ", &last_str);
6826251881Speter  if (! str)
6827251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6828251881Speter                            _("next-id file corrupt"));
6829251881Speter
6830251881Speter  *copy_id = apr_pstrdup(pool, str);
6831251881Speter
6832251881Speter  return SVN_NO_ERROR;
6833251881Speter}
6834251881Speter
6835251881Speter/* Get a new and unique to this transaction node-id for transaction
6836251881Speter   TXN_ID in filesystem FS.  Store the new node-id in *NODE_ID_P.
6837251881Speter   Node-ids are guaranteed to be unique to this transction, but may
6838251881Speter   not necessarily be sequential.  Perform all allocations in POOL. */
6839251881Speterstatic svn_error_t *
6840251881Speterget_new_txn_node_id(const char **node_id_p,
6841251881Speter                    svn_fs_t *fs,
6842251881Speter                    const char *txn_id,
6843251881Speter                    apr_pool_t *pool)
6844251881Speter{
6845251881Speter  const char *cur_node_id, *cur_copy_id;
6846251881Speter  char *node_id;
6847251881Speter  apr_size_t len;
6848251881Speter
6849251881Speter  /* First read in the current next-ids file. */
6850251881Speter  SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
6851251881Speter
6852251881Speter  node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
6853251881Speter
6854251881Speter  len = strlen(cur_node_id);
6855251881Speter  svn_fs_fs__next_key(cur_node_id, &len, node_id);
6856251881Speter
6857251881Speter  SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
6858251881Speter
6859251881Speter  *node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL);
6860251881Speter
6861251881Speter  return SVN_NO_ERROR;
6862251881Speter}
6863251881Speter
6864251881Spetersvn_error_t *
6865251881Spetersvn_fs_fs__create_node(const svn_fs_id_t **id_p,
6866251881Speter                       svn_fs_t *fs,
6867251881Speter                       node_revision_t *noderev,
6868251881Speter                       const char *copy_id,
6869251881Speter                       const char *txn_id,
6870251881Speter                       apr_pool_t *pool)
6871251881Speter{
6872251881Speter  const char *node_id;
6873251881Speter  const svn_fs_id_t *id;
6874251881Speter
6875251881Speter  /* Get a new node-id for this node. */
6876251881Speter  SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
6877251881Speter
6878251881Speter  id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6879251881Speter
6880251881Speter  noderev->id = id;
6881251881Speter
6882251881Speter  SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
6883251881Speter
6884251881Speter  *id_p = id;
6885251881Speter
6886251881Speter  return SVN_NO_ERROR;
6887251881Speter}
6888251881Speter
6889251881Spetersvn_error_t *
6890251881Spetersvn_fs_fs__purge_txn(svn_fs_t *fs,
6891251881Speter                     const char *txn_id,
6892251881Speter                     apr_pool_t *pool)
6893251881Speter{
6894251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
6895251881Speter
6896251881Speter  /* Remove the shared transaction object associated with this transaction. */
6897251881Speter  SVN_ERR(purge_shared_txn(fs, txn_id, pool));
6898251881Speter  /* Remove the directory associated with this transaction. */
6899251881Speter  SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
6900251881Speter                             NULL, NULL, pool));
6901251881Speter  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
6902251881Speter    {
6903251881Speter      /* Delete protorev and its lock, which aren't in the txn
6904251881Speter         directory.  It's OK if they don't exist (for example, if this
6905251881Speter         is post-commit and the proto-rev has been moved into
6906251881Speter         place). */
6907251881Speter      SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool),
6908251881Speter                                  TRUE, pool));
6909251881Speter      SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool),
6910251881Speter                                  TRUE, pool));
6911251881Speter    }
6912251881Speter  return SVN_NO_ERROR;
6913251881Speter}
6914251881Speter
6915251881Speter
6916251881Spetersvn_error_t *
6917251881Spetersvn_fs_fs__abort_txn(svn_fs_txn_t *txn,
6918251881Speter                     apr_pool_t *pool)
6919251881Speter{
6920251881Speter  SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
6921251881Speter
6922251881Speter  /* Now, purge the transaction. */
6923251881Speter  SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
6924251881Speter            apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
6925251881Speter                         txn->id));
6926251881Speter
6927251881Speter  return SVN_NO_ERROR;
6928251881Speter}
6929251881Speter
6930251881Speter
6931251881Spetersvn_error_t *
6932251881Spetersvn_fs_fs__set_entry(svn_fs_t *fs,
6933251881Speter                     const char *txn_id,
6934251881Speter                     node_revision_t *parent_noderev,
6935251881Speter                     const char *name,
6936251881Speter                     const svn_fs_id_t *id,
6937251881Speter                     svn_node_kind_t kind,
6938251881Speter                     apr_pool_t *pool)
6939251881Speter{
6940251881Speter  representation_t *rep = parent_noderev->data_rep;
6941251881Speter  const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
6942251881Speter  apr_file_t *file;
6943251881Speter  svn_stream_t *out;
6944251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
6945251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
6946251881Speter
6947251881Speter  if (!rep || !rep->txn_id)
6948251881Speter    {
6949251881Speter      const char *unique_suffix;
6950251881Speter      apr_hash_t *entries;
6951251881Speter
6952251881Speter      /* Before we can modify the directory, we need to dump its old
6953251881Speter         contents into a mutable representation file. */
6954251881Speter      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
6955251881Speter                                          subpool));
6956251881Speter      SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
6957251881Speter      SVN_ERR(svn_io_file_open(&file, filename,
6958251881Speter                               APR_WRITE | APR_CREATE | APR_BUFFERED,
6959251881Speter                               APR_OS_DEFAULT, pool));
6960251881Speter      out = svn_stream_from_aprfile2(file, TRUE, pool);
6961251881Speter      SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
6962251881Speter
6963251881Speter      svn_pool_clear(subpool);
6964251881Speter
6965251881Speter      /* Mark the node-rev's data rep as mutable. */
6966251881Speter      rep = apr_pcalloc(pool, sizeof(*rep));
6967251881Speter      rep->revision = SVN_INVALID_REVNUM;
6968251881Speter      rep->txn_id = txn_id;
6969251881Speter      SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool));
6970251881Speter      rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix);
6971251881Speter      parent_noderev->data_rep = rep;
6972251881Speter      SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
6973251881Speter                                           parent_noderev, FALSE, pool));
6974251881Speter    }
6975251881Speter  else
6976251881Speter    {
6977251881Speter      /* The directory rep is already mutable, so just open it for append. */
6978251881Speter      SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
6979251881Speter                               APR_OS_DEFAULT, pool));
6980251881Speter      out = svn_stream_from_aprfile2(file, TRUE, pool);
6981251881Speter    }
6982251881Speter
6983251881Speter  /* if we have a directory cache for this transaction, update it */
6984251881Speter  if (ffd->txn_dir_cache)
6985251881Speter    {
6986251881Speter      /* build parameters: (name, new entry) pair */
6987251881Speter      const char *key =
6988251881Speter          svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
6989251881Speter      replace_baton_t baton;
6990251881Speter
6991251881Speter      baton.name = name;
6992251881Speter      baton.new_entry = NULL;
6993251881Speter
6994251881Speter      if (id)
6995251881Speter        {
6996251881Speter          baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
6997251881Speter          baton.new_entry->name = name;
6998251881Speter          baton.new_entry->kind = kind;
6999251881Speter          baton.new_entry->id = id;
7000251881Speter        }
7001251881Speter
7002251881Speter      /* actually update the cached directory (if cached) */
7003251881Speter      SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
7004251881Speter                                     svn_fs_fs__replace_dir_entry, &baton,
7005251881Speter                                     subpool));
7006251881Speter    }
7007251881Speter  svn_pool_clear(subpool);
7008251881Speter
7009251881Speter  /* Append an incremental hash entry for the entry change. */
7010251881Speter  if (id)
7011251881Speter    {
7012251881Speter      const char *val = unparse_dir_entry(kind, id, subpool);
7013251881Speter
7014251881Speter      SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n"
7015251881Speter                                "V %" APR_SIZE_T_FMT "\n%s\n",
7016251881Speter                                strlen(name), name,
7017251881Speter                                strlen(val), val));
7018251881Speter    }
7019251881Speter  else
7020251881Speter    {
7021251881Speter      SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
7022251881Speter                                strlen(name), name));
7023251881Speter    }
7024251881Speter
7025251881Speter  SVN_ERR(svn_io_file_close(file, subpool));
7026251881Speter  svn_pool_destroy(subpool);
7027251881Speter  return SVN_NO_ERROR;
7028251881Speter}
7029251881Speter
7030251881Speter/* Write a single change entry, path PATH, change CHANGE, and copyfrom
7031251881Speter   string COPYFROM, into the file specified by FILE.  Only include the
7032251881Speter   node kind field if INCLUDE_NODE_KIND is true.  All temporary
7033251881Speter   allocations are in POOL. */
7034251881Speterstatic svn_error_t *
7035251881Speterwrite_change_entry(apr_file_t *file,
7036251881Speter                   const char *path,
7037251881Speter                   svn_fs_path_change2_t *change,
7038251881Speter                   svn_boolean_t include_node_kind,
7039251881Speter                   apr_pool_t *pool)
7040251881Speter{
7041251881Speter  const char *idstr, *buf;
7042251881Speter  const char *change_string = NULL;
7043251881Speter  const char *kind_string = "";
7044251881Speter
7045251881Speter  switch (change->change_kind)
7046251881Speter    {
7047251881Speter    case svn_fs_path_change_modify:
7048251881Speter      change_string = ACTION_MODIFY;
7049251881Speter      break;
7050251881Speter    case svn_fs_path_change_add:
7051251881Speter      change_string = ACTION_ADD;
7052251881Speter      break;
7053251881Speter    case svn_fs_path_change_delete:
7054251881Speter      change_string = ACTION_DELETE;
7055251881Speter      break;
7056251881Speter    case svn_fs_path_change_replace:
7057251881Speter      change_string = ACTION_REPLACE;
7058251881Speter      break;
7059251881Speter    case svn_fs_path_change_reset:
7060251881Speter      change_string = ACTION_RESET;
7061251881Speter      break;
7062251881Speter    default:
7063251881Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7064251881Speter                               _("Invalid change type %d"),
7065251881Speter                               change->change_kind);
7066251881Speter    }
7067251881Speter
7068251881Speter  if (change->node_rev_id)
7069251881Speter    idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
7070251881Speter  else
7071251881Speter    idstr = ACTION_RESET;
7072251881Speter
7073251881Speter  if (include_node_kind)
7074251881Speter    {
7075251881Speter      SVN_ERR_ASSERT(change->node_kind == svn_node_dir
7076251881Speter                     || change->node_kind == svn_node_file);
7077251881Speter      kind_string = apr_psprintf(pool, "-%s",
7078251881Speter                                 change->node_kind == svn_node_dir
7079251881Speter                                 ? KIND_DIR : KIND_FILE);
7080251881Speter    }
7081251881Speter  buf = apr_psprintf(pool, "%s %s%s %s %s %s\n",
7082251881Speter                     idstr, change_string, kind_string,
7083251881Speter                     change->text_mod ? FLAG_TRUE : FLAG_FALSE,
7084251881Speter                     change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
7085251881Speter                     path);
7086251881Speter
7087251881Speter  SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7088251881Speter
7089251881Speter  if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
7090251881Speter    {
7091251881Speter      buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev,
7092251881Speter                         change->copyfrom_path);
7093251881Speter      SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7094251881Speter    }
7095251881Speter
7096251881Speter  return svn_io_file_write_full(file, "\n", 1, NULL, pool);
7097251881Speter}
7098251881Speter
7099251881Spetersvn_error_t *
7100251881Spetersvn_fs_fs__add_change(svn_fs_t *fs,
7101251881Speter                      const char *txn_id,
7102251881Speter                      const char *path,
7103251881Speter                      const svn_fs_id_t *id,
7104251881Speter                      svn_fs_path_change_kind_t change_kind,
7105251881Speter                      svn_boolean_t text_mod,
7106251881Speter                      svn_boolean_t prop_mod,
7107251881Speter                      svn_node_kind_t node_kind,
7108251881Speter                      svn_revnum_t copyfrom_rev,
7109251881Speter                      const char *copyfrom_path,
7110251881Speter                      apr_pool_t *pool)
7111251881Speter{
7112251881Speter  apr_file_t *file;
7113251881Speter  svn_fs_path_change2_t *change;
7114251881Speter
7115251881Speter  SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
7116251881Speter                           APR_APPEND | APR_WRITE | APR_CREATE
7117251881Speter                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
7118251881Speter
7119251881Speter  change = svn_fs__path_change_create_internal(id, change_kind, pool);
7120251881Speter  change->text_mod = text_mod;
7121251881Speter  change->prop_mod = prop_mod;
7122251881Speter  change->node_kind = node_kind;
7123251881Speter  change->copyfrom_rev = copyfrom_rev;
7124251881Speter  change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
7125251881Speter
7126251881Speter  SVN_ERR(write_change_entry(file, path, change, TRUE, pool));
7127251881Speter
7128251881Speter  return svn_io_file_close(file, pool);
7129251881Speter}
7130251881Speter
7131251881Speter/* This baton is used by the representation writing streams.  It keeps
7132251881Speter   track of the checksum information as well as the total size of the
7133251881Speter   representation so far. */
7134251881Speterstruct rep_write_baton
7135251881Speter{
7136251881Speter  /* The FS we are writing to. */
7137251881Speter  svn_fs_t *fs;
7138251881Speter
7139251881Speter  /* Actual file to which we are writing. */
7140251881Speter  svn_stream_t *rep_stream;
7141251881Speter
7142251881Speter  /* A stream from the delta combiner.  Data written here gets
7143251881Speter     deltified, then eventually written to rep_stream. */
7144251881Speter  svn_stream_t *delta_stream;
7145251881Speter
7146251881Speter  /* Where is this representation header stored. */
7147251881Speter  apr_off_t rep_offset;
7148251881Speter
7149251881Speter  /* Start of the actual data. */
7150251881Speter  apr_off_t delta_start;
7151251881Speter
7152251881Speter  /* How many bytes have been written to this rep already. */
7153251881Speter  svn_filesize_t rep_size;
7154251881Speter
7155251881Speter  /* The node revision for which we're writing out info. */
7156251881Speter  node_revision_t *noderev;
7157251881Speter
7158251881Speter  /* Actual output file. */
7159251881Speter  apr_file_t *file;
7160251881Speter  /* Lock 'cookie' used to unlock the output file once we've finished
7161251881Speter     writing to it. */
7162251881Speter  void *lockcookie;
7163251881Speter
7164251881Speter  svn_checksum_ctx_t *md5_checksum_ctx;
7165251881Speter  svn_checksum_ctx_t *sha1_checksum_ctx;
7166251881Speter
7167251881Speter  apr_pool_t *pool;
7168251881Speter
7169251881Speter  apr_pool_t *parent_pool;
7170251881Speter};
7171251881Speter
7172251881Speter/* Handler for the write method of the representation writable stream.
7173251881Speter   BATON is a rep_write_baton, DATA is the data to write, and *LEN is
7174251881Speter   the length of this data. */
7175251881Speterstatic svn_error_t *
7176251881Speterrep_write_contents(void *baton,
7177251881Speter                   const char *data,
7178251881Speter                   apr_size_t *len)
7179251881Speter{
7180251881Speter  struct rep_write_baton *b = baton;
7181251881Speter
7182251881Speter  SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
7183251881Speter  SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
7184251881Speter  b->rep_size += *len;
7185251881Speter
7186251881Speter  /* If we are writing a delta, use that stream. */
7187251881Speter  if (b->delta_stream)
7188251881Speter    return svn_stream_write(b->delta_stream, data, len);
7189251881Speter  else
7190251881Speter    return svn_stream_write(b->rep_stream, data, len);
7191251881Speter}
7192251881Speter
7193251881Speter/* Given a node-revision NODEREV in filesystem FS, return the
7194251881Speter   representation in *REP to use as the base for a text representation
7195251881Speter   delta if PROPS is FALSE.  If PROPS has been set, a suitable props
7196251881Speter   base representation will be returned.  Perform temporary allocations
7197251881Speter   in *POOL. */
7198251881Speterstatic svn_error_t *
7199251881Speterchoose_delta_base(representation_t **rep,
7200251881Speter                  svn_fs_t *fs,
7201251881Speter                  node_revision_t *noderev,
7202251881Speter                  svn_boolean_t props,
7203251881Speter                  apr_pool_t *pool)
7204251881Speter{
7205251881Speter  int count;
7206251881Speter  int walk;
7207251881Speter  node_revision_t *base;
7208251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
7209251881Speter  svn_boolean_t maybe_shared_rep = FALSE;
7210251881Speter
7211251881Speter  /* If we have no predecessors, then use the empty stream as a
7212251881Speter     base. */
7213251881Speter  if (! noderev->predecessor_count)
7214251881Speter    {
7215251881Speter      *rep = NULL;
7216251881Speter      return SVN_NO_ERROR;
7217251881Speter    }
7218251881Speter
7219251881Speter  /* Flip the rightmost '1' bit of the predecessor count to determine
7220251881Speter     which file rev (counting from 0) we want to use.  (To see why
7221251881Speter     count & (count - 1) unsets the rightmost set bit, think about how
7222251881Speter     you decrement a binary number.) */
7223251881Speter  count = noderev->predecessor_count;
7224251881Speter  count = count & (count - 1);
7225251881Speter
7226251881Speter  /* We use skip delta for limiting the number of delta operations
7227251881Speter     along very long node histories.  Close to HEAD however, we create
7228251881Speter     a linear history to minimize delta size.  */
7229251881Speter  walk = noderev->predecessor_count - count;
7230251881Speter  if (walk < (int)ffd->max_linear_deltification)
7231251881Speter    count = noderev->predecessor_count - 1;
7232251881Speter
7233251881Speter  /* Finding the delta base over a very long distance can become extremely
7234251881Speter     expensive for very deep histories, possibly causing client timeouts etc.
7235251881Speter     OTOH, this is a rare operation and its gains are minimal. Lets simply
7236251881Speter     start deltification anew close every other 1000 changes or so.  */
7237251881Speter  if (walk > (int)ffd->max_deltification_walk)
7238251881Speter    {
7239251881Speter      *rep = NULL;
7240251881Speter      return SVN_NO_ERROR;
7241251881Speter    }
7242251881Speter
7243251881Speter  /* Walk back a number of predecessors equal to the difference
7244251881Speter     between count and the original predecessor count.  (For example,
7245251881Speter     if noderev has ten predecessors and we want the eighth file rev,
7246251881Speter     walk back two predecessors.) */
7247251881Speter  base = noderev;
7248251881Speter  while ((count++) < noderev->predecessor_count)
7249251881Speter    {
7250251881Speter      SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
7251251881Speter                                           base->predecessor_id, pool));
7252251881Speter
7253251881Speter      /* If there is a shared rep along the way, we need to limit the
7254251881Speter       * length of the deltification chain.
7255251881Speter       *
7256251881Speter       * Please note that copied nodes - such as branch directories - will
7257251881Speter       * look the same (false positive) while reps shared within the same
7258251881Speter       * revision will not be caught (false negative).
7259251881Speter       */
7260251881Speter      if (props)
7261251881Speter        {
7262251881Speter          if (   base->prop_rep
7263251881Speter              && svn_fs_fs__id_rev(base->id) > base->prop_rep->revision)
7264251881Speter            maybe_shared_rep = TRUE;
7265251881Speter        }
7266251881Speter      else
7267251881Speter        {
7268251881Speter          if (   base->data_rep
7269251881Speter              && svn_fs_fs__id_rev(base->id) > base->data_rep->revision)
7270251881Speter            maybe_shared_rep = TRUE;
7271251881Speter        }
7272251881Speter    }
7273251881Speter
7274251881Speter  /* return a suitable base representation */
7275251881Speter  *rep = props ? base->prop_rep : base->data_rep;
7276251881Speter
7277251881Speter  /* if we encountered a shared rep, it's parent chain may be different
7278251881Speter   * from the node-rev parent chain. */
7279251881Speter  if (*rep && maybe_shared_rep)
7280251881Speter    {
7281251881Speter      /* Check whether the length of the deltification chain is acceptable.
7282251881Speter       * Otherwise, shared reps may form a non-skipping delta chain in
7283251881Speter       * extreme cases. */
7284251881Speter      apr_pool_t *sub_pool = svn_pool_create(pool);
7285251881Speter      representation_t base_rep = **rep;
7286251881Speter
7287251881Speter      /* Some reasonable limit, depending on how acceptable longer linear
7288251881Speter       * chains are in this repo.  Also, allow for some minimal chain. */
7289251881Speter      int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2;
7290251881Speter
7291251881Speter      /* re-use open files between iterations */
7292251881Speter      svn_revnum_t rev_hint = SVN_INVALID_REVNUM;
7293251881Speter      apr_file_t *file_hint = NULL;
7294251881Speter
7295251881Speter      /* follow the delta chain towards the end but for at most
7296251881Speter       * MAX_CHAIN_LENGTH steps. */
7297251881Speter      for (; max_chain_length; --max_chain_length)
7298251881Speter        {
7299251881Speter          struct rep_state *rep_state;
7300251881Speter          struct rep_args *rep_args;
7301251881Speter
7302251881Speter          SVN_ERR(create_rep_state_body(&rep_state,
7303251881Speter                                        &rep_args,
7304251881Speter                                        &file_hint,
7305251881Speter                                        &rev_hint,
7306251881Speter                                        &base_rep,
7307251881Speter                                        fs,
7308251881Speter                                        sub_pool));
7309251881Speter          if (!rep_args->is_delta  || !rep_args->base_revision)
7310251881Speter            break;
7311251881Speter
7312251881Speter          base_rep.revision = rep_args->base_revision;
7313251881Speter          base_rep.offset = rep_args->base_offset;
7314251881Speter          base_rep.size = rep_args->base_length;
7315251881Speter          base_rep.txn_id = NULL;
7316251881Speter        }
7317251881Speter
7318251881Speter      /* start new delta chain if the current one has grown too long */
7319251881Speter      if (max_chain_length == 0)
7320251881Speter        *rep = NULL;
7321251881Speter
7322251881Speter      svn_pool_destroy(sub_pool);
7323251881Speter    }
7324251881Speter
7325251881Speter  /* verify that the reps don't form a degenerated '*/
7326251881Speter  return SVN_NO_ERROR;
7327251881Speter}
7328251881Speter
7329251881Speter/* Something went wrong and the pool for the rep write is being
7330251881Speter   cleared before we've finished writing the rep.  So we need
7331251881Speter   to remove the rep from the protorevfile and we need to unlock
7332251881Speter   the protorevfile. */
7333251881Speterstatic apr_status_t
7334251881Speterrep_write_cleanup(void *data)
7335251881Speter{
7336251881Speter  struct rep_write_baton *b = data;
7337251881Speter  const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7338251881Speter  svn_error_t *err;
7339251881Speter
7340251881Speter  /* Truncate and close the protorevfile. */
7341251881Speter  err = svn_io_file_trunc(b->file, b->rep_offset, b->pool);
7342251881Speter  err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool));
7343251881Speter
7344251881Speter  /* Remove our lock regardless of any preceeding errors so that the
7345251881Speter     being_written flag is always removed and stays consistent with the
7346251881Speter     file lock which will be removed no matter what since the pool is
7347251881Speter     going away. */
7348251881Speter  err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id,
7349251881Speter                                                       b->lockcookie, b->pool));
7350251881Speter  if (err)
7351251881Speter    {
7352251881Speter      apr_status_t rc = err->apr_err;
7353251881Speter      svn_error_clear(err);
7354251881Speter      return rc;
7355251881Speter    }
7356251881Speter
7357251881Speter  return APR_SUCCESS;
7358251881Speter}
7359251881Speter
7360251881Speter
7361251881Speter/* Get a rep_write_baton and store it in *WB_P for the representation
7362251881Speter   indicated by NODEREV in filesystem FS.  Perform allocations in
7363251881Speter   POOL.  Only appropriate for file contents, not for props or
7364251881Speter   directory contents. */
7365251881Speterstatic svn_error_t *
7366251881Speterrep_write_get_baton(struct rep_write_baton **wb_p,
7367251881Speter                    svn_fs_t *fs,
7368251881Speter                    node_revision_t *noderev,
7369251881Speter                    apr_pool_t *pool)
7370251881Speter{
7371251881Speter  struct rep_write_baton *b;
7372251881Speter  apr_file_t *file;
7373251881Speter  representation_t *base_rep;
7374251881Speter  svn_stream_t *source;
7375251881Speter  const char *header;
7376251881Speter  svn_txdelta_window_handler_t wh;
7377251881Speter  void *whb;
7378251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
7379251881Speter  int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7380251881Speter
7381251881Speter  b = apr_pcalloc(pool, sizeof(*b));
7382251881Speter
7383251881Speter  b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7384251881Speter  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7385251881Speter
7386251881Speter  b->fs = fs;
7387251881Speter  b->parent_pool = pool;
7388251881Speter  b->pool = svn_pool_create(pool);
7389251881Speter  b->rep_size = 0;
7390251881Speter  b->noderev = noderev;
7391251881Speter
7392251881Speter  /* Open the prototype rev file and seek to its end. */
7393251881Speter  SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
7394251881Speter                                 fs, svn_fs_fs__id_txn_id(noderev->id),
7395251881Speter                                 b->pool));
7396251881Speter
7397251881Speter  b->file = file;
7398251881Speter  b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool);
7399251881Speter
7400251881Speter  SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
7401251881Speter
7402251881Speter  /* Get the base for this delta. */
7403251881Speter  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool));
7404251881Speter  SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
7405251881Speter
7406251881Speter  /* Write out the rep header. */
7407251881Speter  if (base_rep)
7408251881Speter    {
7409251881Speter      header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7410251881Speter                            SVN_FILESIZE_T_FMT "\n",
7411251881Speter                            base_rep->revision, base_rep->offset,
7412251881Speter                            base_rep->size);
7413251881Speter    }
7414251881Speter  else
7415251881Speter    {
7416251881Speter      header = REP_DELTA "\n";
7417251881Speter    }
7418251881Speter  SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7419251881Speter                                 b->pool));
7420251881Speter
7421251881Speter  /* Now determine the offset of the actual svndiff data. */
7422251881Speter  SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
7423251881Speter
7424251881Speter  /* Cleanup in case something goes wrong. */
7425251881Speter  apr_pool_cleanup_register(b->pool, b, rep_write_cleanup,
7426251881Speter                            apr_pool_cleanup_null);
7427251881Speter
7428251881Speter  /* Prepare to write the svndiff data. */
7429251881Speter  svn_txdelta_to_svndiff3(&wh,
7430251881Speter                          &whb,
7431251881Speter                          b->rep_stream,
7432251881Speter                          diff_version,
7433251881Speter                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7434251881Speter                          pool);
7435251881Speter
7436251881Speter  b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
7437251881Speter
7438251881Speter  *wb_p = b;
7439251881Speter
7440251881Speter  return SVN_NO_ERROR;
7441251881Speter}
7442251881Speter
7443251881Speter/* For the hash REP->SHA1, try to find an already existing representation
7444251881Speter   in FS and return it in *OUT_REP.  If no such representation exists or
7445251881Speter   if rep sharing has been disabled for FS, NULL will be returned.  Since
7446251881Speter   there may be new duplicate representations within the same uncommitted
7447251881Speter   revision, those can be passed in REPS_HASH (maps a sha1 digest onto
7448251881Speter   representation_t*), otherwise pass in NULL for REPS_HASH.
7449251881Speter   POOL will be used for allocations. The lifetime of the returned rep is
7450251881Speter   limited by both, POOL and REP lifetime.
7451251881Speter */
7452251881Speterstatic svn_error_t *
7453251881Speterget_shared_rep(representation_t **old_rep,
7454251881Speter               svn_fs_t *fs,
7455251881Speter               representation_t *rep,
7456251881Speter               apr_hash_t *reps_hash,
7457251881Speter               apr_pool_t *pool)
7458251881Speter{
7459251881Speter  svn_error_t *err;
7460251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
7461251881Speter
7462251881Speter  /* Return NULL, if rep sharing has been disabled. */
7463251881Speter  *old_rep = NULL;
7464251881Speter  if (!ffd->rep_sharing_allowed)
7465251881Speter    return SVN_NO_ERROR;
7466251881Speter
7467251881Speter  /* Check and see if we already have a representation somewhere that's
7468251881Speter     identical to the one we just wrote out.  Start with the hash lookup
7469251881Speter     because it is cheepest. */
7470251881Speter  if (reps_hash)
7471251881Speter    *old_rep = apr_hash_get(reps_hash,
7472251881Speter                            rep->sha1_checksum->digest,
7473251881Speter                            APR_SHA1_DIGESTSIZE);
7474251881Speter
7475251881Speter  /* If we haven't found anything yet, try harder and consult our DB. */
7476251881Speter  if (*old_rep == NULL)
7477251881Speter    {
7478251881Speter      err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum,
7479251881Speter                                         pool);
7480251881Speter      /* ### Other error codes that we shouldn't mask out? */
7481251881Speter      if (err == SVN_NO_ERROR)
7482251881Speter        {
7483251881Speter          if (*old_rep)
7484251881Speter            SVN_ERR(verify_walker(*old_rep, NULL, fs, pool));
7485251881Speter        }
7486251881Speter      else if (err->apr_err == SVN_ERR_FS_CORRUPT
7487251881Speter               || SVN_ERROR_IN_CATEGORY(err->apr_err,
7488251881Speter                                        SVN_ERR_MALFUNC_CATEGORY_START))
7489251881Speter        {
7490251881Speter          /* Fatal error; don't mask it.
7491251881Speter
7492251881Speter             In particular, this block is triggered when the rep-cache refers
7493251881Speter             to revisions in the future.  We signal that as a corruption situation
7494251881Speter             since, once those revisions are less than youngest (because of more
7495251881Speter             commits), the rep-cache would be invalid.
7496251881Speter           */
7497251881Speter          SVN_ERR(err);
7498251881Speter        }
7499251881Speter      else
7500251881Speter        {
7501251881Speter          /* Something's wrong with the rep-sharing index.  We can continue
7502251881Speter             without rep-sharing, but warn.
7503251881Speter           */
7504251881Speter          (fs->warning)(fs->warning_baton, err);
7505251881Speter          svn_error_clear(err);
7506251881Speter          *old_rep = NULL;
7507251881Speter        }
7508251881Speter    }
7509251881Speter
7510251881Speter  /* look for intra-revision matches (usually data reps but not limited
7511251881Speter     to them in case props happen to look like some data rep)
7512251881Speter   */
7513251881Speter  if (*old_rep == NULL && rep->txn_id)
7514251881Speter    {
7515251881Speter      svn_node_kind_t kind;
7516251881Speter      const char *file_name
7517251881Speter        = path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool);
7518251881Speter
7519251881Speter      /* in our txn, is there a rep file named with the wanted SHA1?
7520251881Speter         If so, read it and use that rep.
7521251881Speter       */
7522251881Speter      SVN_ERR(svn_io_check_path(file_name, &kind, pool));
7523251881Speter      if (kind == svn_node_file)
7524251881Speter        {
7525251881Speter          svn_stringbuf_t *rep_string;
7526251881Speter          SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool));
7527251881Speter          SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data,
7528251881Speter                                        rep->txn_id, FALSE, pool));
7529251881Speter        }
7530251881Speter    }
7531251881Speter
7532251881Speter  /* Add information that is missing in the cached data. */
7533251881Speter  if (*old_rep)
7534251881Speter    {
7535251881Speter      /* Use the old rep for this content. */
7536251881Speter      (*old_rep)->md5_checksum = rep->md5_checksum;
7537251881Speter      (*old_rep)->uniquifier = rep->uniquifier;
7538251881Speter    }
7539251881Speter
7540251881Speter  return SVN_NO_ERROR;
7541251881Speter}
7542251881Speter
7543251881Speter/* Close handler for the representation write stream.  BATON is a
7544251881Speter   rep_write_baton.  Writes out a new node-rev that correctly
7545251881Speter   references the representation we just finished writing. */
7546251881Speterstatic svn_error_t *
7547251881Speterrep_write_contents_close(void *baton)
7548251881Speter{
7549251881Speter  struct rep_write_baton *b = baton;
7550251881Speter  const char *unique_suffix;
7551251881Speter  representation_t *rep;
7552251881Speter  representation_t *old_rep;
7553251881Speter  apr_off_t offset;
7554251881Speter
7555251881Speter  rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
7556251881Speter  rep->offset = b->rep_offset;
7557251881Speter
7558251881Speter  /* Close our delta stream so the last bits of svndiff are written
7559251881Speter     out. */
7560251881Speter  if (b->delta_stream)
7561251881Speter    SVN_ERR(svn_stream_close(b->delta_stream));
7562251881Speter
7563251881Speter  /* Determine the length of the svndiff data. */
7564251881Speter  SVN_ERR(get_file_offset(&offset, b->file, b->pool));
7565251881Speter  rep->size = offset - b->delta_start;
7566251881Speter
7567251881Speter  /* Fill in the rest of the representation field. */
7568251881Speter  rep->expanded_size = b->rep_size;
7569251881Speter  rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7570251881Speter  SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool));
7571251881Speter  rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id,
7572251881Speter                                 unique_suffix);
7573251881Speter  rep->revision = SVN_INVALID_REVNUM;
7574251881Speter
7575251881Speter  /* Finalize the checksum. */
7576251881Speter  SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx,
7577251881Speter                              b->parent_pool));
7578251881Speter  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx,
7579251881Speter                              b->parent_pool));
7580251881Speter
7581251881Speter  /* Check and see if we already have a representation somewhere that's
7582251881Speter     identical to the one we just wrote out. */
7583251881Speter  SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool));
7584251881Speter
7585251881Speter  if (old_rep)
7586251881Speter    {
7587251881Speter      /* We need to erase from the protorev the data we just wrote. */
7588251881Speter      SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool));
7589251881Speter
7590251881Speter      /* Use the old rep for this content. */
7591251881Speter      b->noderev->data_rep = old_rep;
7592251881Speter    }
7593251881Speter  else
7594251881Speter    {
7595251881Speter      /* Write out our cosmetic end marker. */
7596251881Speter      SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
7597251881Speter
7598251881Speter      b->noderev->data_rep = rep;
7599251881Speter    }
7600251881Speter
7601251881Speter  /* Remove cleanup callback. */
7602251881Speter  apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup);
7603251881Speter
7604251881Speter  /* Write out the new node-rev information. */
7605251881Speter  SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
7606251881Speter                                       b->pool));
7607251881Speter  if (!old_rep)
7608251881Speter    SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool));
7609251881Speter
7610251881Speter  SVN_ERR(svn_io_file_close(b->file, b->pool));
7611251881Speter  SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
7612251881Speter  svn_pool_destroy(b->pool);
7613251881Speter
7614251881Speter  return SVN_NO_ERROR;
7615251881Speter}
7616251881Speter
7617251881Speter/* Store a writable stream in *CONTENTS_P that will receive all data
7618251881Speter   written and store it as the file data representation referenced by
7619251881Speter   NODEREV in filesystem FS.  Perform temporary allocations in
7620251881Speter   POOL.  Only appropriate for file data, not props or directory
7621251881Speter   contents. */
7622251881Speterstatic svn_error_t *
7623251881Speterset_representation(svn_stream_t **contents_p,
7624251881Speter                   svn_fs_t *fs,
7625251881Speter                   node_revision_t *noderev,
7626251881Speter                   apr_pool_t *pool)
7627251881Speter{
7628251881Speter  struct rep_write_baton *wb;
7629251881Speter
7630251881Speter  if (! svn_fs_fs__id_txn_id(noderev->id))
7631251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7632251881Speter                             _("Attempted to write to non-transaction '%s'"),
7633251881Speter                             svn_fs_fs__id_unparse(noderev->id, pool)->data);
7634251881Speter
7635251881Speter  SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
7636251881Speter
7637251881Speter  *contents_p = svn_stream_create(wb, pool);
7638251881Speter  svn_stream_set_write(*contents_p, rep_write_contents);
7639251881Speter  svn_stream_set_close(*contents_p, rep_write_contents_close);
7640251881Speter
7641251881Speter  return SVN_NO_ERROR;
7642251881Speter}
7643251881Speter
7644251881Spetersvn_error_t *
7645251881Spetersvn_fs_fs__set_contents(svn_stream_t **stream,
7646251881Speter                        svn_fs_t *fs,
7647251881Speter                        node_revision_t *noderev,
7648251881Speter                        apr_pool_t *pool)
7649251881Speter{
7650251881Speter  if (noderev->kind != svn_node_file)
7651251881Speter    return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
7652251881Speter                            _("Can't set text contents of a directory"));
7653251881Speter
7654251881Speter  return set_representation(stream, fs, noderev, pool);
7655251881Speter}
7656251881Speter
7657251881Spetersvn_error_t *
7658251881Spetersvn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
7659251881Speter                            svn_fs_t *fs,
7660251881Speter                            const svn_fs_id_t *old_idp,
7661251881Speter                            node_revision_t *new_noderev,
7662251881Speter                            const char *copy_id,
7663251881Speter                            const char *txn_id,
7664251881Speter                            apr_pool_t *pool)
7665251881Speter{
7666251881Speter  const svn_fs_id_t *id;
7667251881Speter
7668251881Speter  if (! copy_id)
7669251881Speter    copy_id = svn_fs_fs__id_copy_id(old_idp);
7670251881Speter  id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
7671251881Speter                                txn_id, pool);
7672251881Speter
7673251881Speter  new_noderev->id = id;
7674251881Speter
7675251881Speter  if (! new_noderev->copyroot_path)
7676251881Speter    {
7677251881Speter      new_noderev->copyroot_path = apr_pstrdup(pool,
7678251881Speter                                               new_noderev->created_path);
7679251881Speter      new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
7680251881Speter    }
7681251881Speter
7682251881Speter  SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
7683251881Speter                                       pool));
7684251881Speter
7685251881Speter  *new_id_p = id;
7686251881Speter
7687251881Speter  return SVN_NO_ERROR;
7688251881Speter}
7689251881Speter
7690251881Spetersvn_error_t *
7691251881Spetersvn_fs_fs__set_proplist(svn_fs_t *fs,
7692251881Speter                        node_revision_t *noderev,
7693251881Speter                        apr_hash_t *proplist,
7694251881Speter                        apr_pool_t *pool)
7695251881Speter{
7696251881Speter  const char *filename = path_txn_node_props(fs, noderev->id, pool);
7697251881Speter  apr_file_t *file;
7698251881Speter  svn_stream_t *out;
7699251881Speter
7700251881Speter  /* Dump the property list to the mutable property file. */
7701251881Speter  SVN_ERR(svn_io_file_open(&file, filename,
7702251881Speter                           APR_WRITE | APR_CREATE | APR_TRUNCATE
7703251881Speter                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
7704251881Speter  out = svn_stream_from_aprfile2(file, TRUE, pool);
7705251881Speter  SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
7706251881Speter  SVN_ERR(svn_io_file_close(file, pool));
7707251881Speter
7708251881Speter  /* Mark the node-rev's prop rep as mutable, if not already done. */
7709251881Speter  if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
7710251881Speter    {
7711251881Speter      noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
7712251881Speter      noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
7713251881Speter      SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
7714251881Speter    }
7715251881Speter
7716251881Speter  return SVN_NO_ERROR;
7717251881Speter}
7718251881Speter
7719251881Speter/* Read the 'current' file for filesystem FS and store the next
7720251881Speter   available node id in *NODE_ID, and the next available copy id in
7721251881Speter   *COPY_ID.  Allocations are performed from POOL. */
7722251881Speterstatic svn_error_t *
7723251881Speterget_next_revision_ids(const char **node_id,
7724251881Speter                      const char **copy_id,
7725251881Speter                      svn_fs_t *fs,
7726251881Speter                      apr_pool_t *pool)
7727251881Speter{
7728251881Speter  char *buf;
7729251881Speter  char *str;
7730251881Speter  svn_stringbuf_t *content;
7731251881Speter
7732251881Speter  SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool));
7733251881Speter  buf = content->data;
7734251881Speter
7735251881Speter  str = svn_cstring_tokenize(" ", &buf);
7736251881Speter  if (! str)
7737251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7738251881Speter                            _("Corrupt 'current' file"));
7739251881Speter
7740251881Speter  str = svn_cstring_tokenize(" ", &buf);
7741251881Speter  if (! str)
7742251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7743251881Speter                            _("Corrupt 'current' file"));
7744251881Speter
7745251881Speter  *node_id = apr_pstrdup(pool, str);
7746251881Speter
7747251881Speter  str = svn_cstring_tokenize(" \n", &buf);
7748251881Speter  if (! str)
7749251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7750251881Speter                            _("Corrupt 'current' file"));
7751251881Speter
7752251881Speter  *copy_id = apr_pstrdup(pool, str);
7753251881Speter
7754251881Speter  return SVN_NO_ERROR;
7755251881Speter}
7756251881Speter
7757251881Speter/* This baton is used by the stream created for write_hash_rep. */
7758251881Speterstruct write_hash_baton
7759251881Speter{
7760251881Speter  svn_stream_t *stream;
7761251881Speter
7762251881Speter  apr_size_t size;
7763251881Speter
7764251881Speter  svn_checksum_ctx_t *md5_ctx;
7765251881Speter  svn_checksum_ctx_t *sha1_ctx;
7766251881Speter};
7767251881Speter
7768251881Speter/* The handler for the write_hash_rep stream.  BATON is a
7769251881Speter   write_hash_baton, DATA has the data to write and *LEN is the number
7770251881Speter   of bytes to write. */
7771251881Speterstatic svn_error_t *
7772251881Speterwrite_hash_handler(void *baton,
7773251881Speter                   const char *data,
7774251881Speter                   apr_size_t *len)
7775251881Speter{
7776251881Speter  struct write_hash_baton *whb = baton;
7777251881Speter
7778251881Speter  SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
7779251881Speter  SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
7780251881Speter
7781251881Speter  SVN_ERR(svn_stream_write(whb->stream, data, len));
7782251881Speter  whb->size += *len;
7783251881Speter
7784251881Speter  return SVN_NO_ERROR;
7785251881Speter}
7786251881Speter
7787251881Speter/* Write out the hash HASH as a text representation to file FILE.  In
7788251881Speter   the process, record position, the total size of the dump and MD5 as
7789251881Speter   well as SHA1 in REP.   If rep sharing has been enabled and REPS_HASH
7790251881Speter   is not NULL, it will be used in addition to the on-disk cache to find
7791251881Speter   earlier reps with the same content.  When such existing reps can be
7792251881Speter   found, we will truncate the one just written from the file and return
7793251881Speter   the existing rep.  Perform temporary allocations in POOL. */
7794251881Speterstatic svn_error_t *
7795251881Speterwrite_hash_rep(representation_t *rep,
7796251881Speter               apr_file_t *file,
7797251881Speter               apr_hash_t *hash,
7798251881Speter               svn_fs_t *fs,
7799251881Speter               apr_hash_t *reps_hash,
7800251881Speter               apr_pool_t *pool)
7801251881Speter{
7802251881Speter  svn_stream_t *stream;
7803251881Speter  struct write_hash_baton *whb;
7804251881Speter  representation_t *old_rep;
7805251881Speter
7806251881Speter  SVN_ERR(get_file_offset(&rep->offset, file, pool));
7807251881Speter
7808251881Speter  whb = apr_pcalloc(pool, sizeof(*whb));
7809251881Speter
7810251881Speter  whb->stream = svn_stream_from_aprfile2(file, TRUE, pool);
7811251881Speter  whb->size = 0;
7812251881Speter  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7813251881Speter  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7814251881Speter
7815251881Speter  stream = svn_stream_create(whb, pool);
7816251881Speter  svn_stream_set_write(stream, write_hash_handler);
7817251881Speter
7818251881Speter  SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
7819251881Speter
7820251881Speter  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7821251881Speter
7822251881Speter  /* Store the results. */
7823251881Speter  SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7824251881Speter  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7825251881Speter
7826251881Speter  /* Check and see if we already have a representation somewhere that's
7827251881Speter     identical to the one we just wrote out. */
7828251881Speter  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7829251881Speter
7830251881Speter  if (old_rep)
7831251881Speter    {
7832251881Speter      /* We need to erase from the protorev the data we just wrote. */
7833251881Speter      SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7834251881Speter
7835251881Speter      /* Use the old rep for this content. */
7836251881Speter      memcpy(rep, old_rep, sizeof (*rep));
7837251881Speter    }
7838251881Speter  else
7839251881Speter    {
7840251881Speter      /* Write out our cosmetic end marker. */
7841251881Speter      SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
7842251881Speter
7843251881Speter      /* update the representation */
7844251881Speter      rep->size = whb->size;
7845251881Speter      rep->expanded_size = 0;
7846251881Speter    }
7847251881Speter
7848251881Speter  return SVN_NO_ERROR;
7849251881Speter}
7850251881Speter
7851251881Speter/* Write out the hash HASH pertaining to the NODEREV in FS as a deltified
7852251881Speter   text representation to file FILE.  In the process, record the total size
7853251881Speter   and the md5 digest in REP.  If rep sharing has been enabled and REPS_HASH
7854251881Speter   is not NULL, it will be used in addition to the on-disk cache to find
7855251881Speter   earlier reps with the same content.  When such existing reps can be found,
7856251881Speter   we will truncate the one just written from the file and return the existing
7857251881Speter   rep.  If PROPS is set, assume that we want to a props representation as
7858251881Speter   the base for our delta.  Perform temporary allocations in POOL. */
7859251881Speterstatic svn_error_t *
7860251881Speterwrite_hash_delta_rep(representation_t *rep,
7861251881Speter                     apr_file_t *file,
7862251881Speter                     apr_hash_t *hash,
7863251881Speter                     svn_fs_t *fs,
7864251881Speter                     node_revision_t *noderev,
7865251881Speter                     apr_hash_t *reps_hash,
7866251881Speter                     svn_boolean_t props,
7867251881Speter                     apr_pool_t *pool)
7868251881Speter{
7869251881Speter  svn_txdelta_window_handler_t diff_wh;
7870251881Speter  void *diff_whb;
7871251881Speter
7872251881Speter  svn_stream_t *file_stream;
7873251881Speter  svn_stream_t *stream;
7874251881Speter  representation_t *base_rep;
7875251881Speter  representation_t *old_rep;
7876251881Speter  svn_stream_t *source;
7877251881Speter  const char *header;
7878251881Speter
7879251881Speter  apr_off_t rep_end = 0;
7880251881Speter  apr_off_t delta_start = 0;
7881251881Speter
7882251881Speter  struct write_hash_baton *whb;
7883251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
7884251881Speter  int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7885251881Speter
7886251881Speter  /* Get the base for this delta. */
7887251881Speter  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool));
7888251881Speter  SVN_ERR(read_representation(&source, fs, base_rep, pool));
7889251881Speter
7890251881Speter  SVN_ERR(get_file_offset(&rep->offset, file, pool));
7891251881Speter
7892251881Speter  /* Write out the rep header. */
7893251881Speter  if (base_rep)
7894251881Speter    {
7895251881Speter      header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7896251881Speter                            SVN_FILESIZE_T_FMT "\n",
7897251881Speter                            base_rep->revision, base_rep->offset,
7898251881Speter                            base_rep->size);
7899251881Speter    }
7900251881Speter  else
7901251881Speter    {
7902251881Speter      header = REP_DELTA "\n";
7903251881Speter    }
7904251881Speter  SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7905251881Speter                                 pool));
7906251881Speter
7907251881Speter  SVN_ERR(get_file_offset(&delta_start, file, pool));
7908251881Speter  file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
7909251881Speter
7910251881Speter  /* Prepare to write the svndiff data. */
7911251881Speter  svn_txdelta_to_svndiff3(&diff_wh,
7912251881Speter                          &diff_whb,
7913251881Speter                          file_stream,
7914251881Speter                          diff_version,
7915251881Speter                          SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7916251881Speter                          pool);
7917251881Speter
7918251881Speter  whb = apr_pcalloc(pool, sizeof(*whb));
7919251881Speter  whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
7920251881Speter  whb->size = 0;
7921251881Speter  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7922251881Speter  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7923251881Speter
7924251881Speter  /* serialize the hash */
7925251881Speter  stream = svn_stream_create(whb, pool);
7926251881Speter  svn_stream_set_write(stream, write_hash_handler);
7927251881Speter
7928251881Speter  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7929251881Speter  SVN_ERR(svn_stream_close(whb->stream));
7930251881Speter
7931251881Speter  /* Store the results. */
7932251881Speter  SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7933251881Speter  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7934251881Speter
7935251881Speter  /* Check and see if we already have a representation somewhere that's
7936251881Speter     identical to the one we just wrote out. */
7937251881Speter  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7938251881Speter
7939251881Speter  if (old_rep)
7940251881Speter    {
7941251881Speter      /* We need to erase from the protorev the data we just wrote. */
7942251881Speter      SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7943251881Speter
7944251881Speter      /* Use the old rep for this content. */
7945251881Speter      memcpy(rep, old_rep, sizeof (*rep));
7946251881Speter    }
7947251881Speter  else
7948251881Speter    {
7949251881Speter      /* Write out our cosmetic end marker. */
7950251881Speter      SVN_ERR(get_file_offset(&rep_end, file, pool));
7951251881Speter      SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
7952251881Speter
7953251881Speter      /* update the representation */
7954251881Speter      rep->expanded_size = whb->size;
7955251881Speter      rep->size = rep_end - delta_start;
7956251881Speter    }
7957251881Speter
7958251881Speter  return SVN_NO_ERROR;
7959251881Speter}
7960251881Speter
7961251881Speter/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
7962251881Speter   of (not yet committed) revision REV in FS.  Use POOL for temporary
7963251881Speter   allocations.
7964251881Speter
7965251881Speter   If you change this function, consider updating svn_fs_fs__verify() too.
7966251881Speter */
7967251881Speterstatic svn_error_t *
7968251881Spetervalidate_root_noderev(svn_fs_t *fs,
7969251881Speter                      node_revision_t *root_noderev,
7970251881Speter                      svn_revnum_t rev,
7971251881Speter                      apr_pool_t *pool)
7972251881Speter{
7973251881Speter  svn_revnum_t head_revnum = rev-1;
7974251881Speter  int head_predecessor_count;
7975251881Speter
7976251881Speter  SVN_ERR_ASSERT(rev > 0);
7977251881Speter
7978251881Speter  /* Compute HEAD_PREDECESSOR_COUNT. */
7979251881Speter  {
7980251881Speter    svn_fs_root_t *head_revision;
7981251881Speter    const svn_fs_id_t *head_root_id;
7982251881Speter    node_revision_t *head_root_noderev;
7983251881Speter
7984251881Speter    /* Get /@HEAD's noderev. */
7985251881Speter    SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
7986251881Speter    SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
7987251881Speter    SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
7988251881Speter                                         pool));
7989251881Speter
7990251881Speter    head_predecessor_count = head_root_noderev->predecessor_count;
7991251881Speter  }
7992251881Speter
7993251881Speter  /* Check that the root noderev's predecessor count equals REV.
7994251881Speter
7995251881Speter     This kind of corruption was seen on svn.apache.org (both on
7996251881Speter     the root noderev and on other fspaths' noderevs); see
7997251881Speter     issue #4129.
7998251881Speter
7999251881Speter     Normally (rev == root_noderev->predecessor_count), but here we
8000251881Speter     use a more roundabout check that should only trigger on new instances
8001251881Speter     of the corruption, rather then trigger on each and every new commit
8002251881Speter     to a repository that has triggered the bug somewhere in its root
8003251881Speter     noderev's history.
8004251881Speter   */
8005251881Speter  if (root_noderev->predecessor_count != -1
8006251881Speter      && (root_noderev->predecessor_count - head_predecessor_count)
8007251881Speter         != (rev - head_revnum))
8008251881Speter    {
8009251881Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
8010251881Speter                               _("predecessor count for "
8011251881Speter                                 "the root node-revision is wrong: "
8012251881Speter                                 "found (%d+%ld != %d), committing r%ld"),
8013251881Speter                                 head_predecessor_count,
8014251881Speter                                 rev - head_revnum, /* This is equal to 1. */
8015251881Speter                                 root_noderev->predecessor_count,
8016251881Speter                                 rev);
8017251881Speter    }
8018251881Speter
8019251881Speter  return SVN_NO_ERROR;
8020251881Speter}
8021251881Speter
8022251881Speter/* Copy a node-revision specified by id ID in fileystem FS from a
8023251881Speter   transaction into the proto-rev-file FILE.  Set *NEW_ID_P to a
8024251881Speter   pointer to the new node-id which will be allocated in POOL.
8025251881Speter   If this is a directory, copy all children as well.
8026251881Speter
8027251881Speter   START_NODE_ID and START_COPY_ID are
8028251881Speter   the first available node and copy ids for this filesystem, for older
8029251881Speter   FS formats.
8030251881Speter
8031251881Speter   REV is the revision number that this proto-rev-file will represent.
8032251881Speter
8033251881Speter   INITIAL_OFFSET is the offset of the proto-rev-file on entry to
8034251881Speter   commit_body.
8035251881Speter
8036251881Speter   If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
8037251881Speter   REPS_POOL) of each data rep that is new in this revision.
8038251881Speter
8039251881Speter   If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
8040251881Speter   of the representations of each property rep that is new in this
8041251881Speter   revision.
8042251881Speter
8043251881Speter   AT_ROOT is true if the node revision being written is the root
8044251881Speter   node-revision.  It is only controls additional sanity checking
8045251881Speter   logic.
8046251881Speter
8047251881Speter   Temporary allocations are also from POOL. */
8048251881Speterstatic svn_error_t *
8049251881Speterwrite_final_rev(const svn_fs_id_t **new_id_p,
8050251881Speter                apr_file_t *file,
8051251881Speter                svn_revnum_t rev,
8052251881Speter                svn_fs_t *fs,
8053251881Speter                const svn_fs_id_t *id,
8054251881Speter                const char *start_node_id,
8055251881Speter                const char *start_copy_id,
8056251881Speter                apr_off_t initial_offset,
8057251881Speter                apr_array_header_t *reps_to_cache,
8058251881Speter                apr_hash_t *reps_hash,
8059251881Speter                apr_pool_t *reps_pool,
8060251881Speter                svn_boolean_t at_root,
8061251881Speter                apr_pool_t *pool)
8062251881Speter{
8063251881Speter  node_revision_t *noderev;
8064251881Speter  apr_off_t my_offset;
8065251881Speter  char my_node_id_buf[MAX_KEY_SIZE + 2];
8066251881Speter  char my_copy_id_buf[MAX_KEY_SIZE + 2];
8067251881Speter  const svn_fs_id_t *new_id;
8068251881Speter  const char *node_id, *copy_id, *my_node_id, *my_copy_id;
8069251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
8070251881Speter
8071251881Speter  *new_id_p = NULL;
8072251881Speter
8073251881Speter  /* Check to see if this is a transaction node. */
8074251881Speter  if (! svn_fs_fs__id_txn_id(id))
8075251881Speter    return SVN_NO_ERROR;
8076251881Speter
8077251881Speter  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
8078251881Speter
8079251881Speter  if (noderev->kind == svn_node_dir)
8080251881Speter    {
8081251881Speter      apr_pool_t *subpool;
8082251881Speter      apr_hash_t *entries, *str_entries;
8083251881Speter      apr_array_header_t *sorted_entries;
8084251881Speter      int i;
8085251881Speter
8086251881Speter      /* This is a directory.  Write out all the children first. */
8087251881Speter      subpool = svn_pool_create(pool);
8088251881Speter
8089251881Speter      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
8090251881Speter      /* For the sake of the repository administrator sort the entries
8091251881Speter         so that the final file is deterministic and repeatable,
8092251881Speter         however the rest of the FSFS code doesn't require any
8093251881Speter         particular order here. */
8094251881Speter      sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically,
8095251881Speter                                      pool);
8096251881Speter      for (i = 0; i < sorted_entries->nelts; ++i)
8097251881Speter        {
8098251881Speter          svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i,
8099251881Speter                                                  svn_sort__item_t).value;
8100251881Speter
8101251881Speter          svn_pool_clear(subpool);
8102251881Speter          SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
8103251881Speter                                  start_node_id, start_copy_id, initial_offset,
8104251881Speter                                  reps_to_cache, reps_hash, reps_pool, FALSE,
8105251881Speter                                  subpool));
8106251881Speter          if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
8107251881Speter            dirent->id = svn_fs_fs__id_copy(new_id, pool);
8108251881Speter        }
8109251881Speter      svn_pool_destroy(subpool);
8110251881Speter
8111251881Speter      if (noderev->data_rep && noderev->data_rep->txn_id)
8112251881Speter        {
8113251881Speter          /* Write out the contents of this directory as a text rep. */
8114251881Speter          SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
8115251881Speter
8116251881Speter          noderev->data_rep->txn_id = NULL;
8117251881Speter          noderev->data_rep->revision = rev;
8118251881Speter
8119251881Speter          if (ffd->deltify_directories)
8120251881Speter            SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
8121251881Speter                                         str_entries, fs, noderev, NULL,
8122251881Speter                                         FALSE, pool));
8123251881Speter          else
8124251881Speter            SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
8125251881Speter                                   fs, NULL, pool));
8126251881Speter        }
8127251881Speter    }
8128251881Speter  else
8129251881Speter    {
8130251881Speter      /* This is a file.  We should make sure the data rep, if it
8131251881Speter         exists in a "this" state, gets rewritten to our new revision
8132251881Speter         num. */
8133251881Speter
8134251881Speter      if (noderev->data_rep && noderev->data_rep->txn_id)
8135251881Speter        {
8136251881Speter          noderev->data_rep->txn_id = NULL;
8137251881Speter          noderev->data_rep->revision = rev;
8138251881Speter
8139251881Speter          /* See issue 3845.  Some unknown mechanism caused the
8140251881Speter             protorev file to get truncated, so check for that
8141251881Speter             here.  */
8142251881Speter          if (noderev->data_rep->offset + noderev->data_rep->size
8143251881Speter              > initial_offset)
8144251881Speter            return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
8145251881Speter                                    _("Truncated protorev file detected"));
8146251881Speter        }
8147251881Speter    }
8148251881Speter
8149251881Speter  /* Fix up the property reps. */
8150251881Speter  if (noderev->prop_rep && noderev->prop_rep->txn_id)
8151251881Speter    {
8152251881Speter      apr_hash_t *proplist;
8153251881Speter      SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
8154251881Speter
8155251881Speter      noderev->prop_rep->txn_id = NULL;
8156251881Speter      noderev->prop_rep->revision = rev;
8157251881Speter
8158251881Speter      if (ffd->deltify_properties)
8159251881Speter        SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
8160251881Speter                                     proplist, fs, noderev, reps_hash,
8161251881Speter                                     TRUE, pool));
8162251881Speter      else
8163251881Speter        SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
8164251881Speter                               fs, reps_hash, pool));
8165251881Speter    }
8166251881Speter
8167251881Speter
8168251881Speter  /* Convert our temporary ID into a permanent revision one. */
8169251881Speter  SVN_ERR(get_file_offset(&my_offset, file, pool));
8170251881Speter
8171251881Speter  node_id = svn_fs_fs__id_node_id(noderev->id);
8172251881Speter  if (*node_id == '_')
8173251881Speter    {
8174251881Speter      if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8175251881Speter        my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev);
8176251881Speter      else
8177251881Speter        {
8178251881Speter          svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf);
8179251881Speter          my_node_id = my_node_id_buf;
8180251881Speter        }
8181251881Speter    }
8182251881Speter  else
8183251881Speter    my_node_id = node_id;
8184251881Speter
8185251881Speter  copy_id = svn_fs_fs__id_copy_id(noderev->id);
8186251881Speter  if (*copy_id == '_')
8187251881Speter    {
8188251881Speter      if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8189251881Speter        my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev);
8190251881Speter      else
8191251881Speter        {
8192251881Speter          svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf);
8193251881Speter          my_copy_id = my_copy_id_buf;
8194251881Speter        }
8195251881Speter    }
8196251881Speter  else
8197251881Speter    my_copy_id = copy_id;
8198251881Speter
8199251881Speter  if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
8200251881Speter    noderev->copyroot_rev = rev;
8201251881Speter
8202251881Speter  new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
8203251881Speter                                    pool);
8204251881Speter
8205251881Speter  noderev->id = new_id;
8206251881Speter
8207251881Speter  if (ffd->rep_sharing_allowed)
8208251881Speter    {
8209251881Speter      /* Save the data representation's hash in the rep cache. */
8210251881Speter      if (   noderev->data_rep && noderev->kind == svn_node_file
8211251881Speter          && noderev->data_rep->revision == rev)
8212251881Speter        {
8213251881Speter          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8214251881Speter          APR_ARRAY_PUSH(reps_to_cache, representation_t *)
8215251881Speter            = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
8216251881Speter        }
8217251881Speter
8218251881Speter      if (noderev->prop_rep && noderev->prop_rep->revision == rev)
8219251881Speter        {
8220251881Speter          /* Add new property reps to hash and on-disk cache. */
8221251881Speter          representation_t *copy
8222251881Speter            = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
8223251881Speter
8224251881Speter          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8225251881Speter          APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
8226251881Speter
8227251881Speter          apr_hash_set(reps_hash,
8228251881Speter                        copy->sha1_checksum->digest,
8229251881Speter                        APR_SHA1_DIGESTSIZE,
8230251881Speter                        copy);
8231251881Speter        }
8232251881Speter    }
8233251881Speter
8234251881Speter  /* don't serialize SHA1 for dirs to disk (waste of space) */
8235251881Speter  if (noderev->data_rep && noderev->kind == svn_node_dir)
8236251881Speter    noderev->data_rep->sha1_checksum = NULL;
8237251881Speter
8238251881Speter  /* don't serialize SHA1 for props to disk (waste of space) */
8239251881Speter  if (noderev->prop_rep)
8240251881Speter    noderev->prop_rep->sha1_checksum = NULL;
8241251881Speter
8242251881Speter  /* Workaround issue #4031: is-fresh-txn-root in revision files. */
8243251881Speter  noderev->is_fresh_txn_root = FALSE;
8244251881Speter
8245251881Speter  /* Write out our new node-revision. */
8246251881Speter  if (at_root)
8247251881Speter    SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
8248251881Speter
8249251881Speter  SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool),
8250251881Speter                                   noderev, ffd->format,
8251251881Speter                                   svn_fs_fs__fs_supports_mergeinfo(fs),
8252251881Speter                                   pool));
8253251881Speter
8254251881Speter  /* Return our ID that references the revision file. */
8255251881Speter  *new_id_p = noderev->id;
8256251881Speter
8257251881Speter  return SVN_NO_ERROR;
8258251881Speter}
8259251881Speter
8260251881Speter/* Write the changed path info from transaction TXN_ID in filesystem
8261251881Speter   FS to the permanent rev-file FILE.  *OFFSET_P is set the to offset
8262251881Speter   in the file of the beginning of this information.  Perform
8263251881Speter   temporary allocations in POOL. */
8264251881Speterstatic svn_error_t *
8265251881Speterwrite_final_changed_path_info(apr_off_t *offset_p,
8266251881Speter                              apr_file_t *file,
8267251881Speter                              svn_fs_t *fs,
8268251881Speter                              const char *txn_id,
8269251881Speter                              apr_pool_t *pool)
8270251881Speter{
8271251881Speter  apr_hash_t *changed_paths;
8272251881Speter  apr_off_t offset;
8273251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
8274251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
8275251881Speter  svn_boolean_t include_node_kinds =
8276251881Speter      ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
8277251881Speter  apr_array_header_t *sorted_changed_paths;
8278251881Speter  int i;
8279251881Speter
8280251881Speter  SVN_ERR(get_file_offset(&offset, file, pool));
8281251881Speter
8282251881Speter  SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool));
8283251881Speter  /* For the sake of the repository administrator sort the changes so
8284251881Speter     that the final file is deterministic and repeatable, however the
8285251881Speter     rest of the FSFS code doesn't require any particular order here. */
8286251881Speter  sorted_changed_paths = svn_sort__hash(changed_paths,
8287251881Speter                                        svn_sort_compare_items_lexically, pool);
8288251881Speter
8289251881Speter  /* Iterate through the changed paths one at a time, and convert the
8290251881Speter     temporary node-id into a permanent one for each change entry. */
8291251881Speter  for (i = 0; i < sorted_changed_paths->nelts; ++i)
8292251881Speter    {
8293251881Speter      node_revision_t *noderev;
8294251881Speter      const svn_fs_id_t *id;
8295251881Speter      svn_fs_path_change2_t *change;
8296251881Speter      const char *path;
8297251881Speter
8298251881Speter      svn_pool_clear(iterpool);
8299251881Speter
8300251881Speter      change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
8301251881Speter      path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
8302251881Speter
8303251881Speter      id = change->node_rev_id;
8304251881Speter
8305251881Speter      /* If this was a delete of a mutable node, then it is OK to
8306251881Speter         leave the change entry pointing to the non-existent temporary
8307251881Speter         node, since it will never be used. */
8308251881Speter      if ((change->change_kind != svn_fs_path_change_delete) &&
8309251881Speter          (! svn_fs_fs__id_txn_id(id)))
8310251881Speter        {
8311251881Speter          SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
8312251881Speter
8313251881Speter          /* noderev has the permanent node-id at this point, so we just
8314251881Speter             substitute it for the temporary one. */
8315251881Speter          change->node_rev_id = noderev->id;
8316251881Speter        }
8317251881Speter
8318251881Speter      /* Write out the new entry into the final rev-file. */
8319251881Speter      SVN_ERR(write_change_entry(file, path, change, include_node_kinds,
8320251881Speter                                 iterpool));
8321251881Speter    }
8322251881Speter
8323251881Speter  svn_pool_destroy(iterpool);
8324251881Speter
8325251881Speter  *offset_p = offset;
8326251881Speter
8327251881Speter  return SVN_NO_ERROR;
8328251881Speter}
8329251881Speter
8330251881Speter/* Atomically update the 'current' file to hold the specifed REV,
8331251881Speter   NEXT_NODE_ID, and NEXT_COPY_ID.  (The two next-ID parameters are
8332251881Speter   ignored and may be NULL if the FS format does not use them.)
8333251881Speter   Perform temporary allocations in POOL. */
8334251881Speterstatic svn_error_t *
8335251881Speterwrite_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
8336251881Speter              const char *next_copy_id, apr_pool_t *pool)
8337251881Speter{
8338251881Speter  char *buf;
8339251881Speter  const char *tmp_name, *name;
8340251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
8341251881Speter
8342251881Speter  /* Now we can just write out this line. */
8343251881Speter  if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8344251881Speter    buf = apr_psprintf(pool, "%ld\n", rev);
8345251881Speter  else
8346251881Speter    buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
8347251881Speter
8348251881Speter  name = svn_fs_fs__path_current(fs, pool);
8349251881Speter  SVN_ERR(svn_io_write_unique(&tmp_name,
8350251881Speter                              svn_dirent_dirname(name, pool),
8351251881Speter                              buf, strlen(buf),
8352251881Speter                              svn_io_file_del_none, pool));
8353251881Speter
8354251881Speter  return move_into_place(tmp_name, name, name, pool);
8355251881Speter}
8356251881Speter
8357251881Speter/* Open a new svn_fs_t handle to FS, set that handle's concept of "current
8358251881Speter   youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
8359251881Speter   NEW_REV's revision root.
8360251881Speter
8361251881Speter   Intended to be called as the very last step in a commit before 'current'
8362251881Speter   is bumped.  This implies that we are holding the write lock. */
8363251881Speterstatic svn_error_t *
8364251881Speterverify_as_revision_before_current_plus_plus(svn_fs_t *fs,
8365251881Speter                                            svn_revnum_t new_rev,
8366251881Speter                                            apr_pool_t *pool)
8367251881Speter{
8368251881Speter#ifdef SVN_DEBUG
8369251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
8370251881Speter  svn_fs_t *ft; /* fs++ == ft */
8371251881Speter  svn_fs_root_t *root;
8372251881Speter  fs_fs_data_t *ft_ffd;
8373251881Speter  apr_hash_t *fs_config;
8374251881Speter
8375251881Speter  SVN_ERR_ASSERT(ffd->svn_fs_open_);
8376251881Speter
8377251881Speter  /* make sure FT does not simply return data cached by other instances
8378251881Speter   * but actually retrieves it from disk at least once.
8379251881Speter   */
8380251881Speter  fs_config = apr_hash_make(pool);
8381251881Speter  svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
8382251881Speter                           svn_uuid_generate(pool));
8383251881Speter  SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
8384251881Speter                            fs_config,
8385251881Speter                            pool));
8386251881Speter  ft_ffd = ft->fsap_data;
8387251881Speter  /* Don't let FT consult rep-cache.db, either. */
8388251881Speter  ft_ffd->rep_sharing_allowed = FALSE;
8389251881Speter
8390251881Speter  /* Time travel! */
8391251881Speter  ft_ffd->youngest_rev_cache = new_rev;
8392251881Speter
8393251881Speter  SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
8394251881Speter  SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
8395251881Speter  SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
8396251881Speter  SVN_ERR(svn_fs_fs__verify_root(root, pool));
8397251881Speter#endif /* SVN_DEBUG */
8398251881Speter
8399251881Speter  return SVN_NO_ERROR;
8400251881Speter}
8401251881Speter
8402251881Speter/* Update the 'current' file to hold the correct next node and copy_ids
8403251881Speter   from transaction TXN_ID in filesystem FS.  The current revision is
8404251881Speter   set to REV.  Perform temporary allocations in POOL. */
8405251881Speterstatic svn_error_t *
8406251881Speterwrite_final_current(svn_fs_t *fs,
8407251881Speter                    const char *txn_id,
8408251881Speter                    svn_revnum_t rev,
8409251881Speter                    const char *start_node_id,
8410251881Speter                    const char *start_copy_id,
8411251881Speter                    apr_pool_t *pool)
8412251881Speter{
8413251881Speter  const char *txn_node_id, *txn_copy_id;
8414251881Speter  char new_node_id[MAX_KEY_SIZE + 2];
8415251881Speter  char new_copy_id[MAX_KEY_SIZE + 2];
8416251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
8417251881Speter
8418251881Speter  if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8419251881Speter    return write_current(fs, rev, NULL, NULL, pool);
8420251881Speter
8421251881Speter  /* To find the next available ids, we add the id that used to be in
8422251881Speter     the 'current' file, to the next ids from the transaction file. */
8423251881Speter  SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
8424251881Speter
8425251881Speter  svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
8426251881Speter  svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
8427251881Speter
8428251881Speter  return write_current(fs, rev, new_node_id, new_copy_id, pool);
8429251881Speter}
8430251881Speter
8431251881Speter/* Verify that the user registed with FS has all the locks necessary to
8432251881Speter   permit all the changes associate with TXN_NAME.
8433251881Speter   The FS write lock is assumed to be held by the caller. */
8434251881Speterstatic svn_error_t *
8435251881Speterverify_locks(svn_fs_t *fs,
8436251881Speter             const char *txn_name,
8437251881Speter             apr_pool_t *pool)
8438251881Speter{
8439251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
8440251881Speter  apr_hash_t *changes;
8441251881Speter  apr_hash_index_t *hi;
8442251881Speter  apr_array_header_t *changed_paths;
8443251881Speter  svn_stringbuf_t *last_recursed = NULL;
8444251881Speter  int i;
8445251881Speter
8446251881Speter  /* Fetch the changes for this transaction. */
8447251881Speter  SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool));
8448251881Speter
8449251881Speter  /* Make an array of the changed paths, and sort them depth-first-ily.  */
8450251881Speter  changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
8451251881Speter                                 sizeof(const char *));
8452251881Speter  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
8453251881Speter    APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi);
8454251881Speter  qsort(changed_paths->elts, changed_paths->nelts,
8455251881Speter        changed_paths->elt_size, svn_sort_compare_paths);
8456251881Speter
8457251881Speter  /* Now, traverse the array of changed paths, verify locks.  Note
8458251881Speter     that if we need to do a recursive verification a path, we'll skip
8459251881Speter     over children of that path when we get to them. */
8460251881Speter  for (i = 0; i < changed_paths->nelts; i++)
8461251881Speter    {
8462251881Speter      const char *path;
8463251881Speter      svn_fs_path_change2_t *change;
8464251881Speter      svn_boolean_t recurse = TRUE;
8465251881Speter
8466251881Speter      svn_pool_clear(subpool);
8467251881Speter      path = APR_ARRAY_IDX(changed_paths, i, const char *);
8468251881Speter
8469251881Speter      /* If this path has already been verified as part of a recursive
8470251881Speter         check of one of its parents, no need to do it again.  */
8471251881Speter      if (last_recursed
8472251881Speter          && svn_dirent_is_child(last_recursed->data, path, subpool))
8473251881Speter        continue;
8474251881Speter
8475251881Speter      /* Fetch the change associated with our path.  */
8476251881Speter      change = svn_hash_gets(changes, path);
8477251881Speter
8478251881Speter      /* What does it mean to succeed at lock verification for a given
8479251881Speter         path?  For an existing file or directory getting modified
8480251881Speter         (text, props), it means we hold the lock on the file or
8481251881Speter         directory.  For paths being added or removed, we need to hold
8482251881Speter         the locks for that path and any children of that path.
8483251881Speter
8484251881Speter         WHEW!  We have no reliable way to determine the node kind
8485251881Speter         of deleted items, but fortunately we are going to do a
8486251881Speter         recursive check on deleted paths regardless of their kind.  */
8487251881Speter      if (change->change_kind == svn_fs_path_change_modify)
8488251881Speter        recurse = FALSE;
8489251881Speter      SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
8490251881Speter                                                subpool));
8491251881Speter
8492251881Speter      /* If we just did a recursive check, remember the path we
8493251881Speter         checked (so children can be skipped).  */
8494251881Speter      if (recurse)
8495251881Speter        {
8496251881Speter          if (! last_recursed)
8497251881Speter            last_recursed = svn_stringbuf_create(path, pool);
8498251881Speter          else
8499251881Speter            svn_stringbuf_set(last_recursed, path);
8500251881Speter        }
8501251881Speter    }
8502251881Speter  svn_pool_destroy(subpool);
8503251881Speter  return SVN_NO_ERROR;
8504251881Speter}
8505251881Speter
8506251881Speter/* Baton used for commit_body below. */
8507251881Speterstruct commit_baton {
8508251881Speter  svn_revnum_t *new_rev_p;
8509251881Speter  svn_fs_t *fs;
8510251881Speter  svn_fs_txn_t *txn;
8511251881Speter  apr_array_header_t *reps_to_cache;
8512251881Speter  apr_hash_t *reps_hash;
8513251881Speter  apr_pool_t *reps_pool;
8514251881Speter};
8515251881Speter
8516251881Speter/* The work-horse for svn_fs_fs__commit, called with the FS write lock.
8517251881Speter   This implements the svn_fs_fs__with_write_lock() 'body' callback
8518251881Speter   type.  BATON is a 'struct commit_baton *'. */
8519251881Speterstatic svn_error_t *
8520251881Spetercommit_body(void *baton, apr_pool_t *pool)
8521251881Speter{
8522251881Speter  struct commit_baton *cb = baton;
8523251881Speter  fs_fs_data_t *ffd = cb->fs->fsap_data;
8524251881Speter  const char *old_rev_filename, *rev_filename, *proto_filename;
8525251881Speter  const char *revprop_filename, *final_revprop;
8526251881Speter  const svn_fs_id_t *root_id, *new_root_id;
8527251881Speter  const char *start_node_id = NULL, *start_copy_id = NULL;
8528251881Speter  svn_revnum_t old_rev, new_rev;
8529251881Speter  apr_file_t *proto_file;
8530251881Speter  void *proto_file_lockcookie;
8531251881Speter  apr_off_t initial_offset, changed_path_offset;
8532251881Speter  char *buf;
8533251881Speter  apr_hash_t *txnprops;
8534251881Speter  apr_array_header_t *txnprop_list;
8535251881Speter  svn_prop_t prop;
8536251881Speter  svn_string_t date;
8537251881Speter
8538251881Speter  /* Get the current youngest revision. */
8539251881Speter  SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
8540251881Speter
8541251881Speter  /* Check to make sure this transaction is based off the most recent
8542251881Speter     revision. */
8543251881Speter  if (cb->txn->base_rev != old_rev)
8544251881Speter    return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
8545251881Speter                            _("Transaction out of date"));
8546251881Speter
8547251881Speter  /* Locks may have been added (or stolen) between the calling of
8548251881Speter     previous svn_fs.h functions and svn_fs_commit_txn(), so we need
8549251881Speter     to re-examine every changed-path in the txn and re-verify all
8550251881Speter     discovered locks. */
8551251881Speter  SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
8552251881Speter
8553251881Speter  /* Get the next node_id and copy_id to use. */
8554251881Speter  if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8555251881Speter    SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
8556251881Speter                                  pool));
8557251881Speter
8558251881Speter  /* We are going to be one better than this puny old revision. */
8559251881Speter  new_rev = old_rev + 1;
8560251881Speter
8561251881Speter  /* Get a write handle on the proto revision file. */
8562251881Speter  SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
8563251881Speter                                 cb->fs, cb->txn->id, pool));
8564251881Speter  SVN_ERR(get_file_offset(&initial_offset, proto_file, pool));
8565251881Speter
8566251881Speter  /* Write out all the node-revisions and directory contents. */
8567251881Speter  root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
8568251881Speter  SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
8569251881Speter                          start_node_id, start_copy_id, initial_offset,
8570251881Speter                          cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
8571251881Speter                          TRUE, pool));
8572251881Speter
8573251881Speter  /* Write the changed-path information. */
8574251881Speter  SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
8575251881Speter                                        cb->fs, cb->txn->id, pool));
8576251881Speter
8577251881Speter  /* Write the final line. */
8578251881Speter  buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
8579251881Speter                     svn_fs_fs__id_offset(new_root_id),
8580251881Speter                     changed_path_offset);
8581251881Speter  SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
8582251881Speter                                 pool));
8583251881Speter  SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
8584251881Speter  SVN_ERR(svn_io_file_close(proto_file, pool));
8585251881Speter
8586251881Speter  /* We don't unlock the prototype revision file immediately to avoid a
8587251881Speter     race with another caller writing to the prototype revision file
8588251881Speter     before we commit it. */
8589251881Speter
8590251881Speter  /* Remove any temporary txn props representing 'flags'. */
8591251881Speter  SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
8592251881Speter  txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t));
8593251881Speter  prop.value = NULL;
8594251881Speter
8595251881Speter  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
8596251881Speter    {
8597251881Speter      prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
8598251881Speter      APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8599251881Speter    }
8600251881Speter
8601251881Speter  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
8602251881Speter    {
8603251881Speter      prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
8604251881Speter      APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8605251881Speter    }
8606251881Speter
8607251881Speter  if (! apr_is_empty_array(txnprop_list))
8608251881Speter    SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool));
8609251881Speter
8610251881Speter  /* Create the shard for the rev and revprop file, if we're sharding and
8611251881Speter     this is the first revision of a new shard.  We don't care if this
8612251881Speter     fails because the shard already existed for some reason. */
8613251881Speter  if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
8614251881Speter    {
8615251881Speter      /* Create the revs shard. */
8616251881Speter        {
8617251881Speter          const char *new_dir = path_rev_shard(cb->fs, new_rev, pool);
8618251881Speter          svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8619251881Speter          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8620251881Speter            return svn_error_trace(err);
8621251881Speter          svn_error_clear(err);
8622251881Speter          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8623251881Speter                                                    PATH_REVS_DIR,
8624251881Speter                                                    pool),
8625251881Speter                                    new_dir, pool));
8626251881Speter        }
8627251881Speter
8628251881Speter      /* Create the revprops shard. */
8629251881Speter      SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8630251881Speter        {
8631251881Speter          const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool);
8632251881Speter          svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8633251881Speter          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8634251881Speter            return svn_error_trace(err);
8635251881Speter          svn_error_clear(err);
8636251881Speter          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8637251881Speter                                                    PATH_REVPROPS_DIR,
8638251881Speter                                                    pool),
8639251881Speter                                    new_dir, pool));
8640251881Speter        }
8641251881Speter    }
8642251881Speter
8643251881Speter  /* Move the finished rev file into place. */
8644251881Speter  SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename,
8645251881Speter                                       cb->fs, old_rev, pool));
8646251881Speter  rev_filename = path_rev(cb->fs, new_rev, pool);
8647251881Speter  proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
8648251881Speter  SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename,
8649251881Speter                          pool));
8650251881Speter
8651251881Speter  /* Now that we've moved the prototype revision file out of the way,
8652251881Speter     we can unlock it (since further attempts to write to the file
8653251881Speter     will fail as it no longer exists).  We must do this so that we can
8654251881Speter     remove the transaction directory later. */
8655251881Speter  SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
8656251881Speter
8657251881Speter  /* Update commit time to ensure that svn:date revprops remain ordered. */
8658251881Speter  date.data = svn_time_to_cstring(apr_time_now(), pool);
8659251881Speter  date.len = strlen(date.data);
8660251881Speter
8661251881Speter  SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
8662251881Speter                                     &date, pool));
8663251881Speter
8664251881Speter  /* Move the revprops file into place. */
8665251881Speter  SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8666251881Speter  revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
8667251881Speter  final_revprop = path_revprops(cb->fs, new_rev, pool);
8668251881Speter  SVN_ERR(move_into_place(revprop_filename, final_revprop,
8669251881Speter                          old_rev_filename, pool));
8670251881Speter
8671251881Speter  /* Update the 'current' file. */
8672251881Speter  SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
8673251881Speter  SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
8674251881Speter                              start_copy_id, pool));
8675251881Speter
8676251881Speter  /* At this point the new revision is committed and globally visible
8677251881Speter     so let the caller know it succeeded by giving it the new revision
8678251881Speter     number, which fulfills svn_fs_commit_txn() contract.  Any errors
8679251881Speter     after this point do not change the fact that a new revision was
8680251881Speter     created. */
8681251881Speter  *cb->new_rev_p = new_rev;
8682251881Speter
8683251881Speter  ffd->youngest_rev_cache = new_rev;
8684251881Speter
8685251881Speter  /* Remove this transaction directory. */
8686251881Speter  SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
8687251881Speter
8688251881Speter  return SVN_NO_ERROR;
8689251881Speter}
8690251881Speter
8691251881Speter/* Add the representations in REPS_TO_CACHE (an array of representation_t *)
8692251881Speter * to the rep-cache database of FS. */
8693251881Speterstatic svn_error_t *
8694251881Speterwrite_reps_to_cache(svn_fs_t *fs,
8695251881Speter                    const apr_array_header_t *reps_to_cache,
8696251881Speter                    apr_pool_t *scratch_pool)
8697251881Speter{
8698251881Speter  int i;
8699251881Speter
8700251881Speter  for (i = 0; i < reps_to_cache->nelts; i++)
8701251881Speter    {
8702251881Speter      representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
8703251881Speter
8704251881Speter      /* FALSE because we don't care if another parallel commit happened to
8705251881Speter       * collide with us.  (Non-parallel collisions will not be detected.) */
8706251881Speter      SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool));
8707251881Speter    }
8708251881Speter
8709251881Speter  return SVN_NO_ERROR;
8710251881Speter}
8711251881Speter
8712251881Spetersvn_error_t *
8713251881Spetersvn_fs_fs__commit(svn_revnum_t *new_rev_p,
8714251881Speter                  svn_fs_t *fs,
8715251881Speter                  svn_fs_txn_t *txn,
8716251881Speter                  apr_pool_t *pool)
8717251881Speter{
8718251881Speter  struct commit_baton cb;
8719251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
8720251881Speter
8721251881Speter  cb.new_rev_p = new_rev_p;
8722251881Speter  cb.fs = fs;
8723251881Speter  cb.txn = txn;
8724251881Speter
8725251881Speter  if (ffd->rep_sharing_allowed)
8726251881Speter    {
8727251881Speter      cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
8728251881Speter      cb.reps_hash = apr_hash_make(pool);
8729251881Speter      cb.reps_pool = pool;
8730251881Speter    }
8731251881Speter  else
8732251881Speter    {
8733251881Speter      cb.reps_to_cache = NULL;
8734251881Speter      cb.reps_hash = NULL;
8735251881Speter      cb.reps_pool = NULL;
8736251881Speter    }
8737251881Speter
8738251881Speter  SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
8739251881Speter
8740251881Speter  /* At this point, *NEW_REV_P has been set, so errors below won't affect
8741251881Speter     the success of the commit.  (See svn_fs_commit_txn().)  */
8742251881Speter
8743251881Speter  if (ffd->rep_sharing_allowed)
8744251881Speter    {
8745251881Speter      SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
8746251881Speter
8747251881Speter      /* Write new entries to the rep-sharing database.
8748251881Speter       *
8749251881Speter       * We use an sqlite transaction to speed things up;
8750251881Speter       * see <http://www.sqlite.org/faq.html#q19>.
8751251881Speter       */
8752251881Speter      SVN_SQLITE__WITH_TXN(
8753251881Speter        write_reps_to_cache(fs, cb.reps_to_cache, pool),
8754251881Speter        ffd->rep_cache_db);
8755251881Speter    }
8756251881Speter
8757251881Speter  return SVN_NO_ERROR;
8758251881Speter}
8759251881Speter
8760251881Speter
8761251881Spetersvn_error_t *
8762251881Spetersvn_fs_fs__reserve_copy_id(const char **copy_id_p,
8763251881Speter                           svn_fs_t *fs,
8764251881Speter                           const char *txn_id,
8765251881Speter                           apr_pool_t *pool)
8766251881Speter{
8767251881Speter  const char *cur_node_id, *cur_copy_id;
8768251881Speter  char *copy_id;
8769251881Speter  apr_size_t len;
8770251881Speter
8771251881Speter  /* First read in the current next-ids file. */
8772251881Speter  SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
8773251881Speter
8774251881Speter  copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
8775251881Speter
8776251881Speter  len = strlen(cur_copy_id);
8777251881Speter  svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
8778251881Speter
8779251881Speter  SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
8780251881Speter
8781251881Speter  *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL);
8782251881Speter
8783251881Speter  return SVN_NO_ERROR;
8784251881Speter}
8785251881Speter
8786251881Speter/* Write out the zeroth revision for filesystem FS. */
8787251881Speterstatic svn_error_t *
8788251881Speterwrite_revision_zero(svn_fs_t *fs)
8789251881Speter{
8790251881Speter  const char *path_revision_zero = path_rev(fs, 0, fs->pool);
8791251881Speter  apr_hash_t *proplist;
8792251881Speter  svn_string_t date;
8793251881Speter
8794251881Speter  /* Write out a rev file for revision 0. */
8795251881Speter  SVN_ERR(svn_io_file_create(path_revision_zero,
8796251881Speter                             "PLAIN\nEND\nENDREP\n"
8797251881Speter                             "id: 0.0.r0/17\n"
8798251881Speter                             "type: dir\n"
8799251881Speter                             "count: 0\n"
8800251881Speter                             "text: 0 0 4 4 "
8801251881Speter                             "2d2977d1c96f487abe4a1e202dd03b4e\n"
8802251881Speter                             "cpath: /\n"
8803251881Speter                             "\n\n17 107\n", fs->pool));
8804251881Speter  SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
8805251881Speter
8806251881Speter  /* Set a date on revision 0. */
8807251881Speter  date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
8808251881Speter  date.len = strlen(date.data);
8809251881Speter  proplist = apr_hash_make(fs->pool);
8810251881Speter  svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
8811251881Speter  return set_revision_proplist(fs, 0, proplist, fs->pool);
8812251881Speter}
8813251881Speter
8814251881Spetersvn_error_t *
8815251881Spetersvn_fs_fs__create(svn_fs_t *fs,
8816251881Speter                  const char *path,
8817251881Speter                  apr_pool_t *pool)
8818251881Speter{
8819251881Speter  int format = SVN_FS_FS__FORMAT_NUMBER;
8820251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
8821251881Speter
8822251881Speter  fs->path = apr_pstrdup(pool, path);
8823251881Speter  /* See if compatibility with older versions was explicitly requested. */
8824251881Speter  if (fs->config)
8825251881Speter    {
8826251881Speter      if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
8827251881Speter        format = 1;
8828251881Speter      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
8829251881Speter        format = 2;
8830251881Speter      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
8831251881Speter        format = 3;
8832251881Speter      else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE))
8833251881Speter        format = 4;
8834251881Speter    }
8835251881Speter  ffd->format = format;
8836251881Speter
8837251881Speter  /* Override the default linear layout if this is a new-enough format. */
8838251881Speter  if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
8839251881Speter    ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
8840251881Speter
8841251881Speter  /* Create the revision data directories. */
8842251881Speter  if (ffd->max_files_per_dir)
8843251881Speter    SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool));
8844251881Speter  else
8845251881Speter    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
8846251881Speter                                                        pool),
8847251881Speter                                        pool));
8848251881Speter
8849251881Speter  /* Create the revprops directory. */
8850251881Speter  if (ffd->max_files_per_dir)
8851251881Speter    SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool),
8852251881Speter                                        pool));
8853251881Speter  else
8854251881Speter    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
8855251881Speter                                                        PATH_REVPROPS_DIR,
8856251881Speter                                                        pool),
8857251881Speter                                        pool));
8858251881Speter
8859251881Speter  /* Create the transaction directory. */
8860251881Speter  SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR,
8861251881Speter                                                      pool),
8862251881Speter                                      pool));
8863251881Speter
8864251881Speter  /* Create the protorevs directory. */
8865251881Speter  if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
8866251881Speter    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR,
8867251881Speter                                                      pool),
8868251881Speter                                        pool));
8869251881Speter
8870251881Speter  /* Create the 'current' file. */
8871251881Speter  SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
8872251881Speter                             (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
8873251881Speter                              ? "0\n" : "0 1 1\n"),
8874251881Speter                             pool));
8875251881Speter  SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool));
8876251881Speter  SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool));
8877251881Speter
8878251881Speter  SVN_ERR(write_revision_zero(fs));
8879251881Speter
8880269847Speter  /* Create the fsfs.conf file if supported.  Older server versions would
8881269847Speter     simply ignore the file but that might result in a different behavior
8882269847Speter     than with the later releases.  Also, hotcopy would ignore, i.e. not
8883269847Speter     copy, a fsfs.conf with old formats. */
8884269847Speter  if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
8885269847Speter    SVN_ERR(write_config(fs, pool));
8886251881Speter
8887251881Speter  SVN_ERR(read_config(ffd, fs->path, pool));
8888251881Speter
8889251881Speter  /* Create the min unpacked rev file. */
8890251881Speter  if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
8891251881Speter    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
8892251881Speter
8893251881Speter  /* Create the txn-current file if the repository supports
8894251881Speter     the transaction sequence file. */
8895251881Speter  if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
8896251881Speter    {
8897251881Speter      SVN_ERR(svn_io_file_create(path_txn_current(fs, pool),
8898251881Speter                                 "0\n", pool));
8899251881Speter      SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool),
8900251881Speter                                 "", pool));
8901251881Speter    }
8902251881Speter
8903251881Speter  /* This filesystem is ready.  Stamp it with a format number. */
8904251881Speter  SVN_ERR(write_format(path_format(fs, pool),
8905251881Speter                       ffd->format, ffd->max_files_per_dir, FALSE, pool));
8906251881Speter
8907251881Speter  ffd->youngest_rev_cache = 0;
8908251881Speter  return SVN_NO_ERROR;
8909251881Speter}
8910251881Speter
8911251881Speter/* Part of the recovery procedure.  Return the largest revision *REV in
8912251881Speter   filesystem FS.  Use POOL for temporary allocation. */
8913251881Speterstatic svn_error_t *
8914251881Speterrecover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
8915251881Speter{
8916251881Speter  /* Discovering the largest revision in the filesystem would be an
8917251881Speter     expensive operation if we did a readdir() or searched linearly,
8918251881Speter     so we'll do a form of binary search.  left is a revision that we
8919251881Speter     know exists, right a revision that we know does not exist. */
8920251881Speter  apr_pool_t *iterpool;
8921251881Speter  svn_revnum_t left, right = 1;
8922251881Speter
8923251881Speter  iterpool = svn_pool_create(pool);
8924251881Speter  /* Keep doubling right, until we find a revision that doesn't exist. */
8925251881Speter  while (1)
8926251881Speter    {
8927251881Speter      svn_error_t *err;
8928251881Speter      apr_file_t *file;
8929251881Speter
8930251881Speter      err = open_pack_or_rev_file(&file, fs, right, iterpool);
8931251881Speter      svn_pool_clear(iterpool);
8932251881Speter
8933251881Speter      if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8934251881Speter        {
8935251881Speter          svn_error_clear(err);
8936251881Speter          break;
8937251881Speter        }
8938251881Speter      else
8939251881Speter        SVN_ERR(err);
8940251881Speter
8941251881Speter      right <<= 1;
8942251881Speter    }
8943251881Speter
8944251881Speter  left = right >> 1;
8945251881Speter
8946251881Speter  /* We know that left exists and right doesn't.  Do a normal bsearch to find
8947251881Speter     the last revision. */
8948251881Speter  while (left + 1 < right)
8949251881Speter    {
8950251881Speter      svn_revnum_t probe = left + ((right - left) / 2);
8951251881Speter      svn_error_t *err;
8952251881Speter      apr_file_t *file;
8953251881Speter
8954251881Speter      err = open_pack_or_rev_file(&file, fs, probe, iterpool);
8955251881Speter      svn_pool_clear(iterpool);
8956251881Speter
8957251881Speter      if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8958251881Speter        {
8959251881Speter          svn_error_clear(err);
8960251881Speter          right = probe;
8961251881Speter        }
8962251881Speter      else
8963251881Speter        {
8964251881Speter          SVN_ERR(err);
8965251881Speter          left = probe;
8966251881Speter        }
8967251881Speter    }
8968251881Speter
8969251881Speter  svn_pool_destroy(iterpool);
8970251881Speter
8971251881Speter  /* left is now the largest revision that exists. */
8972251881Speter  *rev = left;
8973251881Speter  return SVN_NO_ERROR;
8974251881Speter}
8975251881Speter
8976251881Speter/* A baton for reading a fixed amount from an open file.  For
8977251881Speter   recover_find_max_ids() below. */
8978251881Speterstruct recover_read_from_file_baton
8979251881Speter{
8980251881Speter  apr_file_t *file;
8981251881Speter  apr_pool_t *pool;
8982251881Speter  apr_off_t remaining;
8983251881Speter};
8984251881Speter
8985251881Speter/* A stream read handler used by recover_find_max_ids() below.
8986251881Speter   Read and return at most BATON->REMAINING bytes from the stream,
8987251881Speter   returning nothing after that to indicate EOF. */
8988251881Speterstatic svn_error_t *
8989251881Speterread_handler_recover(void *baton, char *buffer, apr_size_t *len)
8990251881Speter{
8991251881Speter  struct recover_read_from_file_baton *b = baton;
8992251881Speter  svn_filesize_t bytes_to_read = *len;
8993251881Speter
8994251881Speter  if (b->remaining == 0)
8995251881Speter    {
8996251881Speter      /* Return a successful read of zero bytes to signal EOF. */
8997251881Speter      *len = 0;
8998251881Speter      return SVN_NO_ERROR;
8999251881Speter    }
9000251881Speter
9001251881Speter  if (bytes_to_read > b->remaining)
9002251881Speter    bytes_to_read = b->remaining;
9003251881Speter  b->remaining -= bytes_to_read;
9004251881Speter
9005251881Speter  return svn_io_file_read_full2(b->file, buffer, (apr_size_t) bytes_to_read,
9006251881Speter                                len, NULL, b->pool);
9007251881Speter}
9008251881Speter
9009251881Speter/* Part of the recovery procedure.  Read the directory noderev at offset
9010251881Speter   OFFSET of file REV_FILE (the revision file of revision REV of
9011251881Speter   filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
9012251881Speter   and copy-id of that node, if greater than the current value stored
9013251881Speter   in either.  Recurse into any child directories that were modified in
9014251881Speter   this revision.
9015251881Speter
9016251881Speter   MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
9017251881Speter
9018251881Speter   Perform temporary allocation in POOL. */
9019251881Speterstatic svn_error_t *
9020251881Speterrecover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev,
9021251881Speter                     apr_file_t *rev_file, apr_off_t offset,
9022251881Speter                     char *max_node_id, char *max_copy_id,
9023251881Speter                     apr_pool_t *pool)
9024251881Speter{
9025251881Speter  apr_hash_t *headers;
9026251881Speter  char *value;
9027251881Speter  representation_t *data_rep;
9028251881Speter  struct rep_args *ra;
9029251881Speter  struct recover_read_from_file_baton baton;
9030251881Speter  svn_stream_t *stream;
9031251881Speter  apr_hash_t *entries;
9032251881Speter  apr_hash_index_t *hi;
9033251881Speter  apr_pool_t *iterpool;
9034251881Speter
9035251881Speter  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9036251881Speter  SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile2(rev_file, TRUE,
9037251881Speter                                                               pool),
9038251881Speter                            pool));
9039251881Speter
9040251881Speter  /* Check that this is a directory.  It should be. */
9041251881Speter  value = svn_hash_gets(headers, HEADER_TYPE);
9042251881Speter  if (value == NULL || strcmp(value, KIND_DIR) != 0)
9043251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9044251881Speter                            _("Recovery encountered a non-directory node"));
9045251881Speter
9046251881Speter  /* Get the data location.  No data location indicates an empty directory. */
9047251881Speter  value = svn_hash_gets(headers, HEADER_TEXT);
9048251881Speter  if (!value)
9049251881Speter    return SVN_NO_ERROR;
9050251881Speter  SVN_ERR(read_rep_offsets(&data_rep, value, NULL, FALSE, pool));
9051251881Speter
9052251881Speter  /* If the directory's data representation wasn't changed in this revision,
9053251881Speter     we've already scanned the directory's contents for noderevs, so we don't
9054251881Speter     need to again.  This will occur if a property is changed on a directory
9055251881Speter     without changing the directory's contents. */
9056251881Speter  if (data_rep->revision != rev)
9057251881Speter    return SVN_NO_ERROR;
9058251881Speter
9059251881Speter  /* We could use get_dir_contents(), but this is much cheaper.  It does
9060251881Speter     rely on directory entries being stored as PLAIN reps, though. */
9061251881Speter  offset = data_rep->offset;
9062251881Speter  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9063251881Speter  SVN_ERR(read_rep_line(&ra, rev_file, pool));
9064251881Speter  if (ra->is_delta)
9065251881Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9066251881Speter                            _("Recovery encountered a deltified directory "
9067251881Speter                              "representation"));
9068251881Speter
9069251881Speter  /* Now create a stream that's allowed to read only as much data as is
9070251881Speter     stored in the representation. */
9071251881Speter  baton.file = rev_file;
9072251881Speter  baton.pool = pool;
9073251881Speter  baton.remaining = data_rep->expanded_size;
9074251881Speter  stream = svn_stream_create(&baton, pool);
9075251881Speter  svn_stream_set_read(stream, read_handler_recover);
9076251881Speter
9077251881Speter  /* Now read the entries from that stream. */
9078251881Speter  entries = apr_hash_make(pool);
9079251881Speter  SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
9080251881Speter  SVN_ERR(svn_stream_close(stream));
9081251881Speter
9082251881Speter  /* Now check each of the entries in our directory to find new node and
9083251881Speter     copy ids, and recurse into new subdirectories. */
9084251881Speter  iterpool = svn_pool_create(pool);
9085251881Speter  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
9086251881Speter    {
9087251881Speter      char *str_val;
9088251881Speter      char *str;
9089251881Speter      svn_node_kind_t kind;
9090251881Speter      svn_fs_id_t *id;
9091251881Speter      const char *node_id, *copy_id;
9092251881Speter      apr_off_t child_dir_offset;
9093251881Speter      const svn_string_t *path = svn__apr_hash_index_val(hi);
9094251881Speter
9095251881Speter      svn_pool_clear(iterpool);
9096251881Speter
9097251881Speter      str_val = apr_pstrdup(iterpool, path->data);
9098251881Speter
9099251881Speter      str = svn_cstring_tokenize(" ", &str_val);
9100251881Speter      if (str == NULL)
9101251881Speter        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9102251881Speter                                _("Directory entry corrupt"));
9103251881Speter
9104251881Speter      if (strcmp(str, KIND_FILE) == 0)
9105251881Speter        kind = svn_node_file;
9106251881Speter      else if (strcmp(str, KIND_DIR) == 0)
9107251881Speter        kind = svn_node_dir;
9108251881Speter      else
9109251881Speter        {
9110251881Speter          return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9111251881Speter                                  _("Directory entry corrupt"));
9112251881Speter        }
9113251881Speter
9114251881Speter      str = svn_cstring_tokenize(" ", &str_val);
9115251881Speter      if (str == NULL)
9116251881Speter        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9117251881Speter                                _("Directory entry corrupt"));
9118251881Speter
9119251881Speter      id = svn_fs_fs__id_parse(str, strlen(str), iterpool);
9120251881Speter
9121251881Speter      if (svn_fs_fs__id_rev(id) != rev)
9122251881Speter        {
9123251881Speter          /* If the node wasn't modified in this revision, we've already
9124251881Speter             checked the node and copy id. */
9125251881Speter          continue;
9126251881Speter        }
9127251881Speter
9128251881Speter      node_id = svn_fs_fs__id_node_id(id);
9129251881Speter      copy_id = svn_fs_fs__id_copy_id(id);
9130251881Speter
9131251881Speter      if (svn_fs_fs__key_compare(node_id, max_node_id) > 0)
9132251881Speter        {
9133251881Speter          SVN_ERR_ASSERT(strlen(node_id) < MAX_KEY_SIZE);
9134251881Speter          apr_cpystrn(max_node_id, node_id, MAX_KEY_SIZE);
9135251881Speter        }
9136251881Speter      if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0)
9137251881Speter        {
9138251881Speter          SVN_ERR_ASSERT(strlen(copy_id) < MAX_KEY_SIZE);
9139251881Speter          apr_cpystrn(max_copy_id, copy_id, MAX_KEY_SIZE);
9140251881Speter        }
9141251881Speter
9142251881Speter      if (kind == svn_node_file)
9143251881Speter        continue;
9144251881Speter
9145251881Speter      child_dir_offset = svn_fs_fs__id_offset(id);
9146251881Speter      SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
9147251881Speter                                   max_node_id, max_copy_id, iterpool));
9148251881Speter    }
9149251881Speter  svn_pool_destroy(iterpool);
9150251881Speter
9151251881Speter  return SVN_NO_ERROR;
9152251881Speter}
9153251881Speter
9154251881Speter/* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
9155251881Speter * Use POOL for temporary allocations.
9156251881Speter * Set *MISSING, if the reason is a missing manifest or pack file.
9157251881Speter */
9158251881Speterstatic svn_boolean_t
9159251881Speterpacked_revprop_available(svn_boolean_t *missing,
9160251881Speter                         svn_fs_t *fs,
9161251881Speter                         svn_revnum_t revision,
9162251881Speter                         apr_pool_t *pool)
9163251881Speter{
9164251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
9165251881Speter  svn_stringbuf_t *content = NULL;
9166251881Speter
9167251881Speter  /* try to read the manifest file */
9168251881Speter  const char *folder = path_revprops_pack_shard(fs, revision, pool);
9169251881Speter  const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool);
9170251881Speter
9171251881Speter  svn_error_t *err = try_stringbuf_from_file(&content,
9172251881Speter                                             missing,
9173251881Speter                                             manifest_path,
9174251881Speter                                             FALSE,
9175251881Speter                                             pool);
9176251881Speter
9177251881Speter  /* if the manifest cannot be read, consider the pack files inaccessible
9178251881Speter   * even if the file itself exists. */
9179251881Speter  if (err)
9180251881Speter    {
9181251881Speter      svn_error_clear(err);
9182251881Speter      return FALSE;
9183251881Speter    }
9184251881Speter
9185251881Speter  if (*missing)
9186251881Speter    return FALSE;
9187251881Speter
9188251881Speter  /* parse manifest content until we find the entry for REVISION.
9189251881Speter   * Revision 0 is never packed. */
9190251881Speter  revision = revision < ffd->max_files_per_dir
9191251881Speter           ? revision - 1
9192251881Speter           : revision % ffd->max_files_per_dir;
9193251881Speter  while (content->data)
9194251881Speter    {
9195251881Speter      char *next = strchr(content->data, '\n');
9196251881Speter      if (next)
9197251881Speter        {
9198251881Speter          *next = 0;
9199251881Speter          ++next;
9200251881Speter        }
9201251881Speter
9202251881Speter      if (revision-- == 0)
9203251881Speter        {
9204251881Speter          /* the respective pack file must exist (and be a file) */
9205251881Speter          svn_node_kind_t kind;
9206251881Speter          err = svn_io_check_path(svn_dirent_join(folder, content->data,
9207251881Speter                                                  pool),
9208251881Speter                                  &kind, pool);
9209251881Speter          if (err)
9210251881Speter            {
9211251881Speter              svn_error_clear(err);
9212251881Speter              return FALSE;
9213251881Speter            }
9214251881Speter
9215251881Speter          *missing = kind == svn_node_none;
9216251881Speter          return kind == svn_node_file;
9217251881Speter        }
9218251881Speter
9219251881Speter      content->data = next;
9220251881Speter    }
9221251881Speter
9222251881Speter  return FALSE;
9223251881Speter}
9224251881Speter
9225251881Speter/* Baton used for recover_body below. */
9226251881Speterstruct recover_baton {
9227251881Speter  svn_fs_t *fs;
9228251881Speter  svn_cancel_func_t cancel_func;
9229251881Speter  void *cancel_baton;
9230251881Speter};
9231251881Speter
9232251881Speter/* The work-horse for svn_fs_fs__recover, called with the FS
9233251881Speter   write lock.  This implements the svn_fs_fs__with_write_lock()
9234251881Speter   'body' callback type.  BATON is a 'struct recover_baton *'. */
9235251881Speterstatic svn_error_t *
9236251881Speterrecover_body(void *baton, apr_pool_t *pool)
9237251881Speter{
9238251881Speter  struct recover_baton *b = baton;
9239251881Speter  svn_fs_t *fs = b->fs;
9240251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
9241251881Speter  svn_revnum_t max_rev;
9242251881Speter  char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE];
9243251881Speter  char *next_node_id = NULL, *next_copy_id = NULL;
9244251881Speter  svn_revnum_t youngest_rev;
9245251881Speter  svn_node_kind_t youngest_revprops_kind;
9246251881Speter
9247251881Speter  /* Lose potentially corrupted data in temp files */
9248251881Speter  SVN_ERR(cleanup_revprop_namespace(fs));
9249251881Speter
9250251881Speter  /* We need to know the largest revision in the filesystem. */
9251251881Speter  SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
9252251881Speter
9253251881Speter  /* Get the expected youngest revision */
9254251881Speter  SVN_ERR(get_youngest(&youngest_rev, fs->path, pool));
9255251881Speter
9256251881Speter  /* Policy note:
9257251881Speter
9258251881Speter     Since the revprops file is written after the revs file, the true
9259251881Speter     maximum available revision is the youngest one for which both are
9260251881Speter     present.  That's probably the same as the max_rev we just found,
9261251881Speter     but if it's not, we could, in theory, repeatedly decrement
9262251881Speter     max_rev until we find a revision that has both a revs and
9263251881Speter     revprops file, then write db/current with that.
9264251881Speter
9265251881Speter     But we choose not to.  If a repository is so corrupt that it's
9266251881Speter     missing at least one revprops file, we shouldn't assume that the
9267251881Speter     youngest revision for which both the revs and revprops files are
9268251881Speter     present is healthy.  In other words, we're willing to recover
9269251881Speter     from a missing or out-of-date db/current file, because db/current
9270251881Speter     is truly redundant -- it's basically a cache so we don't have to
9271251881Speter     find max_rev each time, albeit a cache with unusual semantics,
9272251881Speter     since it also officially defines when a revision goes live.  But
9273251881Speter     if we're missing more than the cache, it's time to back out and
9274251881Speter     let the admin reconstruct things by hand: correctness at that
9275251881Speter     point may depend on external things like checking a commit email
9276251881Speter     list, looking in particular working copies, etc.
9277251881Speter
9278251881Speter     This policy matches well with a typical naive backup scenario.
9279251881Speter     Say you're rsyncing your FSFS repository nightly to the same
9280251881Speter     location.  Once revs and revprops are written, you've got the
9281251881Speter     maximum rev; if the backup should bomb before db/current is
9282251881Speter     written, then db/current could stay arbitrarily out-of-date, but
9283251881Speter     we can still recover.  It's a small window, but we might as well
9284251881Speter     do what we can. */
9285251881Speter
9286251881Speter  /* Even if db/current were missing, it would be created with 0 by
9287251881Speter     get_youngest(), so this conditional remains valid. */
9288251881Speter  if (youngest_rev > max_rev)
9289251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9290251881Speter                             _("Expected current rev to be <= %ld "
9291251881Speter                               "but found %ld"), max_rev, youngest_rev);
9292251881Speter
9293251881Speter  /* We only need to search for maximum IDs for old FS formats which
9294251881Speter     se global ID counters. */
9295251881Speter  if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
9296251881Speter    {
9297251881Speter      /* Next we need to find the maximum node id and copy id in use across the
9298251881Speter         filesystem.  Unfortunately, the only way we can get this information
9299251881Speter         is to scan all the noderevs of all the revisions and keep track as
9300251881Speter         we go along. */
9301251881Speter      svn_revnum_t rev;
9302251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
9303251881Speter      char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0";
9304251881Speter      apr_size_t len;
9305251881Speter
9306251881Speter      for (rev = 0; rev <= max_rev; rev++)
9307251881Speter        {
9308251881Speter          apr_file_t *rev_file;
9309251881Speter          apr_off_t root_offset;
9310251881Speter
9311251881Speter          svn_pool_clear(iterpool);
9312251881Speter
9313251881Speter          if (b->cancel_func)
9314251881Speter            SVN_ERR(b->cancel_func(b->cancel_baton));
9315251881Speter
9316251881Speter          SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, iterpool));
9317251881Speter          SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, fs, rev,
9318251881Speter                                          iterpool));
9319251881Speter          SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
9320251881Speter                                       max_node_id, max_copy_id, iterpool));
9321251881Speter          SVN_ERR(svn_io_file_close(rev_file, iterpool));
9322251881Speter        }
9323251881Speter      svn_pool_destroy(iterpool);
9324251881Speter
9325251881Speter      /* Now that we finally have the maximum revision, node-id and copy-id, we
9326251881Speter         can bump the two ids to get the next of each. */
9327251881Speter      len = strlen(max_node_id);
9328251881Speter      svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf);
9329251881Speter      next_node_id = next_node_id_buf;
9330251881Speter      len = strlen(max_copy_id);
9331251881Speter      svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf);
9332251881Speter      next_copy_id = next_copy_id_buf;
9333251881Speter    }
9334251881Speter
9335251881Speter  /* Before setting current, verify that there is a revprops file
9336251881Speter     for the youngest revision.  (Issue #2992) */
9337251881Speter  SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool),
9338251881Speter                            &youngest_revprops_kind, pool));
9339251881Speter  if (youngest_revprops_kind == svn_node_none)
9340251881Speter    {
9341251881Speter      svn_boolean_t missing = TRUE;
9342251881Speter      if (!packed_revprop_available(&missing, fs, max_rev, pool))
9343251881Speter        {
9344251881Speter          if (missing)
9345251881Speter            {
9346251881Speter              return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9347251881Speter                                      _("Revision %ld has a revs file but no "
9348251881Speter                                        "revprops file"),
9349251881Speter                                      max_rev);
9350251881Speter            }
9351251881Speter          else
9352251881Speter            {
9353251881Speter              return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9354251881Speter                                      _("Revision %ld has a revs file but the "
9355251881Speter                                        "revprops file is inaccessible"),
9356251881Speter                                      max_rev);
9357251881Speter            }
9358251881Speter          }
9359251881Speter    }
9360251881Speter  else if (youngest_revprops_kind != svn_node_file)
9361251881Speter    {
9362251881Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9363251881Speter                               _("Revision %ld has a non-file where its "
9364251881Speter                                 "revprops file should be"),
9365251881Speter                               max_rev);
9366251881Speter    }
9367251881Speter
9368251881Speter  /* Prune younger-than-(newfound-youngest) revisions from the rep
9369251881Speter     cache if sharing is enabled taking care not to create the cache
9370251881Speter     if it does not exist. */
9371251881Speter  if (ffd->rep_sharing_allowed)
9372251881Speter    {
9373251881Speter      svn_boolean_t rep_cache_exists;
9374251881Speter
9375251881Speter      SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool));
9376251881Speter      if (rep_cache_exists)
9377251881Speter        SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool));
9378251881Speter    }
9379251881Speter
9380251881Speter  /* Now store the discovered youngest revision, and the next IDs if
9381251881Speter     relevant, in a new 'current' file. */
9382251881Speter  return write_current(fs, max_rev, next_node_id, next_copy_id, pool);
9383251881Speter}
9384251881Speter
9385251881Speter/* This implements the fs_library_vtable_t.recover() API. */
9386251881Spetersvn_error_t *
9387251881Spetersvn_fs_fs__recover(svn_fs_t *fs,
9388251881Speter                   svn_cancel_func_t cancel_func, void *cancel_baton,
9389251881Speter                   apr_pool_t *pool)
9390251881Speter{
9391251881Speter  struct recover_baton b;
9392251881Speter
9393251881Speter  /* We have no way to take out an exclusive lock in FSFS, so we're
9394251881Speter     restricted as to the types of recovery we can do.  Luckily,
9395251881Speter     we just want to recreate the 'current' file, and we can do that just
9396251881Speter     by blocking other writers. */
9397251881Speter  b.fs = fs;
9398251881Speter  b.cancel_func = cancel_func;
9399251881Speter  b.cancel_baton = cancel_baton;
9400251881Speter  return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool);
9401251881Speter}
9402251881Speter
9403251881Spetersvn_error_t *
9404251881Spetersvn_fs_fs__set_uuid(svn_fs_t *fs,
9405251881Speter                    const char *uuid,
9406251881Speter                    apr_pool_t *pool)
9407251881Speter{
9408251881Speter  char *my_uuid;
9409251881Speter  apr_size_t my_uuid_len;
9410251881Speter  const char *tmp_path;
9411251881Speter  const char *uuid_path = path_uuid(fs, pool);
9412251881Speter
9413251881Speter  if (! uuid)
9414251881Speter    uuid = svn_uuid_generate(pool);
9415251881Speter
9416251881Speter  /* Make sure we have a copy in FS->POOL, and append a newline. */
9417251881Speter  my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL);
9418251881Speter  my_uuid_len = strlen(my_uuid);
9419251881Speter
9420251881Speter  SVN_ERR(svn_io_write_unique(&tmp_path,
9421251881Speter                              svn_dirent_dirname(uuid_path, pool),
9422251881Speter                              my_uuid, my_uuid_len,
9423251881Speter                              svn_io_file_del_none, pool));
9424251881Speter
9425251881Speter  /* We use the permissions of the 'current' file, because the 'uuid'
9426251881Speter     file does not exist during repository creation. */
9427251881Speter  SVN_ERR(move_into_place(tmp_path, uuid_path,
9428251881Speter                          svn_fs_fs__path_current(fs, pool), pool));
9429251881Speter
9430251881Speter  /* Remove the newline we added, and stash the UUID. */
9431251881Speter  my_uuid[my_uuid_len - 1] = '\0';
9432251881Speter  fs->uuid = my_uuid;
9433251881Speter
9434251881Speter  return SVN_NO_ERROR;
9435251881Speter}
9436251881Speter
9437251881Speter/** Node origin lazy cache. */
9438251881Speter
9439251881Speter/* If directory PATH does not exist, create it and give it the same
9440251881Speter   permissions as FS_path.*/
9441251881Spetersvn_error_t *
9442251881Spetersvn_fs_fs__ensure_dir_exists(const char *path,
9443251881Speter                             const char *fs_path,
9444251881Speter                             apr_pool_t *pool)
9445251881Speter{
9446251881Speter  svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
9447251881Speter  if (err && APR_STATUS_IS_EEXIST(err->apr_err))
9448251881Speter    {
9449251881Speter      svn_error_clear(err);
9450251881Speter      return SVN_NO_ERROR;
9451251881Speter    }
9452251881Speter  SVN_ERR(err);
9453251881Speter
9454251881Speter  /* We successfully created a new directory.  Dup the permissions
9455251881Speter     from FS->path. */
9456251881Speter  return svn_io_copy_perms(fs_path, path, pool);
9457251881Speter}
9458251881Speter
9459251881Speter/* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
9460251881Speter   'svn_string_t *' node revision IDs.  Use POOL for allocations. */
9461251881Speterstatic svn_error_t *
9462251881Speterget_node_origins_from_file(svn_fs_t *fs,
9463251881Speter                           apr_hash_t **node_origins,
9464251881Speter                           const char *node_origins_file,
9465251881Speter                           apr_pool_t *pool)
9466251881Speter{
9467251881Speter  apr_file_t *fd;
9468251881Speter  svn_error_t *err;
9469251881Speter  svn_stream_t *stream;
9470251881Speter
9471251881Speter  *node_origins = NULL;
9472251881Speter  err = svn_io_file_open(&fd, node_origins_file,
9473251881Speter                         APR_READ, APR_OS_DEFAULT, pool);
9474251881Speter  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
9475251881Speter    {
9476251881Speter      svn_error_clear(err);
9477251881Speter      return SVN_NO_ERROR;
9478251881Speter    }
9479251881Speter  SVN_ERR(err);
9480251881Speter
9481251881Speter  stream = svn_stream_from_aprfile2(fd, FALSE, pool);
9482251881Speter  *node_origins = apr_hash_make(pool);
9483251881Speter  SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool));
9484251881Speter  return svn_stream_close(stream);
9485251881Speter}
9486251881Speter
9487251881Spetersvn_error_t *
9488251881Spetersvn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
9489251881Speter                           svn_fs_t *fs,
9490251881Speter                           const char *node_id,
9491251881Speter                           apr_pool_t *pool)
9492251881Speter{
9493251881Speter  apr_hash_t *node_origins;
9494251881Speter
9495251881Speter  *origin_id = NULL;
9496251881Speter  SVN_ERR(get_node_origins_from_file(fs, &node_origins,
9497251881Speter                                     path_node_origin(fs, node_id, pool),
9498251881Speter                                     pool));
9499251881Speter  if (node_origins)
9500251881Speter    {
9501251881Speter      svn_string_t *origin_id_str =
9502251881Speter        svn_hash_gets(node_origins, node_id);
9503251881Speter      if (origin_id_str)
9504251881Speter        *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
9505251881Speter                                         origin_id_str->len, pool);
9506251881Speter    }
9507251881Speter  return SVN_NO_ERROR;
9508251881Speter}
9509251881Speter
9510251881Speter
9511251881Speter/* Helper for svn_fs_fs__set_node_origin.  Takes a NODE_ID/NODE_REV_ID
9512251881Speter   pair and adds it to the NODE_ORIGINS_PATH file.  */
9513251881Speterstatic svn_error_t *
9514251881Speterset_node_origins_for_file(svn_fs_t *fs,
9515251881Speter                          const char *node_origins_path,
9516251881Speter                          const char *node_id,
9517251881Speter                          svn_string_t *node_rev_id,
9518251881Speter                          apr_pool_t *pool)
9519251881Speter{
9520251881Speter  const char *path_tmp;
9521251881Speter  svn_stream_t *stream;
9522251881Speter  apr_hash_t *origins_hash;
9523251881Speter  svn_string_t *old_node_rev_id;
9524251881Speter
9525251881Speter  SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
9526251881Speter                                                       PATH_NODE_ORIGINS_DIR,
9527251881Speter                                                       pool),
9528251881Speter                                       fs->path, pool));
9529251881Speter
9530251881Speter  /* Read the previously existing origins (if any), and merge our
9531251881Speter     update with it. */
9532251881Speter  SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
9533251881Speter                                     node_origins_path, pool));
9534251881Speter  if (! origins_hash)
9535251881Speter    origins_hash = apr_hash_make(pool);
9536251881Speter
9537251881Speter  old_node_rev_id = svn_hash_gets(origins_hash, node_id);
9538251881Speter
9539251881Speter  if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
9540251881Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9541251881Speter                             _("Node origin for '%s' exists with a different "
9542251881Speter                               "value (%s) than what we were about to store "
9543251881Speter                               "(%s)"),
9544251881Speter                             node_id, old_node_rev_id->data, node_rev_id->data);
9545251881Speter
9546251881Speter  svn_hash_sets(origins_hash, node_id, node_rev_id);
9547251881Speter
9548251881Speter  /* Sure, there's a race condition here.  Two processes could be
9549251881Speter     trying to add different cache elements to the same file at the
9550251881Speter     same time, and the entries added by the first one to write will
9551251881Speter     be lost.  But this is just a cache of reconstructible data, so
9552251881Speter     we'll accept this problem in return for not having to deal with
9553251881Speter     locking overhead. */
9554251881Speter
9555251881Speter  /* Create a temporary file, write out our hash, and close the file. */
9556251881Speter  SVN_ERR(svn_stream_open_unique(&stream, &path_tmp,
9557251881Speter                                 svn_dirent_dirname(node_origins_path, pool),
9558251881Speter                                 svn_io_file_del_none, pool, pool));
9559251881Speter  SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
9560251881Speter  SVN_ERR(svn_stream_close(stream));
9561251881Speter
9562251881Speter  /* Rename the temp file as the real destination */
9563251881Speter  return svn_io_file_rename(path_tmp, node_origins_path, pool);
9564251881Speter}
9565251881Speter
9566251881Speter
9567251881Spetersvn_error_t *
9568251881Spetersvn_fs_fs__set_node_origin(svn_fs_t *fs,
9569251881Speter                           const char *node_id,
9570251881Speter                           const svn_fs_id_t *node_rev_id,
9571251881Speter                           apr_pool_t *pool)
9572251881Speter{
9573251881Speter  svn_error_t *err;
9574251881Speter  const char *filename = path_node_origin(fs, node_id, pool);
9575251881Speter
9576251881Speter  err = set_node_origins_for_file(fs, filename,
9577251881Speter                                  node_id,
9578251881Speter                                  svn_fs_fs__id_unparse(node_rev_id, pool),
9579251881Speter                                  pool);
9580251881Speter  if (err && APR_STATUS_IS_EACCES(err->apr_err))
9581251881Speter    {
9582251881Speter      /* It's just a cache; stop trying if I can't write. */
9583251881Speter      svn_error_clear(err);
9584251881Speter      err = NULL;
9585251881Speter    }
9586251881Speter  return svn_error_trace(err);
9587251881Speter}
9588251881Speter
9589251881Speter
9590251881Spetersvn_error_t *
9591251881Spetersvn_fs_fs__list_transactions(apr_array_header_t **names_p,
9592251881Speter                             svn_fs_t *fs,
9593251881Speter                             apr_pool_t *pool)
9594251881Speter{
9595251881Speter  const char *txn_dir;
9596251881Speter  apr_hash_t *dirents;
9597251881Speter  apr_hash_index_t *hi;
9598251881Speter  apr_array_header_t *names;
9599251881Speter  apr_size_t ext_len = strlen(PATH_EXT_TXN);
9600251881Speter
9601251881Speter  names = apr_array_make(pool, 1, sizeof(const char *));
9602251881Speter
9603251881Speter  /* Get the transactions directory. */
9604251881Speter  txn_dir = svn_dirent_join(fs->path, PATH_TXNS_DIR, pool);
9605251881Speter
9606251881Speter  /* Now find a listing of this directory. */
9607251881Speter  SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
9608251881Speter
9609251881Speter  /* Loop through all the entries and return anything that ends with '.txn'. */
9610251881Speter  for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
9611251881Speter    {
9612251881Speter      const char *name = svn__apr_hash_index_key(hi);
9613251881Speter      apr_ssize_t klen = svn__apr_hash_index_klen(hi);
9614251881Speter      const char *id;
9615251881Speter
9616251881Speter      /* The name must end with ".txn" to be considered a transaction. */
9617251881Speter      if ((apr_size_t) klen <= ext_len
9618251881Speter          || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
9619251881Speter        continue;
9620251881Speter
9621251881Speter      /* Truncate the ".txn" extension and store the ID. */
9622251881Speter      id = apr_pstrndup(pool, name, strlen(name) - ext_len);
9623251881Speter      APR_ARRAY_PUSH(names, const char *) = id;
9624251881Speter    }
9625251881Speter
9626251881Speter  *names_p = names;
9627251881Speter
9628251881Speter  return SVN_NO_ERROR;
9629251881Speter}
9630251881Speter
9631251881Spetersvn_error_t *
9632251881Spetersvn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
9633251881Speter                    svn_fs_t *fs,
9634251881Speter                    const char *name,
9635251881Speter                    apr_pool_t *pool)
9636251881Speter{
9637251881Speter  svn_fs_txn_t *txn;
9638251881Speter  svn_node_kind_t kind;
9639251881Speter  transaction_t *local_txn;
9640251881Speter
9641251881Speter  /* First check to see if the directory exists. */
9642251881Speter  SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool));
9643251881Speter
9644251881Speter  /* Did we find it? */
9645251881Speter  if (kind != svn_node_dir)
9646251881Speter    return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
9647251881Speter                             _("No such transaction '%s'"),
9648251881Speter                             name);
9649251881Speter
9650251881Speter  txn = apr_pcalloc(pool, sizeof(*txn));
9651251881Speter
9652251881Speter  /* Read in the root node of this transaction. */
9653251881Speter  txn->id = apr_pstrdup(pool, name);
9654251881Speter  txn->fs = fs;
9655251881Speter
9656251881Speter  SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool));
9657251881Speter
9658251881Speter  txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
9659251881Speter
9660251881Speter  txn->vtable = &txn_vtable;
9661251881Speter  *txn_p = txn;
9662251881Speter
9663251881Speter  return SVN_NO_ERROR;
9664251881Speter}
9665251881Speter
9666251881Spetersvn_error_t *
9667251881Spetersvn_fs_fs__txn_proplist(apr_hash_t **table_p,
9668251881Speter                        svn_fs_txn_t *txn,
9669251881Speter                        apr_pool_t *pool)
9670251881Speter{
9671251881Speter  apr_hash_t *proplist = apr_hash_make(pool);
9672251881Speter  SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool));
9673251881Speter  *table_p = proplist;
9674251881Speter
9675251881Speter  return SVN_NO_ERROR;
9676251881Speter}
9677251881Speter
9678251881Spetersvn_error_t *
9679251881Spetersvn_fs_fs__delete_node_revision(svn_fs_t *fs,
9680251881Speter                                const svn_fs_id_t *id,
9681251881Speter                                apr_pool_t *pool)
9682251881Speter{
9683251881Speter  node_revision_t *noderev;
9684251881Speter
9685251881Speter  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
9686251881Speter
9687251881Speter  /* Delete any mutable property representation. */
9688251881Speter  if (noderev->prop_rep && noderev->prop_rep->txn_id)
9689251881Speter    SVN_ERR(svn_io_remove_file2(path_txn_node_props(fs, id, pool), FALSE,
9690251881Speter                                pool));
9691251881Speter
9692251881Speter  /* Delete any mutable data representation. */
9693251881Speter  if (noderev->data_rep && noderev->data_rep->txn_id
9694251881Speter      && noderev->kind == svn_node_dir)
9695251881Speter    {
9696251881Speter      fs_fs_data_t *ffd = fs->fsap_data;
9697251881Speter      SVN_ERR(svn_io_remove_file2(path_txn_node_children(fs, id, pool), FALSE,
9698251881Speter                                  pool));
9699251881Speter
9700251881Speter      /* remove the corresponding entry from the cache, if such exists */
9701251881Speter      if (ffd->txn_dir_cache)
9702251881Speter        {
9703251881Speter          const char *key = svn_fs_fs__id_unparse(id, pool)->data;
9704251881Speter          SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
9705251881Speter        }
9706251881Speter    }
9707251881Speter
9708251881Speter  return svn_io_remove_file2(path_txn_node_rev(fs, id, pool), FALSE, pool);
9709251881Speter}
9710251881Speter
9711251881Speter
9712251881Speter
9713251881Speter/*** Revisions ***/
9714251881Speter
9715251881Spetersvn_error_t *
9716251881Spetersvn_fs_fs__revision_prop(svn_string_t **value_p,
9717251881Speter                         svn_fs_t *fs,
9718251881Speter                         svn_revnum_t rev,
9719251881Speter                         const char *propname,
9720251881Speter                         apr_pool_t *pool)
9721251881Speter{
9722251881Speter  apr_hash_t *table;
9723251881Speter
9724251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9725251881Speter  SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool));
9726251881Speter
9727251881Speter  *value_p = svn_hash_gets(table, propname);
9728251881Speter
9729251881Speter  return SVN_NO_ERROR;
9730251881Speter}
9731251881Speter
9732251881Speter
9733251881Speter/* Baton used for change_rev_prop_body below. */
9734251881Speterstruct change_rev_prop_baton {
9735251881Speter  svn_fs_t *fs;
9736251881Speter  svn_revnum_t rev;
9737251881Speter  const char *name;
9738251881Speter  const svn_string_t *const *old_value_p;
9739251881Speter  const svn_string_t *value;
9740251881Speter};
9741251881Speter
9742251881Speter/* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
9743251881Speter   write lock.  This implements the svn_fs_fs__with_write_lock()
9744251881Speter   'body' callback type.  BATON is a 'struct change_rev_prop_baton *'. */
9745251881Speterstatic svn_error_t *
9746251881Speterchange_rev_prop_body(void *baton, apr_pool_t *pool)
9747251881Speter{
9748251881Speter  struct change_rev_prop_baton *cb = baton;
9749251881Speter  apr_hash_t *table;
9750251881Speter
9751251881Speter  SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool));
9752251881Speter
9753251881Speter  if (cb->old_value_p)
9754251881Speter    {
9755251881Speter      const svn_string_t *wanted_value = *cb->old_value_p;
9756251881Speter      const svn_string_t *present_value = svn_hash_gets(table, cb->name);
9757251881Speter      if ((!wanted_value != !present_value)
9758251881Speter          || (wanted_value && present_value
9759251881Speter              && !svn_string_compare(wanted_value, present_value)))
9760251881Speter        {
9761251881Speter          /* What we expected isn't what we found. */
9762251881Speter          return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
9763251881Speter                                   _("revprop '%s' has unexpected value in "
9764251881Speter                                     "filesystem"),
9765251881Speter                                   cb->name);
9766251881Speter        }
9767251881Speter      /* Fall through. */
9768251881Speter    }
9769251881Speter  svn_hash_sets(table, cb->name, cb->value);
9770251881Speter
9771251881Speter  return set_revision_proplist(cb->fs, cb->rev, table, pool);
9772251881Speter}
9773251881Speter
9774251881Spetersvn_error_t *
9775251881Spetersvn_fs_fs__change_rev_prop(svn_fs_t *fs,
9776251881Speter                           svn_revnum_t rev,
9777251881Speter                           const char *name,
9778251881Speter                           const svn_string_t *const *old_value_p,
9779251881Speter                           const svn_string_t *value,
9780251881Speter                           apr_pool_t *pool)
9781251881Speter{
9782251881Speter  struct change_rev_prop_baton cb;
9783251881Speter
9784251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9785251881Speter
9786251881Speter  cb.fs = fs;
9787251881Speter  cb.rev = rev;
9788251881Speter  cb.name = name;
9789251881Speter  cb.old_value_p = old_value_p;
9790251881Speter  cb.value = value;
9791251881Speter
9792251881Speter  return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
9793251881Speter}
9794251881Speter
9795251881Speter
9796251881Speter
9797251881Speter/*** Transactions ***/
9798251881Speter
9799251881Spetersvn_error_t *
9800251881Spetersvn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
9801251881Speter                       const svn_fs_id_t **base_root_id_p,
9802251881Speter                       svn_fs_t *fs,
9803251881Speter                       const char *txn_name,
9804251881Speter                       apr_pool_t *pool)
9805251881Speter{
9806251881Speter  transaction_t *txn;
9807251881Speter  SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
9808251881Speter  *root_id_p = txn->root_id;
9809251881Speter  *base_root_id_p = txn->base_id;
9810251881Speter  return SVN_NO_ERROR;
9811251881Speter}
9812251881Speter
9813251881Speter
9814251881Speter/* Generic transaction operations.  */
9815251881Speter
9816251881Spetersvn_error_t *
9817251881Spetersvn_fs_fs__txn_prop(svn_string_t **value_p,
9818251881Speter                    svn_fs_txn_t *txn,
9819251881Speter                    const char *propname,
9820251881Speter                    apr_pool_t *pool)
9821251881Speter{
9822251881Speter  apr_hash_t *table;
9823251881Speter  svn_fs_t *fs = txn->fs;
9824251881Speter
9825251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9826251881Speter  SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
9827251881Speter
9828251881Speter  *value_p = svn_hash_gets(table, propname);
9829251881Speter
9830251881Speter  return SVN_NO_ERROR;
9831251881Speter}
9832251881Speter
9833251881Spetersvn_error_t *
9834251881Spetersvn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
9835251881Speter                     svn_fs_t *fs,
9836251881Speter                     svn_revnum_t rev,
9837251881Speter                     apr_uint32_t flags,
9838251881Speter                     apr_pool_t *pool)
9839251881Speter{
9840251881Speter  svn_string_t date;
9841251881Speter  svn_prop_t prop;
9842251881Speter  apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
9843251881Speter
9844251881Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
9845251881Speter
9846251881Speter  SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
9847251881Speter
9848251881Speter  /* Put a datestamp on the newly created txn, so we always know
9849251881Speter     exactly how old it is.  (This will help sysadmins identify
9850251881Speter     long-abandoned txns that may need to be manually removed.)  When
9851251881Speter     a txn is promoted to a revision, this property will be
9852251881Speter     automatically overwritten with a revision datestamp. */
9853251881Speter  date.data = svn_time_to_cstring(apr_time_now(), pool);
9854251881Speter  date.len = strlen(date.data);
9855251881Speter
9856251881Speter  prop.name = SVN_PROP_REVISION_DATE;
9857251881Speter  prop.value = &date;
9858251881Speter  APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9859251881Speter
9860251881Speter  /* Set temporary txn props that represent the requested 'flags'
9861251881Speter     behaviors. */
9862251881Speter  if (flags & SVN_FS_TXN_CHECK_OOD)
9863251881Speter    {
9864251881Speter      prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
9865251881Speter      prop.value = svn_string_create("true", pool);
9866251881Speter      APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9867251881Speter    }
9868251881Speter
9869251881Speter  if (flags & SVN_FS_TXN_CHECK_LOCKS)
9870251881Speter    {
9871251881Speter      prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
9872251881Speter      prop.value = svn_string_create("true", pool);
9873251881Speter      APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9874251881Speter    }
9875251881Speter
9876251881Speter  return svn_fs_fs__change_txn_props(*txn_p, props, pool);
9877251881Speter}
9878251881Speter
9879251881Speter
9880251881Speter/****** Packing FSFS shards *********/
9881251881Speter
9882251881Speter/* Write a file FILENAME in directory FS_PATH, containing a single line
9883251881Speter * with the number REVNUM in ASCII decimal.  Move the file into place
9884251881Speter * atomically, overwriting any existing file.
9885251881Speter *
9886251881Speter * Similar to write_current(). */
9887251881Speterstatic svn_error_t *
9888251881Speterwrite_revnum_file(const char *fs_path,
9889251881Speter                  const char *filename,
9890251881Speter                  svn_revnum_t revnum,
9891251881Speter                  apr_pool_t *scratch_pool)
9892251881Speter{
9893251881Speter  const char *final_path, *tmp_path;
9894251881Speter  svn_stream_t *tmp_stream;
9895251881Speter
9896251881Speter  final_path = svn_dirent_join(fs_path, filename, scratch_pool);
9897251881Speter  SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path,
9898251881Speter                                   svn_io_file_del_none,
9899251881Speter                                   scratch_pool, scratch_pool));
9900251881Speter  SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum));
9901251881Speter  SVN_ERR(svn_stream_close(tmp_stream));
9902251881Speter  SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool));
9903251881Speter  return SVN_NO_ERROR;
9904251881Speter}
9905251881Speter
9906251881Speter/* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions
9907251881Speter * from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations.
9908251881Speter * CANCEL_FUNC and CANCEL_BATON are what you think they are.
9909251881Speter *
9910251881Speter * If for some reason we detect a partial packing already performed, we
9911251881Speter * remove the pack file and start again.
9912251881Speter */
9913251881Speterstatic svn_error_t *
9914251881Speterpack_rev_shard(const char *pack_file_dir,
9915251881Speter               const char *shard_path,
9916251881Speter               apr_int64_t shard,
9917251881Speter               int max_files_per_dir,
9918251881Speter               svn_cancel_func_t cancel_func,
9919251881Speter               void *cancel_baton,
9920251881Speter               apr_pool_t *pool)
9921251881Speter{
9922251881Speter  const char *pack_file_path, *manifest_file_path;
9923251881Speter  svn_stream_t *pack_stream, *manifest_stream;
9924251881Speter  svn_revnum_t start_rev, end_rev, rev;
9925251881Speter  apr_off_t next_offset;
9926251881Speter  apr_pool_t *iterpool;
9927251881Speter
9928251881Speter  /* Some useful paths. */
9929251881Speter  pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
9930251881Speter  manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool);
9931251881Speter
9932251881Speter  /* Remove any existing pack file for this shard, since it is incomplete. */
9933251881Speter  SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
9934251881Speter                             pool));
9935251881Speter
9936251881Speter  /* Create the new directory and pack and manifest files. */
9937251881Speter  SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool));
9938251881Speter  SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool,
9939251881Speter                                    pool));
9940251881Speter  SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
9941251881Speter                                   pool, pool));
9942251881Speter
9943251881Speter  start_rev = (svn_revnum_t) (shard * max_files_per_dir);
9944251881Speter  end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
9945251881Speter  next_offset = 0;
9946251881Speter  iterpool = svn_pool_create(pool);
9947251881Speter
9948251881Speter  /* Iterate over the revisions in this shard, squashing them together. */
9949251881Speter  for (rev = start_rev; rev <= end_rev; rev++)
9950251881Speter    {
9951251881Speter      svn_stream_t *rev_stream;
9952251881Speter      apr_finfo_t finfo;
9953251881Speter      const char *path;
9954251881Speter
9955251881Speter      svn_pool_clear(iterpool);
9956251881Speter
9957251881Speter      /* Get the size of the file. */
9958251881Speter      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
9959251881Speter                             iterpool);
9960251881Speter      SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
9961251881Speter
9962251881Speter      /* Update the manifest. */
9963251881Speter      SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT
9964251881Speter                                "\n", next_offset));
9965251881Speter      next_offset += finfo.size;
9966251881Speter
9967251881Speter      /* Copy all the bits from the rev file to the end of the pack file. */
9968251881Speter      SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool));
9969251881Speter      SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream,
9970251881Speter                                                             iterpool),
9971251881Speter                          cancel_func, cancel_baton, iterpool));
9972251881Speter    }
9973251881Speter
9974251881Speter  SVN_ERR(svn_stream_close(manifest_stream));
9975251881Speter  SVN_ERR(svn_stream_close(pack_stream));
9976251881Speter  SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
9977251881Speter  SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool));
9978251881Speter  SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
9979251881Speter
9980251881Speter  svn_pool_destroy(iterpool);
9981251881Speter
9982251881Speter  return SVN_NO_ERROR;
9983251881Speter}
9984251881Speter
9985251881Speter/* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH
9986251881Speter * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR.
9987251881Speter *
9988251881Speter * The file sizes have already been determined and written to SIZES.
9989251881Speter * Please note that this function will be executed while the filesystem
9990251881Speter * has been locked and that revprops files will therefore not be modified
9991251881Speter * while the pack is in progress.
9992251881Speter *
9993251881Speter * COMPRESSION_LEVEL defines how well the resulting pack file shall be
9994251881Speter * compressed or whether is shall be compressed at all.  TOTAL_SIZE is
9995251881Speter * a hint on which initial buffer size we should use to hold the pack file
9996251881Speter * content.
9997251881Speter *
9998251881Speter * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations
9999251881Speter * are done in SCRATCH_POOL.
10000251881Speter */
10001251881Speterstatic svn_error_t *
10002251881Spetercopy_revprops(const char *pack_file_dir,
10003251881Speter              const char *pack_filename,
10004251881Speter              const char *shard_path,
10005251881Speter              svn_revnum_t start_rev,
10006251881Speter              svn_revnum_t end_rev,
10007251881Speter              apr_array_header_t *sizes,
10008251881Speter              apr_size_t total_size,
10009251881Speter              int compression_level,
10010251881Speter              svn_cancel_func_t cancel_func,
10011251881Speter              void *cancel_baton,
10012251881Speter              apr_pool_t *scratch_pool)
10013251881Speter{
10014251881Speter  svn_stream_t *pack_stream;
10015251881Speter  apr_file_t *pack_file;
10016251881Speter  svn_revnum_t rev;
10017251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10018251881Speter  svn_stream_t *stream;
10019251881Speter
10020251881Speter  /* create empty data buffer and a write stream on top of it */
10021251881Speter  svn_stringbuf_t *uncompressed
10022251881Speter    = svn_stringbuf_create_ensure(total_size, scratch_pool);
10023251881Speter  svn_stringbuf_t *compressed
10024251881Speter    = svn_stringbuf_create_empty(scratch_pool);
10025251881Speter  pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
10026251881Speter
10027251881Speter  /* write the pack file header */
10028251881Speter  SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
10029251881Speter                                    sizes->nelts, iterpool));
10030251881Speter
10031251881Speter  /* Some useful paths. */
10032251881Speter  SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
10033251881Speter                                                       pack_filename,
10034251881Speter                                                       scratch_pool),
10035251881Speter                           APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
10036251881Speter                           scratch_pool));
10037251881Speter
10038251881Speter  /* Iterate over the revisions in this shard, squashing them together. */
10039251881Speter  for (rev = start_rev; rev <= end_rev; rev++)
10040251881Speter    {
10041251881Speter      const char *path;
10042251881Speter
10043251881Speter      svn_pool_clear(iterpool);
10044251881Speter
10045251881Speter      /* Construct the file name. */
10046251881Speter      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10047251881Speter                             iterpool);
10048251881Speter
10049251881Speter      /* Copy all the bits from the non-packed revprop file to the end of
10050251881Speter       * the pack file. */
10051251881Speter      SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
10052251881Speter      SVN_ERR(svn_stream_copy3(stream, pack_stream,
10053251881Speter                               cancel_func, cancel_baton, iterpool));
10054251881Speter    }
10055251881Speter
10056251881Speter  /* flush stream buffers to content buffer */
10057251881Speter  SVN_ERR(svn_stream_close(pack_stream));
10058251881Speter
10059251881Speter  /* compress the content (or just store it for COMPRESSION_LEVEL 0) */
10060251881Speter  SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
10061251881Speter                        compressed, compression_level));
10062251881Speter
10063251881Speter  /* write the pack file content to disk */
10064251881Speter  stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool);
10065251881Speter  SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len));
10066251881Speter  SVN_ERR(svn_stream_close(stream));
10067251881Speter
10068251881Speter  svn_pool_destroy(iterpool);
10069251881Speter
10070251881Speter  return SVN_NO_ERROR;
10071251881Speter}
10072251881Speter
10073251881Speter/* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR
10074251881Speter * revprop files in it, create a packed shared at PACK_FILE_DIR.
10075251881Speter *
10076251881Speter * COMPRESSION_LEVEL defines how well the resulting pack file shall be
10077251881Speter * compressed or whether is shall be compressed at all.  Individual pack
10078251881Speter * file containing more than one revision will be limited to a size of
10079251881Speter * MAX_PACK_SIZE bytes before compression.
10080251881Speter *
10081251881Speter * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10082251881Speter * allocations are done in SCRATCH_POOL.
10083251881Speter */
10084251881Speterstatic svn_error_t *
10085251881Speterpack_revprops_shard(const char *pack_file_dir,
10086251881Speter                    const char *shard_path,
10087251881Speter                    apr_int64_t shard,
10088251881Speter                    int max_files_per_dir,
10089251881Speter                    apr_off_t max_pack_size,
10090251881Speter                    int compression_level,
10091251881Speter                    svn_cancel_func_t cancel_func,
10092251881Speter                    void *cancel_baton,
10093251881Speter                    apr_pool_t *scratch_pool)
10094251881Speter{
10095251881Speter  const char *manifest_file_path, *pack_filename = NULL;
10096251881Speter  svn_stream_t *manifest_stream;
10097251881Speter  svn_revnum_t start_rev, end_rev, rev;
10098251881Speter  apr_off_t total_size;
10099251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10100251881Speter  apr_array_header_t *sizes;
10101251881Speter
10102251881Speter  /* Some useful paths. */
10103251881Speter  manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
10104251881Speter                                       scratch_pool);
10105251881Speter
10106251881Speter  /* Remove any existing pack file for this shard, since it is incomplete. */
10107251881Speter  SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
10108251881Speter                             scratch_pool));
10109251881Speter
10110251881Speter  /* Create the new directory and manifest file stream. */
10111251881Speter  SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
10112251881Speter  SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
10113251881Speter                                   scratch_pool, scratch_pool));
10114251881Speter
10115251881Speter  /* revisions to handle. Special case: revision 0 */
10116251881Speter  start_rev = (svn_revnum_t) (shard * max_files_per_dir);
10117251881Speter  end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
10118251881Speter  if (start_rev == 0)
10119251881Speter    ++start_rev;
10120251881Speter
10121251881Speter  /* initialize the revprop size info */
10122251881Speter  sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
10123251881Speter  total_size = 2 * SVN_INT64_BUFFER_SIZE;
10124251881Speter
10125251881Speter  /* Iterate over the revisions in this shard, determine their size and
10126251881Speter   * squashing them together into pack files. */
10127251881Speter  for (rev = start_rev; rev <= end_rev; rev++)
10128251881Speter    {
10129251881Speter      apr_finfo_t finfo;
10130251881Speter      const char *path;
10131251881Speter
10132251881Speter      svn_pool_clear(iterpool);
10133251881Speter
10134251881Speter      /* Get the size of the file. */
10135251881Speter      path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10136251881Speter                             iterpool);
10137251881Speter      SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
10138251881Speter
10139251881Speter      /* if we already have started a pack file and this revprop cannot be
10140251881Speter       * appended to it, write the previous pack file. */
10141251881Speter      if (sizes->nelts != 0 &&
10142251881Speter          total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
10143251881Speter        {
10144251881Speter          SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10145251881Speter                                start_rev, rev-1, sizes, (apr_size_t)total_size,
10146251881Speter                                compression_level, cancel_func, cancel_baton,
10147251881Speter                                iterpool));
10148251881Speter
10149251881Speter          /* next pack file starts empty again */
10150251881Speter          apr_array_clear(sizes);
10151251881Speter          total_size = 2 * SVN_INT64_BUFFER_SIZE;
10152251881Speter          start_rev = rev;
10153251881Speter        }
10154251881Speter
10155251881Speter      /* Update the manifest. Allocate a file name for the current pack
10156251881Speter       * file if it is a new one */
10157251881Speter      if (sizes->nelts == 0)
10158251881Speter        pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
10159251881Speter
10160251881Speter      SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
10161251881Speter                                pack_filename));
10162251881Speter
10163251881Speter      /* add to list of files to put into the current pack file */
10164251881Speter      APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
10165251881Speter      total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
10166251881Speter    }
10167251881Speter
10168251881Speter  /* write the last pack file */
10169251881Speter  if (sizes->nelts != 0)
10170251881Speter    SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10171251881Speter                          start_rev, rev-1, sizes, (apr_size_t)total_size,
10172251881Speter                          compression_level, cancel_func, cancel_baton,
10173251881Speter                          iterpool));
10174251881Speter
10175251881Speter  /* flush the manifest file and update permissions */
10176251881Speter  SVN_ERR(svn_stream_close(manifest_stream));
10177251881Speter  SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
10178251881Speter
10179251881Speter  svn_pool_destroy(iterpool);
10180251881Speter
10181251881Speter  return SVN_NO_ERROR;
10182251881Speter}
10183251881Speter
10184251881Speter/* Delete the non-packed revprop SHARD at SHARD_PATH with exactly
10185251881Speter * MAX_FILES_PER_DIR revprop files in it.  If this is shard 0, keep the
10186251881Speter * revprop file for revision 0.
10187251881Speter *
10188251881Speter * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10189251881Speter * allocations are done in SCRATCH_POOL.
10190251881Speter */
10191251881Speterstatic svn_error_t *
10192251881Speterdelete_revprops_shard(const char *shard_path,
10193251881Speter                      apr_int64_t shard,
10194251881Speter                      int max_files_per_dir,
10195251881Speter                      svn_cancel_func_t cancel_func,
10196251881Speter                      void *cancel_baton,
10197251881Speter                      apr_pool_t *scratch_pool)
10198251881Speter{
10199251881Speter  if (shard == 0)
10200251881Speter    {
10201251881Speter      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10202251881Speter      int i;
10203251881Speter
10204251881Speter      /* delete all files except the one for revision 0 */
10205251881Speter      for (i = 1; i < max_files_per_dir; ++i)
10206251881Speter        {
10207251881Speter          const char *path = svn_dirent_join(shard_path,
10208251881Speter                                       apr_psprintf(iterpool, "%d", i),
10209251881Speter                                       iterpool);
10210251881Speter          if (cancel_func)
10211251881Speter            SVN_ERR((*cancel_func)(cancel_baton));
10212251881Speter
10213251881Speter          SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
10214251881Speter          svn_pool_clear(iterpool);
10215251881Speter        }
10216251881Speter
10217251881Speter      svn_pool_destroy(iterpool);
10218251881Speter    }
10219251881Speter  else
10220251881Speter    SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
10221251881Speter                               cancel_func, cancel_baton, scratch_pool));
10222251881Speter
10223251881Speter  return SVN_NO_ERROR;
10224251881Speter}
10225251881Speter
10226251881Speter/* In the file system at FS_PATH, pack the SHARD in REVS_DIR and
10227251881Speter * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL
10228251881Speter * for allocations.  REVPROPS_DIR will be NULL if revprop packing is not
10229251881Speter * supported.  COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that
10230251881Speter * case.
10231251881Speter *
10232251881Speter * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly
10233251881Speter * NOTIFY_FUNC and NOTIFY_BATON.
10234251881Speter *
10235251881Speter * If for some reason we detect a partial packing already performed, we
10236251881Speter * remove the pack file and start again.
10237251881Speter */
10238251881Speterstatic svn_error_t *
10239251881Speterpack_shard(const char *revs_dir,
10240251881Speter           const char *revsprops_dir,
10241251881Speter           const char *fs_path,
10242251881Speter           apr_int64_t shard,
10243251881Speter           int max_files_per_dir,
10244251881Speter           apr_off_t max_pack_size,
10245251881Speter           int compression_level,
10246251881Speter           svn_fs_pack_notify_t notify_func,
10247251881Speter           void *notify_baton,
10248251881Speter           svn_cancel_func_t cancel_func,
10249251881Speter           void *cancel_baton,
10250251881Speter           apr_pool_t *pool)
10251251881Speter{
10252251881Speter  const char *rev_shard_path, *rev_pack_file_dir;
10253251881Speter  const char *revprops_shard_path, *revprops_pack_file_dir;
10254251881Speter
10255251881Speter  /* Notify caller we're starting to pack this shard. */
10256251881Speter  if (notify_func)
10257251881Speter    SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start,
10258251881Speter                        pool));
10259251881Speter
10260251881Speter  /* Some useful paths. */
10261251881Speter  rev_pack_file_dir = svn_dirent_join(revs_dir,
10262251881Speter                  apr_psprintf(pool,
10263251881Speter                               "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10264251881Speter                               shard),
10265251881Speter                  pool);
10266251881Speter  rev_shard_path = svn_dirent_join(revs_dir,
10267251881Speter                           apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10268251881Speter                           pool);
10269251881Speter
10270251881Speter  /* pack the revision content */
10271251881Speter  SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path,
10272251881Speter                         shard, max_files_per_dir,
10273251881Speter                         cancel_func, cancel_baton, pool));
10274251881Speter
10275251881Speter  /* if enabled, pack the revprops in an equivalent way */
10276251881Speter  if (revsprops_dir)
10277251881Speter    {
10278251881Speter      revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
10279251881Speter                   apr_psprintf(pool,
10280251881Speter                                "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10281251881Speter                                shard),
10282251881Speter                   pool);
10283251881Speter      revprops_shard_path = svn_dirent_join(revsprops_dir,
10284251881Speter                           apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10285251881Speter                           pool);
10286251881Speter
10287251881Speter      SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
10288251881Speter                                  shard, max_files_per_dir,
10289251881Speter                                  (int)(0.9 * max_pack_size),
10290251881Speter                                  compression_level,
10291251881Speter                                  cancel_func, cancel_baton, pool));
10292251881Speter    }
10293251881Speter
10294251881Speter  /* Update the min-unpacked-rev file to reflect our newly packed shard.
10295251881Speter   * (This doesn't update ffd->min_unpacked_rev.  That will be updated by
10296251881Speter   * update_min_unpacked_rev() when necessary.) */
10297251881Speter  SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV,
10298251881Speter                            (svn_revnum_t)((shard + 1) * max_files_per_dir),
10299251881Speter                            pool));
10300251881Speter
10301251881Speter  /* Finally, remove the existing shard directories. */
10302251881Speter  SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE,
10303251881Speter                             cancel_func, cancel_baton, pool));
10304251881Speter  if (revsprops_dir)
10305251881Speter    SVN_ERR(delete_revprops_shard(revprops_shard_path,
10306251881Speter                                  shard, max_files_per_dir,
10307251881Speter                                  cancel_func, cancel_baton, pool));
10308251881Speter
10309251881Speter  /* Notify caller we're starting to pack this shard. */
10310251881Speter  if (notify_func)
10311251881Speter    SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end,
10312251881Speter                        pool));
10313251881Speter
10314251881Speter  return SVN_NO_ERROR;
10315251881Speter}
10316251881Speter
10317251881Speterstruct pack_baton
10318251881Speter{
10319251881Speter  svn_fs_t *fs;
10320251881Speter  svn_fs_pack_notify_t notify_func;
10321251881Speter  void *notify_baton;
10322251881Speter  svn_cancel_func_t cancel_func;
10323251881Speter  void *cancel_baton;
10324251881Speter};
10325251881Speter
10326251881Speter
10327251881Speter/* The work-horse for svn_fs_fs__pack, called with the FS write lock.
10328251881Speter   This implements the svn_fs_fs__with_write_lock() 'body' callback
10329251881Speter   type.  BATON is a 'struct pack_baton *'.
10330251881Speter
10331251881Speter   WARNING: if you add a call to this function, please note:
10332251881Speter     The code currently assumes that any piece of code running with
10333251881Speter     the write-lock set can rely on the ffd->min_unpacked_rev and
10334251881Speter     ffd->min_unpacked_revprop caches to be up-to-date (and, by
10335251881Speter     extension, on not having to use a retry when calling
10336251881Speter     svn_fs_fs__path_rev_absolute() and friends).  If you add a call
10337251881Speter     to this function, consider whether you have to call
10338251881Speter     update_min_unpacked_rev().
10339251881Speter     See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith
10340251881Speter */
10341251881Speterstatic svn_error_t *
10342251881Speterpack_body(void *baton,
10343251881Speter          apr_pool_t *pool)
10344251881Speter{
10345251881Speter  struct pack_baton *pb = baton;
10346251881Speter  fs_fs_data_t ffd = {0};
10347251881Speter  apr_int64_t completed_shards;
10348251881Speter  apr_int64_t i;
10349251881Speter  svn_revnum_t youngest;
10350251881Speter  apr_pool_t *iterpool;
10351251881Speter  const char *rev_data_path;
10352251881Speter  const char *revprops_data_path = NULL;
10353251881Speter
10354251881Speter  /* read repository settings */
10355251881Speter  SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir,
10356251881Speter                      path_format(pb->fs, pool), pool));
10357251881Speter  SVN_ERR(check_format(ffd.format));
10358251881Speter  SVN_ERR(read_config(&ffd, pb->fs->path, pool));
10359251881Speter
10360251881Speter  /* If the repository isn't a new enough format, we don't support packing.
10361251881Speter     Return a friendly error to that effect. */
10362251881Speter  if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT)
10363251881Speter    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
10364251881Speter      _("FSFS format (%d) too old to pack; please upgrade the filesystem."),
10365251881Speter      ffd.format);
10366251881Speter
10367251881Speter  /* If we aren't using sharding, we can't do any packing, so quit. */
10368251881Speter  if (!ffd.max_files_per_dir)
10369251881Speter    return SVN_NO_ERROR;
10370251881Speter
10371251881Speter  SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev,
10372251881Speter                                path_min_unpacked_rev(pb->fs, pool),
10373251881Speter                                pool));
10374251881Speter
10375251881Speter  SVN_ERR(get_youngest(&youngest, pb->fs->path, pool));
10376251881Speter  completed_shards = (youngest + 1) / ffd.max_files_per_dir;
10377251881Speter
10378251881Speter  /* See if we've already completed all possible shards thus far. */
10379251881Speter  if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir))
10380251881Speter    return SVN_NO_ERROR;
10381251881Speter
10382251881Speter  rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
10383251881Speter  if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
10384251881Speter    revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
10385251881Speter                                         pool);
10386251881Speter
10387251881Speter  iterpool = svn_pool_create(pool);
10388251881Speter  for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir;
10389251881Speter       i < completed_shards;
10390251881Speter       i++)
10391251881Speter    {
10392251881Speter      svn_pool_clear(iterpool);
10393251881Speter
10394251881Speter      if (pb->cancel_func)
10395251881Speter        SVN_ERR(pb->cancel_func(pb->cancel_baton));
10396251881Speter
10397251881Speter      SVN_ERR(pack_shard(rev_data_path, revprops_data_path,
10398251881Speter                         pb->fs->path, i, ffd.max_files_per_dir,
10399251881Speter                         ffd.revprop_pack_size,
10400251881Speter                         ffd.compress_packed_revprops
10401251881Speter                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
10402251881Speter                           : SVN_DELTA_COMPRESSION_LEVEL_NONE,
10403251881Speter                         pb->notify_func, pb->notify_baton,
10404251881Speter                         pb->cancel_func, pb->cancel_baton, iterpool));
10405251881Speter    }
10406251881Speter
10407251881Speter  svn_pool_destroy(iterpool);
10408251881Speter  return SVN_NO_ERROR;
10409251881Speter}
10410251881Speter
10411251881Spetersvn_error_t *
10412251881Spetersvn_fs_fs__pack(svn_fs_t *fs,
10413251881Speter                svn_fs_pack_notify_t notify_func,
10414251881Speter                void *notify_baton,
10415251881Speter                svn_cancel_func_t cancel_func,
10416251881Speter                void *cancel_baton,
10417251881Speter                apr_pool_t *pool)
10418251881Speter{
10419251881Speter  struct pack_baton pb = { 0 };
10420251881Speter  pb.fs = fs;
10421251881Speter  pb.notify_func = notify_func;
10422251881Speter  pb.notify_baton = notify_baton;
10423251881Speter  pb.cancel_func = cancel_func;
10424251881Speter  pb.cancel_baton = cancel_baton;
10425251881Speter  return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool);
10426251881Speter}
10427251881Speter
10428251881Speter
10429251881Speter/** Verifying. **/
10430251881Speter
10431251881Speter/* Baton type expected by verify_walker().  The purpose is to reuse open
10432251881Speter * rev / pack file handles between calls.  Its contents need to be cleaned
10433251881Speter * periodically to limit resource usage.
10434251881Speter */
10435251881Spetertypedef struct verify_walker_baton_t
10436251881Speter{
10437251881Speter  /* number of calls to verify_walker() since the last clean */
10438251881Speter  int iteration_count;
10439251881Speter
10440251881Speter  /* number of files opened since the last clean */
10441251881Speter  int file_count;
10442251881Speter
10443251881Speter  /* progress notification callback to invoke periodically (may be NULL) */
10444251881Speter  svn_fs_progress_notify_func_t notify_func;
10445251881Speter
10446251881Speter  /* baton to use with NOTIFY_FUNC */
10447251881Speter  void *notify_baton;
10448251881Speter
10449251881Speter  /* remember the last revision for which we called notify_func */
10450251881Speter  svn_revnum_t last_notified_revision;
10451251881Speter
10452251881Speter  /* current file handle (or NULL) */
10453251881Speter  apr_file_t *file_hint;
10454251881Speter
10455251881Speter  /* corresponding revision (or SVN_INVALID_REVNUM) */
10456251881Speter  svn_revnum_t rev_hint;
10457251881Speter
10458251881Speter  /* pool to use for the file handles etc. */
10459251881Speter  apr_pool_t *pool;
10460251881Speter} verify_walker_baton_t;
10461251881Speter
10462251881Speter/* Used by svn_fs_fs__verify().
10463251881Speter   Implements svn_fs_fs__walk_rep_reference().walker.  */
10464251881Speterstatic svn_error_t *
10465251881Speterverify_walker(representation_t *rep,
10466251881Speter              void *baton,
10467251881Speter              svn_fs_t *fs,
10468251881Speter              apr_pool_t *scratch_pool)
10469251881Speter{
10470251881Speter  struct rep_state *rs;
10471251881Speter  struct rep_args *rep_args;
10472251881Speter
10473251881Speter  if (baton)
10474251881Speter    {
10475251881Speter      verify_walker_baton_t *walker_baton = baton;
10476251881Speter      apr_file_t * previous_file;
10477251881Speter
10478251881Speter      /* notify and free resources periodically */
10479251881Speter      if (   walker_baton->iteration_count > 1000
10480251881Speter          || walker_baton->file_count > 16)
10481251881Speter        {
10482251881Speter          if (   walker_baton->notify_func
10483251881Speter              && rep->revision != walker_baton->last_notified_revision)
10484251881Speter            {
10485251881Speter              walker_baton->notify_func(rep->revision,
10486251881Speter                                        walker_baton->notify_baton,
10487251881Speter                                        scratch_pool);
10488251881Speter              walker_baton->last_notified_revision = rep->revision;
10489251881Speter            }
10490251881Speter
10491251881Speter          svn_pool_clear(walker_baton->pool);
10492251881Speter
10493251881Speter          walker_baton->iteration_count = 0;
10494251881Speter          walker_baton->file_count = 0;
10495251881Speter          walker_baton->file_hint = NULL;
10496251881Speter          walker_baton->rev_hint = SVN_INVALID_REVNUM;
10497251881Speter        }
10498251881Speter
10499251881Speter      /* access the repo data */
10500251881Speter      previous_file = walker_baton->file_hint;
10501251881Speter      SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint,
10502251881Speter                               &walker_baton->rev_hint, rep, fs,
10503251881Speter                               walker_baton->pool));
10504251881Speter
10505251881Speter      /* update resource usage counters */
10506251881Speter      walker_baton->iteration_count++;
10507251881Speter      if (previous_file != walker_baton->file_hint)
10508251881Speter        walker_baton->file_count++;
10509251881Speter    }
10510251881Speter  else
10511251881Speter    {
10512251881Speter      /* ### Should this be using read_rep_line() directly? */
10513251881Speter      SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs,
10514251881Speter                               scratch_pool));
10515251881Speter    }
10516251881Speter
10517251881Speter  return SVN_NO_ERROR;
10518251881Speter}
10519251881Speter
10520251881Spetersvn_error_t *
10521251881Spetersvn_fs_fs__verify(svn_fs_t *fs,
10522251881Speter                  svn_revnum_t start,
10523251881Speter                  svn_revnum_t end,
10524251881Speter                  svn_fs_progress_notify_func_t notify_func,
10525251881Speter                  void *notify_baton,
10526251881Speter                  svn_cancel_func_t cancel_func,
10527251881Speter                  void *cancel_baton,
10528251881Speter                  apr_pool_t *pool)
10529251881Speter{
10530251881Speter  fs_fs_data_t *ffd = fs->fsap_data;
10531251881Speter  svn_boolean_t exists;
10532251881Speter  svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
10533251881Speter
10534251881Speter  if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
10535251881Speter    return SVN_NO_ERROR;
10536251881Speter
10537251881Speter  /* Input validation. */
10538251881Speter  if (! SVN_IS_VALID_REVNUM(start))
10539251881Speter    start = 0;
10540251881Speter  if (! SVN_IS_VALID_REVNUM(end))
10541251881Speter    end = youngest;
10542251881Speter  SVN_ERR(ensure_revision_exists(fs, start, pool));
10543251881Speter  SVN_ERR(ensure_revision_exists(fs, end, pool));
10544251881Speter
10545251881Speter  /* rep-cache verification. */
10546251881Speter  SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
10547251881Speter  if (exists)
10548251881Speter    {
10549251881Speter      /* provide a baton to allow the reuse of open file handles between
10550251881Speter         iterations (saves 2/3 of OS level file operations). */
10551251881Speter      verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
10552251881Speter      baton->rev_hint = SVN_INVALID_REVNUM;
10553251881Speter      baton->pool = svn_pool_create(pool);
10554251881Speter      baton->last_notified_revision = SVN_INVALID_REVNUM;
10555251881Speter      baton->notify_func = notify_func;
10556251881Speter      baton->notify_baton = notify_baton;
10557251881Speter
10558251881Speter      /* tell the user that we are now ready to do *something* */
10559251881Speter      if (notify_func)
10560251881Speter        notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool);
10561251881Speter
10562251881Speter      /* Do not attempt to walk the rep-cache database if its file does
10563251881Speter         not exist,  since doing so would create it --- which may confuse
10564251881Speter         the administrator.   Don't take any lock. */
10565251881Speter      SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end,
10566251881Speter                                            verify_walker, baton,
10567251881Speter                                            cancel_func, cancel_baton,
10568251881Speter                                            pool));
10569251881Speter
10570251881Speter      /* walker resource cleanup */
10571251881Speter      svn_pool_destroy(baton->pool);
10572251881Speter    }
10573251881Speter
10574251881Speter  return SVN_NO_ERROR;
10575251881Speter}
10576251881Speter
10577251881Speter
10578251881Speter/** Hotcopy. **/
10579251881Speter
10580251881Speter/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
10581251881Speter * the destination and do not differ in terms of kind, size, and mtime. */
10582251881Speterstatic svn_error_t *
10583251881Speterhotcopy_io_dir_file_copy(const char *src_path,
10584251881Speter                         const char *dst_path,
10585251881Speter                         const char *file,
10586251881Speter                         apr_pool_t *scratch_pool)
10587251881Speter{
10588251881Speter  const svn_io_dirent2_t *src_dirent;
10589251881Speter  const svn_io_dirent2_t *dst_dirent;
10590251881Speter  const char *src_target;
10591251881Speter  const char *dst_target;
10592251881Speter
10593251881Speter  /* Does the destination already exist? If not, we must copy it. */
10594251881Speter  dst_target = svn_dirent_join(dst_path, file, scratch_pool);
10595251881Speter  SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
10596251881Speter                              scratch_pool, scratch_pool));
10597251881Speter  if (dst_dirent->kind != svn_node_none)
10598251881Speter    {
10599251881Speter      /* If the destination's stat information indicates that the file
10600251881Speter       * is equal to the source, don't bother copying the file again. */
10601251881Speter      src_target = svn_dirent_join(src_path, file, scratch_pool);
10602251881Speter      SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
10603251881Speter                                  scratch_pool, scratch_pool));
10604251881Speter      if (src_dirent->kind == dst_dirent->kind &&
10605251881Speter          src_dirent->special == dst_dirent->special &&
10606251881Speter          src_dirent->filesize == dst_dirent->filesize &&
10607251881Speter          src_dirent->mtime <= dst_dirent->mtime)
10608251881Speter        return SVN_NO_ERROR;
10609251881Speter    }
10610251881Speter
10611251881Speter  return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
10612251881Speter                                              scratch_pool));
10613251881Speter}
10614251881Speter
10615251881Speter/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
10616251881Speter * NAME is in the internal encoding used by APR; PARENT is in
10617251881Speter * UTF-8 and in internal (not local) style.
10618251881Speter *
10619251881Speter * Use PARENT only for generating an error string if the conversion
10620251881Speter * fails because NAME could not be represented in UTF-8.  In that
10621251881Speter * case, return a two-level error in which the outer error's message
10622251881Speter * mentions PARENT, but the inner error's message does not mention
10623251881Speter * NAME (except possibly in hex) since NAME may not be printable.
10624251881Speter * Such a compound error at least allows the user to go looking in the
10625251881Speter * right directory for the problem.
10626251881Speter *
10627251881Speter * If there is any other error, just return that error directly.
10628251881Speter *
10629251881Speter * If there is any error, the effect on *NAME_P is undefined.
10630251881Speter *
10631251881Speter * *NAME_P and NAME may refer to the same storage.
10632251881Speter */
10633251881Speterstatic svn_error_t *
10634251881Speterentry_name_to_utf8(const char **name_p,
10635251881Speter                   const char *name,
10636251881Speter                   const char *parent,
10637251881Speter                   apr_pool_t *pool)
10638251881Speter{
10639251881Speter  svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
10640251881Speter  if (err && err->apr_err == APR_EINVAL)
10641251881Speter    {
10642251881Speter      return svn_error_createf(err->apr_err, err,
10643251881Speter                               _("Error converting entry "
10644251881Speter                                 "in directory '%s' to UTF-8"),
10645251881Speter                               svn_dirent_local_style(parent, pool));
10646251881Speter    }
10647251881Speter  return err;
10648251881Speter}
10649251881Speter
10650251881Speter/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
10651251881Speter * exist in the destination and do not differ from the source in terms of
10652251881Speter * kind, size, and mtime. */
10653251881Speterstatic svn_error_t *
10654251881Speterhotcopy_io_copy_dir_recursively(const char *src,
10655251881Speter                                const char *dst_parent,
10656251881Speter                                const char *dst_basename,
10657251881Speter                                svn_boolean_t copy_perms,
10658251881Speter                                svn_cancel_func_t cancel_func,
10659251881Speter                                void *cancel_baton,
10660251881Speter                                apr_pool_t *pool)
10661251881Speter{
10662251881Speter  svn_node_kind_t kind;
10663251881Speter  apr_status_t status;
10664251881Speter  const char *dst_path;
10665251881Speter  apr_dir_t *this_dir;
10666251881Speter  apr_finfo_t this_entry;
10667251881Speter  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
10668251881Speter
10669251881Speter  /* Make a subpool for recursion */
10670251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
10671251881Speter
10672251881Speter  /* The 'dst_path' is simply dst_parent/dst_basename */
10673251881Speter  dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
10674251881Speter
10675251881Speter  /* Sanity checks:  SRC and DST_PARENT are directories, and
10676251881Speter     DST_BASENAME doesn't already exist in DST_PARENT. */
10677251881Speter  SVN_ERR(svn_io_check_path(src, &kind, subpool));
10678251881Speter  if (kind != svn_node_dir)
10679251881Speter    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10680251881Speter                             _("Source '%s' is not a directory"),
10681251881Speter                             svn_dirent_local_style(src, pool));
10682251881Speter
10683251881Speter  SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
10684251881Speter  if (kind != svn_node_dir)
10685251881Speter    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10686251881Speter                             _("Destination '%s' is not a directory"),
10687251881Speter                             svn_dirent_local_style(dst_parent, pool));
10688251881Speter
10689251881Speter  SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
10690251881Speter
10691251881Speter  /* Create the new directory. */
10692251881Speter  /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
10693251881Speter  SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
10694251881Speter
10695251881Speter  /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
10696251881Speter  SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
10697251881Speter
10698251881Speter  for (status = apr_dir_read(&this_entry, flags, this_dir);
10699251881Speter       status == APR_SUCCESS;
10700251881Speter       status = apr_dir_read(&this_entry, flags, this_dir))
10701251881Speter    {
10702251881Speter      if ((this_entry.name[0] == '.')
10703251881Speter          && ((this_entry.name[1] == '\0')
10704251881Speter              || ((this_entry.name[1] == '.')
10705251881Speter                  && (this_entry.name[2] == '\0'))))
10706251881Speter        {
10707251881Speter          continue;
10708251881Speter        }
10709251881Speter      else
10710251881Speter        {
10711251881Speter          const char *entryname_utf8;
10712251881Speter
10713251881Speter          if (cancel_func)
10714251881Speter            SVN_ERR(cancel_func(cancel_baton));
10715251881Speter
10716251881Speter          SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
10717251881Speter                                     src, subpool));
10718251881Speter          if (this_entry.filetype == APR_REG) /* regular file */
10719251881Speter            {
10720251881Speter              SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8,
10721251881Speter                                               subpool));
10722251881Speter            }
10723251881Speter          else if (this_entry.filetype == APR_LNK) /* symlink */
10724251881Speter            {
10725251881Speter              const char *src_target = svn_dirent_join(src, entryname_utf8,
10726251881Speter                                                       subpool);
10727251881Speter              const char *dst_target = svn_dirent_join(dst_path,
10728251881Speter                                                       entryname_utf8,
10729251881Speter                                                       subpool);
10730251881Speter              SVN_ERR(svn_io_copy_link(src_target, dst_target,
10731251881Speter                                       subpool));
10732251881Speter            }
10733251881Speter          else if (this_entry.filetype == APR_DIR) /* recurse */
10734251881Speter            {
10735251881Speter              const char *src_target;
10736251881Speter
10737251881Speter              /* Prevent infinite recursion by filtering off our
10738251881Speter                 newly created destination path. */
10739251881Speter              if (strcmp(src, dst_parent) == 0
10740251881Speter                  && strcmp(entryname_utf8, dst_basename) == 0)
10741251881Speter                continue;
10742251881Speter
10743251881Speter              src_target = svn_dirent_join(src, entryname_utf8, subpool);
10744251881Speter              SVN_ERR(hotcopy_io_copy_dir_recursively(src_target,
10745251881Speter                                                      dst_path,
10746251881Speter                                                      entryname_utf8,
10747251881Speter                                                      copy_perms,
10748251881Speter                                                      cancel_func,
10749251881Speter                                                      cancel_baton,
10750251881Speter                                                      subpool));
10751251881Speter            }
10752251881Speter          /* ### support other APR node types someday?? */
10753251881Speter
10754251881Speter        }
10755251881Speter    }
10756251881Speter
10757251881Speter  if (! (APR_STATUS_IS_ENOENT(status)))
10758251881Speter    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
10759251881Speter                              svn_dirent_local_style(src, pool));
10760251881Speter
10761251881Speter  status = apr_dir_close(this_dir);
10762251881Speter  if (status)
10763251881Speter    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
10764251881Speter                              svn_dirent_local_style(src, pool));
10765251881Speter
10766251881Speter  /* Free any memory used by recursion */
10767251881Speter  svn_pool_destroy(subpool);
10768251881Speter
10769251881Speter  return SVN_NO_ERROR;
10770251881Speter}
10771251881Speter
10772251881Speter/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
10773251881Speter * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
10774251881Speter * Use SCRATCH_POOL for temporary allocations. */
10775251881Speterstatic svn_error_t *
10776251881Speterhotcopy_copy_shard_file(const char *src_subdir,
10777251881Speter                        const char *dst_subdir,
10778251881Speter                        svn_revnum_t rev,
10779251881Speter                        int max_files_per_dir,
10780251881Speter                        apr_pool_t *scratch_pool)
10781251881Speter{
10782251881Speter  const char *src_subdir_shard = src_subdir,
10783251881Speter             *dst_subdir_shard = dst_subdir;
10784251881Speter
10785251881Speter  if (max_files_per_dir)
10786251881Speter    {
10787251881Speter      const char *shard = apr_psprintf(scratch_pool, "%ld",
10788251881Speter                                       rev / max_files_per_dir);
10789251881Speter      src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
10790251881Speter      dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10791251881Speter
10792251881Speter      if (rev % max_files_per_dir == 0)
10793251881Speter        {
10794251881Speter          SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
10795251881Speter          SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
10796251881Speter                                    scratch_pool));
10797251881Speter        }
10798251881Speter    }
10799251881Speter
10800251881Speter  SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
10801251881Speter                                   apr_psprintf(scratch_pool, "%ld", rev),
10802251881Speter                                   scratch_pool));
10803251881Speter  return SVN_NO_ERROR;
10804251881Speter}
10805251881Speter
10806251881Speter
10807251881Speter/* Copy a packed shard containing revision REV, and which contains
10808251881Speter * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
10809251881Speter * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
10810251881Speter * Do not re-copy data which already exists in DST_FS.
10811251881Speter * Use SCRATCH_POOL for temporary allocations. */
10812251881Speterstatic svn_error_t *
10813251881Speterhotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev,
10814251881Speter                          svn_fs_t *src_fs,
10815251881Speter                          svn_fs_t *dst_fs,
10816251881Speter                          svn_revnum_t rev,
10817251881Speter                          int max_files_per_dir,
10818251881Speter                          apr_pool_t *scratch_pool)
10819251881Speter{
10820251881Speter  const char *src_subdir;
10821251881Speter  const char *dst_subdir;
10822251881Speter  const char *packed_shard;
10823251881Speter  const char *src_subdir_packed_shard;
10824251881Speter  svn_revnum_t revprop_rev;
10825251881Speter  apr_pool_t *iterpool;
10826251881Speter  fs_fs_data_t *src_ffd = src_fs->fsap_data;
10827251881Speter
10828251881Speter  /* Copy the packed shard. */
10829251881Speter  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
10830251881Speter  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
10831251881Speter  packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10832251881Speter                              rev / max_files_per_dir);
10833251881Speter  src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10834251881Speter                                            scratch_pool);
10835251881Speter  SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10836251881Speter                                          dst_subdir, packed_shard,
10837251881Speter                                          TRUE /* copy_perms */,
10838251881Speter                                          NULL /* cancel_func */, NULL,
10839251881Speter                                          scratch_pool));
10840251881Speter
10841251881Speter  /* Copy revprops belonging to revisions in this pack. */
10842251881Speter  src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10843251881Speter  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10844251881Speter
10845251881Speter  if (   src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
10846251881Speter      || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
10847251881Speter    {
10848251881Speter      /* copy unpacked revprops rev by rev */
10849251881Speter      iterpool = svn_pool_create(scratch_pool);
10850251881Speter      for (revprop_rev = rev;
10851251881Speter           revprop_rev < rev + max_files_per_dir;
10852251881Speter           revprop_rev++)
10853251881Speter        {
10854251881Speter          svn_pool_clear(iterpool);
10855251881Speter
10856251881Speter          SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10857251881Speter                                          revprop_rev, max_files_per_dir,
10858251881Speter                                          iterpool));
10859251881Speter        }
10860251881Speter      svn_pool_destroy(iterpool);
10861251881Speter    }
10862251881Speter  else
10863251881Speter    {
10864251881Speter      /* revprop for revision 0 will never be packed */
10865251881Speter      if (rev == 0)
10866251881Speter        SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10867251881Speter                                        0, max_files_per_dir,
10868251881Speter                                        scratch_pool));
10869251881Speter
10870251881Speter      /* packed revprops folder */
10871251881Speter      packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10872251881Speter                                  rev / max_files_per_dir);
10873251881Speter      src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10874251881Speter                                                scratch_pool);
10875251881Speter      SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10876251881Speter                                              dst_subdir, packed_shard,
10877251881Speter                                              TRUE /* copy_perms */,
10878251881Speter                                              NULL /* cancel_func */, NULL,
10879251881Speter                                              scratch_pool));
10880251881Speter    }
10881251881Speter
10882251881Speter  /* If necessary, update the min-unpacked rev file in the hotcopy. */
10883251881Speter  if (*dst_min_unpacked_rev < rev + max_files_per_dir)
10884251881Speter    {
10885251881Speter      *dst_min_unpacked_rev = rev + max_files_per_dir;
10886251881Speter      SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV,
10887251881Speter                                *dst_min_unpacked_rev,
10888251881Speter                                scratch_pool));
10889251881Speter    }
10890251881Speter
10891251881Speter  return SVN_NO_ERROR;
10892251881Speter}
10893251881Speter
10894251881Speter/* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current'
10895251881Speter * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST.
10896251881Speter * Use SCRATCH_POOL for temporary allocations. */
10897251881Speterstatic svn_error_t *
10898251881Speterhotcopy_update_current(svn_revnum_t *dst_youngest,
10899251881Speter                       svn_fs_t *dst_fs,
10900251881Speter                       svn_revnum_t new_youngest,
10901251881Speter                       apr_pool_t *scratch_pool)
10902251881Speter{
10903251881Speter  char next_node_id[MAX_KEY_SIZE] = "0";
10904251881Speter  char next_copy_id[MAX_KEY_SIZE] = "0";
10905251881Speter  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
10906251881Speter
10907251881Speter  if (*dst_youngest >= new_youngest)
10908251881Speter    return SVN_NO_ERROR;
10909251881Speter
10910251881Speter  /* If necessary, get new current next_node and next_copy IDs. */
10911251881Speter  if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
10912251881Speter    {
10913251881Speter      apr_off_t root_offset;
10914251881Speter      apr_file_t *rev_file;
10915251881Speter
10916251881Speter      if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
10917251881Speter        SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool));
10918251881Speter
10919251881Speter      SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest,
10920251881Speter                                    scratch_pool));
10921251881Speter      SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file,
10922251881Speter                                      dst_fs, new_youngest, scratch_pool));
10923251881Speter      SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file,
10924251881Speter                                   root_offset, next_node_id, next_copy_id,
10925251881Speter                                   scratch_pool));
10926251881Speter      SVN_ERR(svn_io_file_close(rev_file, scratch_pool));
10927251881Speter    }
10928251881Speter
10929251881Speter  /* Update 'current'. */
10930251881Speter  SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id,
10931251881Speter                        scratch_pool));
10932251881Speter
10933251881Speter  *dst_youngest = new_youngest;
10934251881Speter
10935251881Speter  return SVN_NO_ERROR;
10936251881Speter}
10937251881Speter
10938251881Speter
10939262253Speter/* Remove revision or revprop files between START_REV (inclusive) and
10940262253Speter * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS.  Assume
10941262253Speter * sharding as per MAX_FILES_PER_DIR.
10942251881Speter * Use SCRATCH_POOL for temporary allocations. */
10943251881Speterstatic svn_error_t *
10944262253Speterhotcopy_remove_files(svn_fs_t *dst_fs,
10945262253Speter                     const char *dst_subdir,
10946262253Speter                     svn_revnum_t start_rev,
10947262253Speter                     svn_revnum_t end_rev,
10948262253Speter                     int max_files_per_dir,
10949262253Speter                     apr_pool_t *scratch_pool)
10950251881Speter{
10951251881Speter  const char *shard;
10952251881Speter  const char *dst_subdir_shard;
10953251881Speter  svn_revnum_t rev;
10954251881Speter  apr_pool_t *iterpool;
10955251881Speter
10956251881Speter  /* Pre-compute paths for initial shard. */
10957251881Speter  shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
10958251881Speter  dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10959251881Speter
10960251881Speter  iterpool = svn_pool_create(scratch_pool);
10961251881Speter  for (rev = start_rev; rev < end_rev; rev++)
10962251881Speter    {
10963262253Speter      const char *path;
10964251881Speter      svn_pool_clear(iterpool);
10965251881Speter
10966251881Speter      /* If necessary, update paths for shard. */
10967251881Speter      if (rev != start_rev && rev % max_files_per_dir == 0)
10968251881Speter        {
10969251881Speter          shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
10970251881Speter          dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10971251881Speter        }
10972251881Speter
10973262253Speter      /* remove files for REV */
10974262253Speter      path = svn_dirent_join(dst_subdir_shard,
10975262253Speter                             apr_psprintf(iterpool, "%ld", rev),
10976262253Speter                             iterpool);
10977251881Speter
10978251881Speter      /* Make the rev file writable and remove it. */
10979262253Speter      SVN_ERR(svn_io_set_file_read_write(path, TRUE, iterpool));
10980262253Speter      SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
10981251881Speter    }
10982262253Speter
10983251881Speter  svn_pool_destroy(iterpool);
10984251881Speter
10985251881Speter  return SVN_NO_ERROR;
10986251881Speter}
10987251881Speter
10988262253Speter/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
10989262253Speter * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
10990262253Speter * Use SCRATCH_POOL for temporary allocations. */
10991262253Speterstatic svn_error_t *
10992262253Speterhotcopy_remove_rev_files(svn_fs_t *dst_fs,
10993262253Speter                         svn_revnum_t start_rev,
10994262253Speter                         svn_revnum_t end_rev,
10995262253Speter                         int max_files_per_dir,
10996262253Speter                         apr_pool_t *scratch_pool)
10997262253Speter{
10998262253Speter  SVN_ERR_ASSERT(start_rev <= end_rev);
10999262253Speter  SVN_ERR(hotcopy_remove_files(dst_fs,
11000262253Speter                               svn_dirent_join(dst_fs->path,
11001262253Speter                                               PATH_REVS_DIR,
11002262253Speter                                               scratch_pool),
11003262253Speter                               start_rev, end_rev,
11004262253Speter                               max_files_per_dir, scratch_pool));
11005262253Speter
11006262253Speter  return SVN_NO_ERROR;
11007262253Speter}
11008262253Speter
11009262253Speter/* Remove revision properties between START_REV (inclusive) and END_REV
11010262253Speter * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
11011262253Speter * Use SCRATCH_POOL for temporary allocations.  Revision 0 revprops will
11012262253Speter * not be deleted. */
11013262253Speterstatic svn_error_t *
11014262253Speterhotcopy_remove_revprop_files(svn_fs_t *dst_fs,
11015262253Speter                             svn_revnum_t start_rev,
11016262253Speter                             svn_revnum_t end_rev,
11017262253Speter                             int max_files_per_dir,
11018262253Speter                             apr_pool_t *scratch_pool)
11019262253Speter{
11020262253Speter  SVN_ERR_ASSERT(start_rev <= end_rev);
11021262253Speter
11022262253Speter  /* don't delete rev 0 props */
11023262253Speter  SVN_ERR(hotcopy_remove_files(dst_fs,
11024262253Speter                               svn_dirent_join(dst_fs->path,
11025262253Speter                                               PATH_REVPROPS_DIR,
11026262253Speter                                               scratch_pool),
11027262253Speter                               start_rev ? start_rev : 1, end_rev,
11028262253Speter                               max_files_per_dir, scratch_pool));
11029262253Speter
11030262253Speter  return SVN_NO_ERROR;
11031262253Speter}
11032262253Speter
11033251881Speter/* Verify that DST_FS is a suitable destination for an incremental
11034251881Speter * hotcopy from SRC_FS. */
11035251881Speterstatic svn_error_t *
11036251881Speterhotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
11037251881Speter                                        svn_fs_t *dst_fs,
11038251881Speter                                        apr_pool_t *pool)
11039251881Speter{
11040251881Speter  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11041251881Speter  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11042251881Speter
11043251881Speter  /* We only support incremental hotcopy between the same format. */
11044251881Speter  if (src_ffd->format != dst_ffd->format)
11045251881Speter    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11046251881Speter      _("The FSFS format (%d) of the hotcopy source does not match the "
11047251881Speter        "FSFS format (%d) of the hotcopy destination; please upgrade "
11048251881Speter        "both repositories to the same format"),
11049251881Speter      src_ffd->format, dst_ffd->format);
11050251881Speter
11051251881Speter  /* Make sure the UUID of source and destination match up.
11052251881Speter   * We don't want to copy over a different repository. */
11053251881Speter  if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
11054251881Speter    return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
11055251881Speter                            _("The UUID of the hotcopy source does "
11056251881Speter                              "not match the UUID of the hotcopy "
11057251881Speter                              "destination"));
11058251881Speter
11059251881Speter  /* Also require same shard size. */
11060251881Speter  if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
11061251881Speter    return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11062251881Speter                            _("The sharding layout configuration "
11063251881Speter                              "of the hotcopy source does not match "
11064251881Speter                              "the sharding layout configuration of "
11065251881Speter                              "the hotcopy destination"));
11066251881Speter  return SVN_NO_ERROR;
11067251881Speter}
11068251881Speter
11069262253Speter/* Remove folder PATH.  Ignore errors due to the sub-tree not being empty.
11070262253Speter * CANCEL_FUNC and CANCEL_BATON do the usual thing.
11071262253Speter * Use POOL for temporary allocations.
11072262253Speter */
11073262253Speterstatic svn_error_t *
11074262253Speterremove_folder(const char *path,
11075262253Speter              svn_cancel_func_t cancel_func,
11076262253Speter              void *cancel_baton,
11077262253Speter              apr_pool_t *pool)
11078262253Speter{
11079262253Speter  svn_error_t *err = svn_io_remove_dir2(path, TRUE,
11080262253Speter                                        cancel_func, cancel_baton, pool);
11081251881Speter
11082262253Speter  if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
11083262253Speter    {
11084262253Speter      svn_error_clear(err);
11085262253Speter      err = SVN_NO_ERROR;
11086262253Speter    }
11087262253Speter
11088262253Speter  return svn_error_trace(err);
11089262253Speter}
11090262253Speter
11091251881Speter/* Baton for hotcopy_body(). */
11092251881Speterstruct hotcopy_body_baton {
11093251881Speter  svn_fs_t *src_fs;
11094251881Speter  svn_fs_t *dst_fs;
11095251881Speter  svn_boolean_t incremental;
11096251881Speter  svn_cancel_func_t cancel_func;
11097251881Speter  void *cancel_baton;
11098251881Speter} hotcopy_body_baton;
11099251881Speter
11100251881Speter/* Perform a hotcopy, either normal or incremental.
11101251881Speter *
11102251881Speter * Normal hotcopy assumes that the destination exists as an empty
11103251881Speter * directory. It behaves like an incremental hotcopy except that
11104251881Speter * none of the copied files already exist in the destination.
11105251881Speter *
11106251881Speter * An incremental hotcopy copies only changed or new files to the destination,
11107251881Speter * and removes files from the destination no longer present in the source.
11108251881Speter * While the incremental hotcopy is running, readers should still be able
11109251881Speter * to access the destintation repository without error and should not see
11110251881Speter * revisions currently in progress of being copied. Readers are able to see
11111251881Speter * new fully copied revisions even if the entire incremental hotcopy procedure
11112251881Speter * has not yet completed.
11113251881Speter *
11114251881Speter * Writers are blocked out completely during the entire incremental hotcopy
11115251881Speter * process to ensure consistency. This function assumes that the repository
11116251881Speter * write-lock is held.
11117251881Speter */
11118251881Speterstatic svn_error_t *
11119251881Speterhotcopy_body(void *baton, apr_pool_t *pool)
11120251881Speter{
11121251881Speter  struct hotcopy_body_baton *hbb = baton;
11122251881Speter  svn_fs_t *src_fs = hbb->src_fs;
11123251881Speter  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11124251881Speter  svn_fs_t *dst_fs = hbb->dst_fs;
11125251881Speter  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11126251881Speter  int max_files_per_dir = src_ffd->max_files_per_dir;
11127251881Speter  svn_boolean_t incremental = hbb->incremental;
11128251881Speter  svn_cancel_func_t cancel_func = hbb->cancel_func;
11129251881Speter  void* cancel_baton = hbb->cancel_baton;
11130251881Speter  svn_revnum_t src_youngest;
11131251881Speter  svn_revnum_t dst_youngest;
11132251881Speter  svn_revnum_t rev;
11133251881Speter  svn_revnum_t src_min_unpacked_rev;
11134251881Speter  svn_revnum_t dst_min_unpacked_rev;
11135251881Speter  const char *src_subdir;
11136251881Speter  const char *dst_subdir;
11137251881Speter  const char *revprop_src_subdir;
11138251881Speter  const char *revprop_dst_subdir;
11139251881Speter  apr_pool_t *iterpool;
11140251881Speter  svn_node_kind_t kind;
11141251881Speter
11142251881Speter  /* Try to copy the config.
11143251881Speter   *
11144251881Speter   * ### We try copying the config file before doing anything else,
11145251881Speter   * ### because higher layers will abort the hotcopy if we throw
11146251881Speter   * ### an error from this function, and that renders the hotcopy
11147251881Speter   * ### unusable anyway. */
11148251881Speter  if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
11149251881Speter    {
11150251881Speter      svn_error_t *err;
11151251881Speter
11152251881Speter      err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
11153251881Speter                                 pool);
11154251881Speter      if (err)
11155251881Speter        {
11156251881Speter          if (APR_STATUS_IS_ENOENT(err->apr_err))
11157251881Speter            {
11158251881Speter              /* 1.6.0 to 1.6.11 did not copy the configuration file during
11159251881Speter               * hotcopy. So if we're hotcopying a repository which has been
11160251881Speter               * created as a hotcopy itself, it's possible that fsfs.conf
11161251881Speter               * does not exist. Ask the user to re-create it.
11162251881Speter               *
11163251881Speter               * ### It would be nice to make this a non-fatal error,
11164251881Speter               * ### but this function does not get an svn_fs_t object
11165251881Speter               * ### so we have no way of just printing a warning via
11166251881Speter               * ### the fs->warning() callback. */
11167251881Speter
11168251881Speter              const char *msg;
11169251881Speter              const char *src_abspath;
11170251881Speter              const char *dst_abspath;
11171251881Speter              const char *config_relpath;
11172251881Speter              svn_error_t *err2;
11173251881Speter
11174251881Speter              config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
11175251881Speter              err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
11176251881Speter              if (err2)
11177251881Speter                return svn_error_trace(svn_error_compose_create(err, err2));
11178251881Speter              err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
11179251881Speter              if (err2)
11180251881Speter                return svn_error_trace(svn_error_compose_create(err, err2));
11181251881Speter
11182251881Speter              /* ### hack: strip off the 'db/' directory from paths so
11183251881Speter               * ### they make sense to the user */
11184251881Speter              src_abspath = svn_dirent_dirname(src_abspath, pool);
11185251881Speter              dst_abspath = svn_dirent_dirname(dst_abspath, pool);
11186251881Speter
11187251881Speter              msg = apr_psprintf(pool,
11188251881Speter                                 _("Failed to create hotcopy at '%s'. "
11189251881Speter                                   "The file '%s' is missing from the source "
11190251881Speter                                   "repository. Please create this file, for "
11191251881Speter                                   "instance by running 'svnadmin upgrade %s'"),
11192251881Speter                                 dst_abspath, config_relpath, src_abspath);
11193251881Speter              return svn_error_quick_wrap(err, msg);
11194251881Speter            }
11195251881Speter          else
11196251881Speter            return svn_error_trace(err);
11197251881Speter        }
11198251881Speter    }
11199251881Speter
11200251881Speter  if (cancel_func)
11201251881Speter    SVN_ERR(cancel_func(cancel_baton));
11202251881Speter
11203251881Speter  /* Find the youngest revision in the source and destination.
11204251881Speter   * We only support hotcopies from sources with an equal or greater amount
11205251881Speter   * of revisions than the destination.
11206251881Speter   * This also catches the case where users accidentally swap the
11207251881Speter   * source and destination arguments. */
11208251881Speter  SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool));
11209251881Speter  if (incremental)
11210251881Speter    {
11211251881Speter      SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool));
11212251881Speter      if (src_youngest < dst_youngest)
11213251881Speter        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11214251881Speter                 _("The hotcopy destination already contains more revisions "
11215251881Speter                   "(%lu) than the hotcopy source contains (%lu); are source "
11216251881Speter                   "and destination swapped?"),
11217251881Speter                  dst_youngest, src_youngest);
11218251881Speter    }
11219251881Speter  else
11220251881Speter    dst_youngest = 0;
11221251881Speter
11222251881Speter  if (cancel_func)
11223251881Speter    SVN_ERR(cancel_func(cancel_baton));
11224251881Speter
11225251881Speter  /* Copy the min unpacked rev, and read its value. */
11226251881Speter  if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11227251881Speter    {
11228251881Speter      const char *min_unpacked_rev_path;
11229251881Speter
11230251881Speter      min_unpacked_rev_path = svn_dirent_join(src_fs->path,
11231251881Speter                                              PATH_MIN_UNPACKED_REV,
11232251881Speter                                              pool);
11233251881Speter      SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev,
11234251881Speter                                    min_unpacked_rev_path,
11235251881Speter                                    pool));
11236251881Speter
11237251881Speter      min_unpacked_rev_path = svn_dirent_join(dst_fs->path,
11238251881Speter                                              PATH_MIN_UNPACKED_REV,
11239251881Speter                                              pool);
11240251881Speter      SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev,
11241251881Speter                                    min_unpacked_rev_path,
11242251881Speter                                    pool));
11243251881Speter
11244251881Speter      /* We only support packs coming from the hotcopy source.
11245251881Speter       * The destination should not be packed independently from
11246251881Speter       * the source. This also catches the case where users accidentally
11247251881Speter       * swap the source and destination arguments. */
11248251881Speter      if (src_min_unpacked_rev < dst_min_unpacked_rev)
11249251881Speter        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11250251881Speter                                 _("The hotcopy destination already contains "
11251251881Speter                                   "more packed revisions (%lu) than the "
11252251881Speter                                   "hotcopy source contains (%lu)"),
11253251881Speter                                   dst_min_unpacked_rev - 1,
11254251881Speter                                   src_min_unpacked_rev - 1);
11255251881Speter
11256251881Speter      SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11257251881Speter                                   PATH_MIN_UNPACKED_REV, pool));
11258251881Speter    }
11259251881Speter  else
11260251881Speter    {
11261251881Speter      src_min_unpacked_rev = 0;
11262251881Speter      dst_min_unpacked_rev = 0;
11263251881Speter    }
11264251881Speter
11265251881Speter  if (cancel_func)
11266251881Speter    SVN_ERR(cancel_func(cancel_baton));
11267251881Speter
11268251881Speter  /*
11269251881Speter   * Copy the necessary rev files.
11270251881Speter   */
11271251881Speter
11272251881Speter  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
11273251881Speter  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
11274251881Speter  SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
11275251881Speter
11276251881Speter  iterpool = svn_pool_create(pool);
11277251881Speter  /* First, copy packed shards. */
11278251881Speter  for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
11279251881Speter    {
11280251881Speter      svn_pool_clear(iterpool);
11281251881Speter
11282251881Speter      if (cancel_func)
11283251881Speter        SVN_ERR(cancel_func(cancel_baton));
11284251881Speter
11285251881Speter      /* Copy the packed shard. */
11286251881Speter      SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11287251881Speter                                        src_fs, dst_fs,
11288251881Speter                                        rev, max_files_per_dir,
11289251881Speter                                        iterpool));
11290251881Speter
11291251881Speter      /* If necessary, update 'current' to the most recent packed rev,
11292251881Speter       * so readers can see new revisions which arrived in this pack. */
11293251881Speter      SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs,
11294251881Speter                                     rev + max_files_per_dir - 1,
11295251881Speter                                     iterpool));
11296251881Speter
11297251881Speter      /* Remove revision files which are now packed. */
11298251881Speter      if (incremental)
11299262253Speter        {
11300262253Speter          SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
11301262253Speter                                           rev + max_files_per_dir,
11302262253Speter                                           max_files_per_dir, iterpool));
11303262253Speter          if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11304262253Speter            SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
11305262253Speter                                                 rev + max_files_per_dir,
11306262253Speter                                                 max_files_per_dir,
11307262253Speter                                                 iterpool));
11308262253Speter        }
11309251881Speter
11310251881Speter      /* Now that all revisions have moved into the pack, the original
11311251881Speter       * rev dir can be removed. */
11312262253Speter      SVN_ERR(remove_folder(path_rev_shard(dst_fs, rev, iterpool),
11313262253Speter                            cancel_func, cancel_baton, iterpool));
11314262253Speter      if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11315262253Speter        SVN_ERR(remove_folder(path_revprops_shard(dst_fs, rev, iterpool),
11316262253Speter                              cancel_func, cancel_baton, iterpool));
11317251881Speter    }
11318251881Speter
11319251881Speter  if (cancel_func)
11320251881Speter    SVN_ERR(cancel_func(cancel_baton));
11321251881Speter
11322251881Speter  /* Now, copy pairs of non-packed revisions and revprop files.
11323251881Speter   * If necessary, update 'current' after copying all files from a shard. */
11324251881Speter  SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
11325251881Speter  SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
11326251881Speter  revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
11327251881Speter  revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
11328251881Speter  SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool));
11329251881Speter  for (; rev <= src_youngest; rev++)
11330251881Speter    {
11331251881Speter      svn_error_t *err;
11332251881Speter
11333251881Speter      svn_pool_clear(iterpool);
11334251881Speter
11335251881Speter      if (cancel_func)
11336251881Speter        SVN_ERR(cancel_func(cancel_baton));
11337251881Speter
11338251881Speter      /* Copy the rev file. */
11339251881Speter      err = hotcopy_copy_shard_file(src_subdir, dst_subdir,
11340251881Speter                                    rev, max_files_per_dir,
11341251881Speter                                    iterpool);
11342251881Speter      if (err)
11343251881Speter        {
11344251881Speter          if (APR_STATUS_IS_ENOENT(err->apr_err) &&
11345251881Speter              src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11346251881Speter            {
11347251881Speter              svn_error_clear(err);
11348251881Speter
11349251881Speter              /* The source rev file does not exist. This can happen if the
11350251881Speter               * source repository is being packed concurrently with this
11351251881Speter               * hotcopy operation.
11352251881Speter               *
11353251881Speter               * If the new revision is now packed, and the youngest revision
11354251881Speter               * we're interested in is not inside this pack, try to copy the
11355251881Speter               * pack instead.
11356251881Speter               *
11357251881Speter               * If the youngest revision ended up being packed, don't try
11358251881Speter               * to be smart and work around this. Just abort the hotcopy. */
11359251881Speter              SVN_ERR(update_min_unpacked_rev(src_fs, pool));
11360251881Speter              if (is_packed_rev(src_fs, rev))
11361251881Speter                {
11362251881Speter                  if (is_packed_rev(src_fs, src_youngest))
11363251881Speter                    return svn_error_createf(
11364251881Speter                             SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11365251881Speter                             _("The assumed HEAD revision (%lu) of the "
11366251881Speter                               "hotcopy source has been packed while the "
11367251881Speter                               "hotcopy was in progress; please restart "
11368251881Speter                               "the hotcopy operation"),
11369251881Speter                             src_youngest);
11370251881Speter
11371251881Speter                  SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11372251881Speter                                                    src_fs, dst_fs,
11373251881Speter                                                    rev, max_files_per_dir,
11374251881Speter                                                    iterpool));
11375251881Speter                  rev = dst_min_unpacked_rev;
11376251881Speter                  continue;
11377251881Speter                }
11378251881Speter              else
11379251881Speter                return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11380251881Speter                                         _("Revision %lu disappeared from the "
11381251881Speter                                           "hotcopy source while hotcopy was "
11382251881Speter                                           "in progress"), rev);
11383251881Speter            }
11384251881Speter          else
11385251881Speter            return svn_error_trace(err);
11386251881Speter        }
11387251881Speter
11388251881Speter      /* Copy the revprop file. */
11389251881Speter      SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir,
11390251881Speter                                      revprop_dst_subdir,
11391251881Speter                                      rev, max_files_per_dir,
11392251881Speter                                      iterpool));
11393251881Speter
11394251881Speter      /* After completing a full shard, update 'current'. */
11395251881Speter      if (max_files_per_dir && rev % max_files_per_dir == 0)
11396251881Speter        SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool));
11397251881Speter    }
11398251881Speter  svn_pool_destroy(iterpool);
11399251881Speter
11400251881Speter  if (cancel_func)
11401251881Speter    SVN_ERR(cancel_func(cancel_baton));
11402251881Speter
11403251881Speter  /* We assume that all revisions were copied now, i.e. we didn't exit the
11404251881Speter   * above loop early. 'rev' was last incremented during exit of the loop. */
11405251881Speter  SVN_ERR_ASSERT(rev == src_youngest + 1);
11406251881Speter
11407251881Speter  /* All revisions were copied. Update 'current'. */
11408251881Speter  SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool));
11409251881Speter
11410251881Speter  /* Replace the locks tree.
11411251881Speter   * This is racy in case readers are currently trying to list locks in
11412251881Speter   * the destination. However, we need to get rid of stale locks.
11413251881Speter   * This is the simplest way of doing this, so we accept this small race. */
11414251881Speter  dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
11415251881Speter  SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
11416251881Speter                             pool));
11417251881Speter  src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
11418251881Speter  SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11419251881Speter  if (kind == svn_node_dir)
11420251881Speter    SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
11421251881Speter                                        PATH_LOCKS_DIR, TRUE,
11422251881Speter                                        cancel_func, cancel_baton, pool));
11423251881Speter
11424251881Speter  /* Now copy the node-origins cache tree. */
11425251881Speter  src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
11426251881Speter  SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11427251881Speter  if (kind == svn_node_dir)
11428251881Speter    SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path,
11429251881Speter                                            PATH_NODE_ORIGINS_DIR, TRUE,
11430251881Speter                                            cancel_func, cancel_baton, pool));
11431251881Speter
11432251881Speter  /*
11433251881Speter   * NB: Data copied below is only read by writers, not readers.
11434251881Speter   *     Writers are still locked out at this point.
11435251881Speter   */
11436251881Speter
11437251881Speter  if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
11438251881Speter    {
11439251881Speter      /* Copy the rep cache and then remove entries for revisions
11440251881Speter       * younger than the destination's youngest revision. */
11441251881Speter      src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
11442251881Speter      dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
11443251881Speter      SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11444251881Speter      if (kind == svn_node_file)
11445251881Speter        {
11446251881Speter          SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
11447251881Speter          SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool));
11448251881Speter        }
11449251881Speter    }
11450251881Speter
11451251881Speter  /* Copy the txn-current file. */
11452251881Speter  if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11453251881Speter    SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11454251881Speter                                 PATH_TXN_CURRENT, pool));
11455251881Speter
11456251881Speter  /* If a revprop generation file exists in the source filesystem,
11457251881Speter   * reset it to zero (since this is on a different path, it will not
11458251881Speter   * overlap with data already in cache).  Also, clean up stale files
11459251881Speter   * used for the named atomics implementation. */
11460251881Speter  SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
11461251881Speter                            &kind, pool));
11462251881Speter  if (kind == svn_node_file)
11463251881Speter    SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool));
11464251881Speter
11465251881Speter  SVN_ERR(cleanup_revprop_namespace(dst_fs));
11466251881Speter
11467251881Speter  /* Hotcopied FS is complete. Stamp it with a format file. */
11468251881Speter  SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool),
11469251881Speter                       dst_ffd->format, max_files_per_dir, TRUE, pool));
11470251881Speter
11471251881Speter  return SVN_NO_ERROR;
11472251881Speter}
11473251881Speter
11474251881Speter
11475251881Speter/* Set up shared data between SRC_FS and DST_FS. */
11476251881Speterstatic void
11477251881Speterhotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs)
11478251881Speter{
11479251881Speter  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11480251881Speter  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11481251881Speter
11482251881Speter  /* The common pool and mutexes are shared between src and dst filesystems.
11483251881Speter   * During hotcopy we only grab the mutexes for the destination, so there
11484251881Speter   * is no risk of dead-lock. We don't write to the src filesystem. Shared
11485251881Speter   * data for the src_fs has already been initialised in fs_hotcopy(). */
11486251881Speter  dst_ffd->shared = src_ffd->shared;
11487251881Speter}
11488251881Speter
11489251881Speter/* Create an empty filesystem at DST_FS at DST_PATH with the same
11490251881Speter * configuration as SRC_FS (uuid, format, and other parameters).
11491251881Speter * After creation DST_FS has no revisions, not even revision zero. */
11492251881Speterstatic svn_error_t *
11493251881Speterhotcopy_create_empty_dest(svn_fs_t *src_fs,
11494251881Speter                          svn_fs_t *dst_fs,
11495251881Speter                          const char *dst_path,
11496251881Speter                          apr_pool_t *pool)
11497251881Speter{
11498251881Speter  fs_fs_data_t *src_ffd = src_fs->fsap_data;
11499251881Speter  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11500251881Speter
11501251881Speter  dst_fs->path = apr_pstrdup(pool, dst_path);
11502251881Speter
11503251881Speter  dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir;
11504251881Speter  dst_ffd->config = src_ffd->config;
11505251881Speter  dst_ffd->format = src_ffd->format;
11506251881Speter
11507251881Speter  /* Create the revision data directories. */
11508251881Speter  if (dst_ffd->max_files_per_dir)
11509251881Speter    SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool),
11510251881Speter                                        pool));
11511251881Speter  else
11512251881Speter    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11513251881Speter                                                        PATH_REVS_DIR, pool),
11514251881Speter                                        pool));
11515251881Speter
11516251881Speter  /* Create the revprops directory. */
11517251881Speter  if (src_ffd->max_files_per_dir)
11518251881Speter    SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool),
11519251881Speter                                        pool));
11520251881Speter  else
11521251881Speter    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11522251881Speter                                                        PATH_REVPROPS_DIR,
11523251881Speter                                                        pool),
11524251881Speter                                        pool));
11525251881Speter
11526251881Speter  /* Create the transaction directory. */
11527251881Speter  SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR,
11528251881Speter                                                      pool),
11529251881Speter                                      pool));
11530251881Speter
11531251881Speter  /* Create the protorevs directory. */
11532251881Speter  if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
11533251881Speter    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11534251881Speter                                                        PATH_TXN_PROTOS_DIR,
11535251881Speter                                                        pool),
11536251881Speter                                        pool));
11537251881Speter
11538251881Speter  /* Create the 'current' file. */
11539251881Speter  SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool),
11540251881Speter                             (dst_ffd->format >=
11541251881Speter                                SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
11542251881Speter                                ? "0\n" : "0 1 1\n"),
11543251881Speter                             pool));
11544251881Speter
11545251881Speter  /* Create lock file and UUID. */
11546251881Speter  SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool));
11547251881Speter  SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool));
11548251881Speter
11549251881Speter  /* Create the min unpacked rev file. */
11550251881Speter  if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11551251881Speter    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool),
11552251881Speter                                                     "0\n", pool));
11553251881Speter  /* Create the txn-current file if the repository supports
11554251881Speter     the transaction sequence file. */
11555251881Speter  if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11556251881Speter    {
11557251881Speter      SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool),
11558251881Speter                                 "0\n", pool));
11559251881Speter      SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool),
11560251881Speter                                 "", pool));
11561251881Speter    }
11562251881Speter
11563251881Speter  dst_ffd->youngest_rev_cache = 0;
11564251881Speter
11565251881Speter  hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11566251881Speter  SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11567251881Speter
11568251881Speter  return SVN_NO_ERROR;
11569251881Speter}
11570251881Speter
11571251881Spetersvn_error_t *
11572251881Spetersvn_fs_fs__hotcopy(svn_fs_t *src_fs,
11573251881Speter                   svn_fs_t *dst_fs,
11574251881Speter                   const char *src_path,
11575251881Speter                   const char *dst_path,
11576251881Speter                   svn_boolean_t incremental,
11577251881Speter                   svn_cancel_func_t cancel_func,
11578251881Speter                   void *cancel_baton,
11579251881Speter                   apr_pool_t *pool)
11580251881Speter{
11581251881Speter  struct hotcopy_body_baton hbb;
11582251881Speter
11583251881Speter  if (cancel_func)
11584251881Speter    SVN_ERR(cancel_func(cancel_baton));
11585251881Speter
11586251881Speter  SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
11587251881Speter
11588251881Speter  if (incremental)
11589251881Speter    {
11590251881Speter      const char *dst_format_abspath;
11591251881Speter      svn_node_kind_t dst_format_kind;
11592251881Speter
11593251881Speter      /* Check destination format to be sure we know how to incrementally
11594251881Speter       * hotcopy to the destination FS. */
11595251881Speter      dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
11596251881Speter      SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
11597251881Speter      if (dst_format_kind == svn_node_none)
11598251881Speter        {
11599251881Speter          /* Destination doesn't exist yet. Perform a normal hotcopy to a
11600251881Speter           * empty destination using the same configuration as the source. */
11601251881Speter          SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11602251881Speter        }
11603251881Speter      else
11604251881Speter        {
11605251881Speter          /* Check the existing repository. */
11606251881Speter          SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
11607251881Speter          SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
11608251881Speter                                                          pool));
11609251881Speter          hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11610251881Speter          SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11611251881Speter        }
11612251881Speter    }
11613251881Speter  else
11614251881Speter    {
11615251881Speter      /* Start out with an empty destination using the same configuration
11616251881Speter       * as the source. */
11617251881Speter      SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11618251881Speter    }
11619251881Speter
11620251881Speter  if (cancel_func)
11621251881Speter    SVN_ERR(cancel_func(cancel_baton));
11622251881Speter
11623251881Speter  hbb.src_fs = src_fs;
11624251881Speter  hbb.dst_fs = dst_fs;
11625251881Speter  hbb.incremental = incremental;
11626251881Speter  hbb.cancel_func = cancel_func;
11627251881Speter  hbb.cancel_baton = cancel_baton;
11628251881Speter  SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool));
11629251881Speter
11630251881Speter  return SVN_NO_ERROR;
11631251881Speter}
11632