]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_fs_fs/fs_fs.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_fs_fs / fs_fs.c
1 /* fs_fs.c --- filesystem operations specific to fs_fs
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <ctype.h>
27 #include <assert.h>
28 #include <errno.h>
29
30 #include <apr_general.h>
31 #include <apr_pools.h>
32 #include <apr_file_io.h>
33 #include <apr_uuid.h>
34 #include <apr_lib.h>
35 #include <apr_md5.h>
36 #include <apr_sha1.h>
37 #include <apr_strings.h>
38 #include <apr_thread_mutex.h>
39
40 #include "svn_pools.h"
41 #include "svn_fs.h"
42 #include "svn_dirent_uri.h"
43 #include "svn_path.h"
44 #include "svn_hash.h"
45 #include "svn_props.h"
46 #include "svn_sorts.h"
47 #include "svn_string.h"
48 #include "svn_time.h"
49 #include "svn_mergeinfo.h"
50 #include "svn_config.h"
51 #include "svn_ctype.h"
52 #include "svn_version.h"
53
54 #include "fs.h"
55 #include "tree.h"
56 #include "lock.h"
57 #include "key-gen.h"
58 #include "fs_fs.h"
59 #include "id.h"
60 #include "rep-cache.h"
61 #include "temp_serializer.h"
62
63 #include "private/svn_string_private.h"
64 #include "private/svn_fs_util.h"
65 #include "private/svn_subr_private.h"
66 #include "private/svn_delta_private.h"
67 #include "../libsvn_fs/fs-loader.h"
68
69 #include "svn_private_config.h"
70 #include "temp_serializer.h"
71
72 /* An arbitrary maximum path length, so clients can't run us out of memory
73  * by giving us arbitrarily large paths. */
74 #define FSFS_MAX_PATH_LEN 4096
75
76 /* The default maximum number of files per directory to store in the
77    rev and revprops directory.  The number below is somewhat arbitrary,
78    and can be overridden by defining the macro while compiling; the
79    figure of 1000 is reasonable for VFAT filesystems, which are by far
80    the worst performers in this area. */
81 #ifndef SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR
82 #define SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR 1000
83 #endif
84
85 /* Begin deltification after a node history exceeded this this limit.
86    Useful values are 4 to 64 with 16 being a good compromise between
87    computational overhead and repository size savings.
88    Should be a power of 2.
89    Values < 2 will result in standard skip-delta behavior. */
90 #define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16
91
92 /* Finding a deltification base takes operations proportional to the
93    number of changes being skipped. To prevent exploding runtime
94    during commits, limit the deltification range to this value.
95    Should be a power of 2 minus one.
96    Values < 1 disable deltification. */
97 #define SVN_FS_FS_MAX_DELTIFICATION_WALK 1023
98
99 /* Give writing processes 10 seconds to replace an existing revprop
100    file with a new one. After that time, we assume that the writing
101    process got aborted and that we have re-read revprops. */
102 #define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
103
104 /* The following are names of atomics that will be used to communicate
105  * revprop updates across all processes on this machine. */
106 #define ATOMIC_REVPROP_GENERATION "rev-prop-generation"
107 #define ATOMIC_REVPROP_TIMEOUT    "rev-prop-timeout"
108 #define ATOMIC_REVPROP_NAMESPACE  "rev-prop-atomics"
109
110 /* Following are defines that specify the textual elements of the
111    native filesystem directories and revision files. */
112
113 /* Headers used to describe node-revision in the revision file. */
114 #define HEADER_ID          "id"
115 #define HEADER_TYPE        "type"
116 #define HEADER_COUNT       "count"
117 #define HEADER_PROPS       "props"
118 #define HEADER_TEXT        "text"
119 #define HEADER_CPATH       "cpath"
120 #define HEADER_PRED        "pred"
121 #define HEADER_COPYFROM    "copyfrom"
122 #define HEADER_COPYROOT    "copyroot"
123 #define HEADER_FRESHTXNRT  "is-fresh-txn-root"
124 #define HEADER_MINFO_HERE  "minfo-here"
125 #define HEADER_MINFO_CNT   "minfo-cnt"
126
127 /* Kinds that a change can be. */
128 #define ACTION_MODIFY      "modify"
129 #define ACTION_ADD         "add"
130 #define ACTION_DELETE      "delete"
131 #define ACTION_REPLACE     "replace"
132 #define ACTION_RESET       "reset"
133
134 /* True and False flags. */
135 #define FLAG_TRUE          "true"
136 #define FLAG_FALSE         "false"
137
138 /* Kinds that a node-rev can be. */
139 #define KIND_FILE          "file"
140 #define KIND_DIR           "dir"
141
142 /* Kinds of representation. */
143 #define REP_PLAIN          "PLAIN"
144 #define REP_DELTA          "DELTA"
145
146 /* Notes:
147
148 To avoid opening and closing the rev-files all the time, it would
149 probably be advantageous to keep each rev-file open for the
150 lifetime of the transaction object.  I'll leave that as a later
151 optimization for now.
152
153 I didn't keep track of pool lifetimes at all in this code.  There
154 are likely some errors because of that.
155
156 */
157
158 /* The vtable associated with an open transaction object. */
159 static txn_vtable_t txn_vtable = {
160   svn_fs_fs__commit_txn,
161   svn_fs_fs__abort_txn,
162   svn_fs_fs__txn_prop,
163   svn_fs_fs__txn_proplist,
164   svn_fs_fs__change_txn_prop,
165   svn_fs_fs__txn_root,
166   svn_fs_fs__change_txn_props
167 };
168
169 /* Declarations. */
170
171 static svn_error_t *
172 read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
173                       const char *path,
174                       apr_pool_t *pool);
175
176 static svn_error_t *
177 update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool);
178
179 static svn_error_t *
180 get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
181
182 static svn_error_t *
183 verify_walker(representation_t *rep,
184               void *baton,
185               svn_fs_t *fs,
186               apr_pool_t *scratch_pool);
187
188 /* Pathname helper functions */
189
190 /* Return TRUE is REV is packed in FS, FALSE otherwise. */
191 static svn_boolean_t
192 is_packed_rev(svn_fs_t *fs, svn_revnum_t rev)
193 {
194   fs_fs_data_t *ffd = fs->fsap_data;
195
196   return (rev < ffd->min_unpacked_rev);
197 }
198
199 /* Return TRUE is REV is packed in FS, FALSE otherwise. */
200 static svn_boolean_t
201 is_packed_revprop(svn_fs_t *fs, svn_revnum_t rev)
202 {
203   fs_fs_data_t *ffd = fs->fsap_data;
204
205   /* rev 0 will not be packed */
206   return (rev < ffd->min_unpacked_rev)
207       && (rev != 0)
208       && (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT);
209 }
210
211 static const char *
212 path_format(svn_fs_t *fs, apr_pool_t *pool)
213 {
214   return svn_dirent_join(fs->path, PATH_FORMAT, pool);
215 }
216
217 static APR_INLINE const char *
218 path_uuid(svn_fs_t *fs, apr_pool_t *pool)
219 {
220   return svn_dirent_join(fs->path, PATH_UUID, pool);
221 }
222
223 const char *
224 svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool)
225 {
226   return svn_dirent_join(fs->path, PATH_CURRENT, pool);
227 }
228
229 static APR_INLINE const char *
230 path_txn_current(svn_fs_t *fs, apr_pool_t *pool)
231 {
232   return svn_dirent_join(fs->path, PATH_TXN_CURRENT, pool);
233 }
234
235 static APR_INLINE const char *
236 path_txn_current_lock(svn_fs_t *fs, apr_pool_t *pool)
237 {
238   return svn_dirent_join(fs->path, PATH_TXN_CURRENT_LOCK, pool);
239 }
240
241 static APR_INLINE const char *
242 path_lock(svn_fs_t *fs, apr_pool_t *pool)
243 {
244   return svn_dirent_join(fs->path, PATH_LOCK_FILE, pool);
245 }
246
247 static const char *
248 path_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
249 {
250   return svn_dirent_join(fs->path, PATH_REVPROP_GENERATION, pool);
251 }
252
253 static const char *
254 path_rev_packed(svn_fs_t *fs, svn_revnum_t rev, const char *kind,
255                 apr_pool_t *pool)
256 {
257   fs_fs_data_t *ffd = fs->fsap_data;
258
259   assert(ffd->max_files_per_dir);
260   assert(is_packed_rev(fs, rev));
261
262   return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
263                               apr_psprintf(pool,
264                                            "%ld" PATH_EXT_PACKED_SHARD,
265                                            rev / ffd->max_files_per_dir),
266                               kind, NULL);
267 }
268
269 static const char *
270 path_rev_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
271 {
272   fs_fs_data_t *ffd = fs->fsap_data;
273
274   assert(ffd->max_files_per_dir);
275   return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
276                               apr_psprintf(pool, "%ld",
277                                                  rev / ffd->max_files_per_dir),
278                               NULL);
279 }
280
281 static const char *
282 path_rev(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
283 {
284   fs_fs_data_t *ffd = fs->fsap_data;
285
286   assert(! is_packed_rev(fs, rev));
287
288   if (ffd->max_files_per_dir)
289     {
290       return svn_dirent_join(path_rev_shard(fs, rev, pool),
291                              apr_psprintf(pool, "%ld", rev),
292                              pool);
293     }
294
295   return svn_dirent_join_many(pool, fs->path, PATH_REVS_DIR,
296                               apr_psprintf(pool, "%ld", rev), NULL);
297 }
298
299 svn_error_t *
300 svn_fs_fs__path_rev_absolute(const char **path,
301                              svn_fs_t *fs,
302                              svn_revnum_t rev,
303                              apr_pool_t *pool)
304 {
305   fs_fs_data_t *ffd = fs->fsap_data;
306
307   if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT
308       || ! is_packed_rev(fs, rev))
309     {
310       *path = path_rev(fs, rev, pool);
311     }
312   else
313     {
314       *path = path_rev_packed(fs, rev, PATH_PACKED, pool);
315     }
316
317   return SVN_NO_ERROR;
318 }
319
320 static const char *
321 path_revprops_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
322 {
323   fs_fs_data_t *ffd = fs->fsap_data;
324
325   assert(ffd->max_files_per_dir);
326   return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
327                               apr_psprintf(pool, "%ld",
328                                            rev / ffd->max_files_per_dir),
329                               NULL);
330 }
331
332 static const char *
333 path_revprops_pack_shard(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
334 {
335   fs_fs_data_t *ffd = fs->fsap_data;
336
337   assert(ffd->max_files_per_dir);
338   return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
339                               apr_psprintf(pool, "%ld" PATH_EXT_PACKED_SHARD,
340                                            rev / ffd->max_files_per_dir),
341                               NULL);
342 }
343
344 static const char *
345 path_revprops(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool)
346 {
347   fs_fs_data_t *ffd = fs->fsap_data;
348
349   if (ffd->max_files_per_dir)
350     {
351       return svn_dirent_join(path_revprops_shard(fs, rev, pool),
352                              apr_psprintf(pool, "%ld", rev),
353                              pool);
354     }
355
356   return svn_dirent_join_many(pool, fs->path, PATH_REVPROPS_DIR,
357                               apr_psprintf(pool, "%ld", rev), NULL);
358 }
359
360 static APR_INLINE const char *
361 path_txn_dir(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
362 {
363   SVN_ERR_ASSERT_NO_RETURN(txn_id != NULL);
364   return svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
365                               apr_pstrcat(pool, txn_id, PATH_EXT_TXN,
366                                           (char *)NULL),
367                               NULL);
368 }
369
370 /* Return the name of the sha1->rep mapping file in transaction TXN_ID
371  * within FS for the given SHA1 checksum.  Use POOL for allocations.
372  */
373 static APR_INLINE const char *
374 path_txn_sha1(svn_fs_t *fs, const char *txn_id, svn_checksum_t *sha1,
375               apr_pool_t *pool)
376 {
377   return svn_dirent_join(path_txn_dir(fs, txn_id, pool),
378                          svn_checksum_to_cstring(sha1, pool),
379                          pool);
380 }
381
382 static APR_INLINE const char *
383 path_txn_changes(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
384 {
385   return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_CHANGES, pool);
386 }
387
388 static APR_INLINE const char *
389 path_txn_props(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
390 {
391   return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_TXN_PROPS, pool);
392 }
393
394 static APR_INLINE const char *
395 path_txn_next_ids(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
396 {
397   return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_NEXT_IDS, pool);
398 }
399
400 static APR_INLINE const char *
401 path_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
402 {
403   return svn_dirent_join(fs->path, PATH_MIN_UNPACKED_REV, pool);
404 }
405
406
407 static APR_INLINE const char *
408 path_txn_proto_rev(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
409 {
410   fs_fs_data_t *ffd = fs->fsap_data;
411   if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
412     return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
413                                 apr_pstrcat(pool, txn_id, PATH_EXT_REV,
414                                             (char *)NULL),
415                                 NULL);
416   else
417     return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV, pool);
418 }
419
420 static APR_INLINE const char *
421 path_txn_proto_rev_lock(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
422 {
423   fs_fs_data_t *ffd = fs->fsap_data;
424   if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
425     return svn_dirent_join_many(pool, fs->path, PATH_TXN_PROTOS_DIR,
426                                 apr_pstrcat(pool, txn_id, PATH_EXT_REV_LOCK,
427                                             (char *)NULL),
428                                 NULL);
429   else
430     return svn_dirent_join(path_txn_dir(fs, txn_id, pool), PATH_REV_LOCK,
431                            pool);
432 }
433
434 static const char *
435 path_txn_node_rev(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
436 {
437   const char *txn_id = svn_fs_fs__id_txn_id(id);
438   const char *node_id = svn_fs_fs__id_node_id(id);
439   const char *copy_id = svn_fs_fs__id_copy_id(id);
440   const char *name = apr_psprintf(pool, PATH_PREFIX_NODE "%s.%s",
441                                   node_id, copy_id);
442
443   return svn_dirent_join(path_txn_dir(fs, txn_id, pool), name, pool);
444 }
445
446 static APR_INLINE const char *
447 path_txn_node_props(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
448 {
449   return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool), PATH_EXT_PROPS,
450                      (char *)NULL);
451 }
452
453 static APR_INLINE const char *
454 path_txn_node_children(svn_fs_t *fs, const svn_fs_id_t *id, apr_pool_t *pool)
455 {
456   return apr_pstrcat(pool, path_txn_node_rev(fs, id, pool),
457                      PATH_EXT_CHILDREN, (char *)NULL);
458 }
459
460 static APR_INLINE const char *
461 path_node_origin(svn_fs_t *fs, const char *node_id, apr_pool_t *pool)
462 {
463   size_t len = strlen(node_id);
464   const char *node_id_minus_last_char =
465     (len == 1) ? "0" : apr_pstrmemdup(pool, node_id, len - 1);
466   return svn_dirent_join_many(pool, fs->path, PATH_NODE_ORIGINS_DIR,
467                               node_id_minus_last_char, NULL);
468 }
469
470 static APR_INLINE const char *
471 path_and_offset_of(apr_file_t *file, apr_pool_t *pool)
472 {
473   const char *path;
474   apr_off_t offset = 0;
475
476   if (apr_file_name_get(&path, file) != APR_SUCCESS)
477     path = "(unknown)";
478
479   if (apr_file_seek(file, APR_CUR, &offset) != APR_SUCCESS)
480     offset = -1;
481
482   return apr_psprintf(pool, "%s:%" APR_OFF_T_FMT, path, offset);
483 }
484
485
486
487 /* Functions for working with shared transaction data. */
488
489 /* Return the transaction object for transaction TXN_ID from the
490    transaction list of filesystem FS (which must already be locked via the
491    txn_list_lock mutex).  If the transaction does not exist in the list,
492    then create a new transaction object and return it (if CREATE_NEW is
493    true) or return NULL (otherwise). */
494 static fs_fs_shared_txn_data_t *
495 get_shared_txn(svn_fs_t *fs, const char *txn_id, svn_boolean_t create_new)
496 {
497   fs_fs_data_t *ffd = fs->fsap_data;
498   fs_fs_shared_data_t *ffsd = ffd->shared;
499   fs_fs_shared_txn_data_t *txn;
500
501   for (txn = ffsd->txns; txn; txn = txn->next)
502     if (strcmp(txn->txn_id, txn_id) == 0)
503       break;
504
505   if (txn || !create_new)
506     return txn;
507
508   /* Use the transaction object from the (single-object) freelist,
509      if one is available, or otherwise create a new object. */
510   if (ffsd->free_txn)
511     {
512       txn = ffsd->free_txn;
513       ffsd->free_txn = NULL;
514     }
515   else
516     {
517       apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
518       txn = apr_palloc(subpool, sizeof(*txn));
519       txn->pool = subpool;
520     }
521
522   assert(strlen(txn_id) < sizeof(txn->txn_id));
523   apr_cpystrn(txn->txn_id, txn_id, sizeof(txn->txn_id));
524   txn->being_written = FALSE;
525
526   /* Link this transaction into the head of the list.  We will typically
527      be dealing with only one active transaction at a time, so it makes
528      sense for searches through the transaction list to look at the
529      newest transactions first.  */
530   txn->next = ffsd->txns;
531   ffsd->txns = txn;
532
533   return txn;
534 }
535
536 /* Free the transaction object for transaction TXN_ID, and remove it
537    from the transaction list of filesystem FS (which must already be
538    locked via the txn_list_lock mutex).  Do nothing if the transaction
539    does not exist. */
540 static void
541 free_shared_txn(svn_fs_t *fs, const char *txn_id)
542 {
543   fs_fs_data_t *ffd = fs->fsap_data;
544   fs_fs_shared_data_t *ffsd = ffd->shared;
545   fs_fs_shared_txn_data_t *txn, *prev = NULL;
546
547   for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
548     if (strcmp(txn->txn_id, txn_id) == 0)
549       break;
550
551   if (!txn)
552     return;
553
554   if (prev)
555     prev->next = txn->next;
556   else
557     ffsd->txns = txn->next;
558
559   /* As we typically will be dealing with one transaction after another,
560      we will maintain a single-object free list so that we can hopefully
561      keep reusing the same transaction object. */
562   if (!ffsd->free_txn)
563     ffsd->free_txn = txn;
564   else
565     svn_pool_destroy(txn->pool);
566 }
567
568
569 /* Obtain a lock on the transaction list of filesystem FS, call BODY
570    with FS, BATON, and POOL, and then unlock the transaction list.
571    Return what BODY returned. */
572 static svn_error_t *
573 with_txnlist_lock(svn_fs_t *fs,
574                   svn_error_t *(*body)(svn_fs_t *fs,
575                                        const void *baton,
576                                        apr_pool_t *pool),
577                   const void *baton,
578                   apr_pool_t *pool)
579 {
580   fs_fs_data_t *ffd = fs->fsap_data;
581   fs_fs_shared_data_t *ffsd = ffd->shared;
582
583   SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
584                        body(fs, baton, pool));
585
586   return SVN_NO_ERROR;
587 }
588
589
590 /* Get a lock on empty file LOCK_FILENAME, creating it in POOL. */
591 static svn_error_t *
592 get_lock_on_filesystem(const char *lock_filename,
593                        apr_pool_t *pool)
594 {
595   svn_error_t *err = svn_io_file_lock2(lock_filename, TRUE, FALSE, pool);
596
597   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
598     {
599       /* No lock file?  No big deal; these are just empty files
600          anyway.  Create it and try again. */
601       svn_error_clear(err);
602       err = NULL;
603
604       SVN_ERR(svn_io_file_create(lock_filename, "", pool));
605       SVN_ERR(svn_io_file_lock2(lock_filename, TRUE, FALSE, pool));
606     }
607
608   return svn_error_trace(err);
609 }
610
611 /* Reset the HAS_WRITE_LOCK member in the FFD given as BATON_VOID.
612    When registered with the pool holding the lock on the lock file,
613    this makes sure the flag gets reset just before we release the lock. */
614 static apr_status_t
615 reset_lock_flag(void *baton_void)
616 {
617   fs_fs_data_t *ffd = baton_void;
618   ffd->has_write_lock = FALSE;
619   return APR_SUCCESS;
620 }
621
622 /* Obtain a write lock on the file LOCK_FILENAME (protecting with
623    LOCK_MUTEX if APR is threaded) in a subpool of POOL, call BODY with
624    BATON and that subpool, destroy the subpool (releasing the write
625    lock) and return what BODY returned.  If IS_GLOBAL_LOCK is set,
626    set the HAS_WRITE_LOCK flag while we keep the write lock. */
627 static svn_error_t *
628 with_some_lock_file(svn_fs_t *fs,
629                     svn_error_t *(*body)(void *baton,
630                                          apr_pool_t *pool),
631                     void *baton,
632                     const char *lock_filename,
633                     svn_boolean_t is_global_lock,
634                     apr_pool_t *pool)
635 {
636   apr_pool_t *subpool = svn_pool_create(pool);
637   svn_error_t *err = get_lock_on_filesystem(lock_filename, subpool);
638
639   if (!err)
640     {
641       fs_fs_data_t *ffd = fs->fsap_data;
642
643       if (is_global_lock)
644         {
645           /* set the "got the lock" flag and register reset function */
646           apr_pool_cleanup_register(subpool,
647                                     ffd,
648                                     reset_lock_flag,
649                                     apr_pool_cleanup_null);
650           ffd->has_write_lock = TRUE;
651         }
652
653       /* nobody else will modify the repo state
654          => read HEAD & pack info once */
655       if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
656         SVN_ERR(update_min_unpacked_rev(fs, pool));
657       SVN_ERR(get_youngest(&ffd->youngest_rev_cache, fs->path,
658                            pool));
659       err = body(baton, subpool);
660     }
661
662   svn_pool_destroy(subpool);
663
664   return svn_error_trace(err);
665 }
666
667 svn_error_t *
668 svn_fs_fs__with_write_lock(svn_fs_t *fs,
669                            svn_error_t *(*body)(void *baton,
670                                                 apr_pool_t *pool),
671                            void *baton,
672                            apr_pool_t *pool)
673 {
674   fs_fs_data_t *ffd = fs->fsap_data;
675   fs_fs_shared_data_t *ffsd = ffd->shared;
676
677   SVN_MUTEX__WITH_LOCK(ffsd->fs_write_lock,
678                        with_some_lock_file(fs, body, baton,
679                                            path_lock(fs, pool),
680                                            TRUE,
681                                            pool));
682
683   return SVN_NO_ERROR;
684 }
685
686 /* Run BODY (with BATON and POOL) while the txn-current file
687    of FS is locked. */
688 static svn_error_t *
689 with_txn_current_lock(svn_fs_t *fs,
690                       svn_error_t *(*body)(void *baton,
691                                            apr_pool_t *pool),
692                       void *baton,
693                       apr_pool_t *pool)
694 {
695   fs_fs_data_t *ffd = fs->fsap_data;
696   fs_fs_shared_data_t *ffsd = ffd->shared;
697
698   SVN_MUTEX__WITH_LOCK(ffsd->txn_current_lock,
699                        with_some_lock_file(fs, body, baton,
700                                            path_txn_current_lock(fs, pool),
701                                            FALSE,
702                                            pool));
703
704   return SVN_NO_ERROR;
705 }
706
707 /* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
708    which see. */
709 struct unlock_proto_rev_baton
710 {
711   const char *txn_id;
712   void *lockcookie;
713 };
714
715 /* Callback used in the implementation of unlock_proto_rev(). */
716 static svn_error_t *
717 unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
718 {
719   const struct unlock_proto_rev_baton *b = baton;
720   const char *txn_id = b->txn_id;
721   apr_file_t *lockfile = b->lockcookie;
722   fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, FALSE);
723   apr_status_t apr_err;
724
725   if (!txn)
726     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
727                              _("Can't unlock unknown transaction '%s'"),
728                              txn_id);
729   if (!txn->being_written)
730     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
731                              _("Can't unlock nonlocked transaction '%s'"),
732                              txn_id);
733
734   apr_err = apr_file_unlock(lockfile);
735   if (apr_err)
736     return svn_error_wrap_apr
737       (apr_err,
738        _("Can't unlock prototype revision lockfile for transaction '%s'"),
739        txn_id);
740   apr_err = apr_file_close(lockfile);
741   if (apr_err)
742     return svn_error_wrap_apr
743       (apr_err,
744        _("Can't close prototype revision lockfile for transaction '%s'"),
745        txn_id);
746
747   txn->being_written = FALSE;
748
749   return SVN_NO_ERROR;
750 }
751
752 /* Unlock the prototype revision file for transaction TXN_ID in filesystem
753    FS using cookie LOCKCOOKIE.  The original prototype revision file must
754    have been closed _before_ calling this function.
755
756    Perform temporary allocations in POOL. */
757 static svn_error_t *
758 unlock_proto_rev(svn_fs_t *fs, const char *txn_id, void *lockcookie,
759                  apr_pool_t *pool)
760 {
761   struct unlock_proto_rev_baton b;
762
763   b.txn_id = txn_id;
764   b.lockcookie = lockcookie;
765   return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
766 }
767
768 /* Same as unlock_proto_rev(), but requires that the transaction list
769    lock is already held. */
770 static svn_error_t *
771 unlock_proto_rev_list_locked(svn_fs_t *fs, const char *txn_id,
772                              void *lockcookie,
773                              apr_pool_t *pool)
774 {
775   struct unlock_proto_rev_baton b;
776
777   b.txn_id = txn_id;
778   b.lockcookie = lockcookie;
779   return unlock_proto_rev_body(fs, &b, pool);
780 }
781
782 /* A structure used by get_writable_proto_rev() and
783    get_writable_proto_rev_body(), which see. */
784 struct get_writable_proto_rev_baton
785 {
786   apr_file_t **file;
787   void **lockcookie;
788   const char *txn_id;
789 };
790
791 /* Callback used in the implementation of get_writable_proto_rev(). */
792 static svn_error_t *
793 get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
794 {
795   const struct get_writable_proto_rev_baton *b = baton;
796   apr_file_t **file = b->file;
797   void **lockcookie = b->lockcookie;
798   const char *txn_id = b->txn_id;
799   svn_error_t *err;
800   fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, txn_id, TRUE);
801
802   /* First, ensure that no thread in this process (including this one)
803      is currently writing to this transaction's proto-rev file. */
804   if (txn->being_written)
805     return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
806                              _("Cannot write to the prototype revision file "
807                                "of transaction '%s' because a previous "
808                                "representation is currently being written by "
809                                "this process"),
810                              txn_id);
811
812
813   /* We know that no thread in this process is writing to the proto-rev
814      file, and by extension, that no thread in this process is holding a
815      lock on the prototype revision lock file.  It is therefore safe
816      for us to attempt to lock this file, to see if any other process
817      is holding a lock. */
818
819   {
820     apr_file_t *lockfile;
821     apr_status_t apr_err;
822     const char *lockfile_path = path_txn_proto_rev_lock(fs, txn_id, pool);
823
824     /* Open the proto-rev lockfile, creating it if necessary, as it may
825        not exist if the transaction dates from before the lockfiles were
826        introduced.
827
828        ### We'd also like to use something like svn_io_file_lock2(), but
829            that forces us to create a subpool just to be able to unlock
830            the file, which seems a waste. */
831     SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
832                              APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
833
834     apr_err = apr_file_lock(lockfile,
835                             APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
836     if (apr_err)
837       {
838         svn_error_clear(svn_io_file_close(lockfile, pool));
839
840         if (APR_STATUS_IS_EAGAIN(apr_err))
841           return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
842                                    _("Cannot write to the prototype revision "
843                                      "file of transaction '%s' because a "
844                                      "previous representation is currently "
845                                      "being written by another process"),
846                                    txn_id);
847
848         return svn_error_wrap_apr(apr_err,
849                                   _("Can't get exclusive lock on file '%s'"),
850                                   svn_dirent_local_style(lockfile_path, pool));
851       }
852
853     *lockcookie = lockfile;
854   }
855
856   /* We've successfully locked the transaction; mark it as such. */
857   txn->being_written = TRUE;
858
859
860   /* Now open the prototype revision file and seek to the end. */
861   err = svn_io_file_open(file, path_txn_proto_rev(fs, txn_id, pool),
862                          APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT, pool);
863
864   /* You might expect that we could dispense with the following seek
865      and achieve the same thing by opening the file using APR_APPEND.
866      Unfortunately, APR's buffered file implementation unconditionally
867      places its initial file pointer at the start of the file (even for
868      files opened with APR_APPEND), so we need this seek to reconcile
869      the APR file pointer to the OS file pointer (since we need to be
870      able to read the current file position later). */
871   if (!err)
872     {
873       apr_off_t offset = 0;
874       err = svn_io_file_seek(*file, APR_END, &offset, pool);
875     }
876
877   if (err)
878     {
879       err = svn_error_compose_create(
880               err,
881               unlock_proto_rev_list_locked(fs, txn_id, *lockcookie, pool));
882
883       *lockcookie = NULL;
884     }
885
886   return svn_error_trace(err);
887 }
888
889 /* Get a handle to the prototype revision file for transaction TXN_ID in
890    filesystem FS, and lock it for writing.  Return FILE, a file handle
891    positioned at the end of the file, and LOCKCOOKIE, a cookie that
892    should be passed to unlock_proto_rev() to unlock the file once FILE
893    has been closed.
894
895    If the prototype revision file is already locked, return error
896    SVN_ERR_FS_REP_BEING_WRITTEN.
897
898    Perform all allocations in POOL. */
899 static svn_error_t *
900 get_writable_proto_rev(apr_file_t **file,
901                        void **lockcookie,
902                        svn_fs_t *fs, const char *txn_id,
903                        apr_pool_t *pool)
904 {
905   struct get_writable_proto_rev_baton b;
906
907   b.file = file;
908   b.lockcookie = lockcookie;
909   b.txn_id = txn_id;
910
911   return with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool);
912 }
913
914 /* Callback used in the implementation of purge_shared_txn(). */
915 static svn_error_t *
916 purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
917 {
918   const char *txn_id = baton;
919
920   free_shared_txn(fs, txn_id);
921   svn_fs_fs__reset_txn_caches(fs);
922
923   return SVN_NO_ERROR;
924 }
925
926 /* Purge the shared data for transaction TXN_ID in filesystem FS.
927    Perform all allocations in POOL. */
928 static svn_error_t *
929 purge_shared_txn(svn_fs_t *fs, const char *txn_id, apr_pool_t *pool)
930 {
931   return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
932 }
933 \f
934
935
936 /* Fetch the current offset of FILE into *OFFSET_P. */
937 static svn_error_t *
938 get_file_offset(apr_off_t *offset_p, apr_file_t *file, apr_pool_t *pool)
939 {
940   apr_off_t offset;
941
942   /* Note that, for buffered files, one (possibly surprising) side-effect
943      of this call is to flush any unwritten data to disk. */
944   offset = 0;
945   SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
946   *offset_p = offset;
947
948   return SVN_NO_ERROR;
949 }
950
951
952 /* Check that BUF, a nul-terminated buffer of text from file PATH,
953    contains only digits at OFFSET and beyond, raising an error if not.
954    TITLE contains a user-visible description of the file, usually the
955    short file name.
956
957    Uses POOL for temporary allocation. */
958 static svn_error_t *
959 check_file_buffer_numeric(const char *buf, apr_off_t offset,
960                           const char *path, const char *title,
961                           apr_pool_t *pool)
962 {
963   const char *p;
964
965   for (p = buf + offset; *p; p++)
966     if (!svn_ctype_isdigit(*p))
967       return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
968         _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
969         title, svn_dirent_local_style(path, pool), *p, buf);
970
971   return SVN_NO_ERROR;
972 }
973
974 /* Check that BUF, a nul-terminated buffer of text from format file PATH,
975    contains only digits at OFFSET and beyond, raising an error if not.
976
977    Uses POOL for temporary allocation. */
978 static svn_error_t *
979 check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
980                                  const char *path, apr_pool_t *pool)
981 {
982   return check_file_buffer_numeric(buf, offset, path, "Format", pool);
983 }
984
985 /* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
986    number is not the same as a format number supported by this
987    Subversion. */
988 static svn_error_t *
989 check_format(int format)
990 {
991   /* Blacklist.  These formats may be either younger or older than
992      SVN_FS_FS__FORMAT_NUMBER, but we don't support them. */
993   if (format == SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT)
994     return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
995                              _("Found format '%d', only created by "
996                                "unreleased dev builds; see "
997                                "http://subversion.apache.org"
998                                "/docs/release-notes/1.7#revprop-packing"),
999                              format);
1000
1001   /* We support all formats from 1-current simultaneously */
1002   if (1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER)
1003     return SVN_NO_ERROR;
1004
1005   return svn_error_createf(SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
1006      _("Expected FS format between '1' and '%d'; found format '%d'"),
1007      SVN_FS_FS__FORMAT_NUMBER, format);
1008 }
1009
1010 /* Read the format number and maximum number of files per directory
1011    from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
1012    respectively.
1013
1014    *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
1015    will be set to zero if a linear scheme should be used.
1016
1017    Use POOL for temporary allocation. */
1018 static svn_error_t *
1019 read_format(int *pformat, int *max_files_per_dir,
1020             const char *path, apr_pool_t *pool)
1021 {
1022   svn_error_t *err;
1023   svn_stream_t *stream;
1024   svn_stringbuf_t *content;
1025   svn_stringbuf_t *buf;
1026   svn_boolean_t eos = FALSE;
1027
1028   err = svn_stringbuf_from_file2(&content, path, pool);
1029   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1030     {
1031       /* Treat an absent format file as format 1.  Do not try to
1032          create the format file on the fly, because the repository
1033          might be read-only for us, or this might be a read-only
1034          operation, and the spirit of FSFS is to make no changes
1035          whatseover in read-only operations.  See thread starting at
1036          http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=97600
1037          for more. */
1038       svn_error_clear(err);
1039       *pformat = 1;
1040       *max_files_per_dir = 0;
1041
1042       return SVN_NO_ERROR;
1043     }
1044   SVN_ERR(err);
1045
1046   stream = svn_stream_from_stringbuf(content, pool);
1047   SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
1048   if (buf->len == 0 && eos)
1049     {
1050       /* Return a more useful error message. */
1051       return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
1052                                _("Can't read first line of format file '%s'"),
1053                                svn_dirent_local_style(path, pool));
1054     }
1055
1056   /* Check that the first line contains only digits. */
1057   SVN_ERR(check_format_file_buffer_numeric(buf->data, 0, path, pool));
1058   SVN_ERR(svn_cstring_atoi(pformat, buf->data));
1059
1060   /* Check that we support this format at all */
1061   SVN_ERR(check_format(*pformat));
1062
1063   /* Set the default values for anything that can be set via an option. */
1064   *max_files_per_dir = 0;
1065
1066   /* Read any options. */
1067   while (!eos)
1068     {
1069       SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eos, pool));
1070       if (buf->len == 0)
1071         break;
1072
1073       if (*pformat >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT &&
1074           strncmp(buf->data, "layout ", 7) == 0)
1075         {
1076           if (strcmp(buf->data + 7, "linear") == 0)
1077             {
1078               *max_files_per_dir = 0;
1079               continue;
1080             }
1081
1082           if (strncmp(buf->data + 7, "sharded ", 8) == 0)
1083             {
1084               /* Check that the argument is numeric. */
1085               SVN_ERR(check_format_file_buffer_numeric(buf->data, 15, path, pool));
1086               SVN_ERR(svn_cstring_atoi(max_files_per_dir, buf->data + 15));
1087               continue;
1088             }
1089         }
1090
1091       return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
1092          _("'%s' contains invalid filesystem format option '%s'"),
1093          svn_dirent_local_style(path, pool), buf->data);
1094     }
1095
1096   return SVN_NO_ERROR;
1097 }
1098
1099 /* Write the format number and maximum number of files per directory
1100    to a new format file in PATH, possibly expecting to overwrite a
1101    previously existing file.
1102
1103    Use POOL for temporary allocation. */
1104 static svn_error_t *
1105 write_format(const char *path, int format, int max_files_per_dir,
1106              svn_boolean_t overwrite, apr_pool_t *pool)
1107 {
1108   svn_stringbuf_t *sb;
1109
1110   SVN_ERR_ASSERT(1 <= format && format <= SVN_FS_FS__FORMAT_NUMBER);
1111
1112   sb = svn_stringbuf_createf(pool, "%d\n", format);
1113
1114   if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
1115     {
1116       if (max_files_per_dir)
1117         svn_stringbuf_appendcstr(sb, apr_psprintf(pool, "layout sharded %d\n",
1118                                                   max_files_per_dir));
1119       else
1120         svn_stringbuf_appendcstr(sb, "layout linear\n");
1121     }
1122
1123   /* svn_io_write_version_file() does a load of magic to allow it to
1124      replace version files that already exist.  We only need to do
1125      that when we're allowed to overwrite an existing file. */
1126   if (! overwrite)
1127     {
1128       /* Create the file */
1129       SVN_ERR(svn_io_file_create(path, sb->data, pool));
1130     }
1131   else
1132     {
1133       const char *path_tmp;
1134
1135       SVN_ERR(svn_io_write_unique(&path_tmp,
1136                                   svn_dirent_dirname(path, pool),
1137                                   sb->data, sb->len,
1138                                   svn_io_file_del_none, pool));
1139
1140       /* rename the temp file as the real destination */
1141       SVN_ERR(svn_io_file_rename(path_tmp, path, pool));
1142     }
1143
1144   /* And set the perms to make it read only */
1145   return svn_io_set_file_read_only(path, FALSE, pool);
1146 }
1147
1148 svn_boolean_t
1149 svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs)
1150 {
1151   fs_fs_data_t *ffd = fs->fsap_data;
1152   return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
1153 }
1154
1155 /* Read the configuration information of the file system at FS_PATH
1156  * and set the respective values in FFD.  Use POOL for allocations.
1157  */
1158 static svn_error_t *
1159 read_config(fs_fs_data_t *ffd,
1160             const char *fs_path,
1161             apr_pool_t *pool)
1162 {
1163   SVN_ERR(svn_config_read3(&ffd->config,
1164                            svn_dirent_join(fs_path, PATH_CONFIG, pool),
1165                            FALSE, FALSE, FALSE, pool));
1166
1167   /* Initialize ffd->rep_sharing_allowed. */
1168   if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
1169     SVN_ERR(svn_config_get_bool(ffd->config, &ffd->rep_sharing_allowed,
1170                                 CONFIG_SECTION_REP_SHARING,
1171                                 CONFIG_OPTION_ENABLE_REP_SHARING, TRUE));
1172   else
1173     ffd->rep_sharing_allowed = FALSE;
1174
1175   /* Initialize deltification settings in ffd. */
1176   if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
1177     {
1178       SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_directories,
1179                                   CONFIG_SECTION_DELTIFICATION,
1180                                   CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
1181                                   FALSE));
1182       SVN_ERR(svn_config_get_bool(ffd->config, &ffd->deltify_properties,
1183                                   CONFIG_SECTION_DELTIFICATION,
1184                                   CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION,
1185                                   FALSE));
1186       SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_deltification_walk,
1187                                    CONFIG_SECTION_DELTIFICATION,
1188                                    CONFIG_OPTION_MAX_DELTIFICATION_WALK,
1189                                    SVN_FS_FS_MAX_DELTIFICATION_WALK));
1190       SVN_ERR(svn_config_get_int64(ffd->config, &ffd->max_linear_deltification,
1191                                    CONFIG_SECTION_DELTIFICATION,
1192                                    CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
1193                                    SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
1194     }
1195   else
1196     {
1197       ffd->deltify_directories = FALSE;
1198       ffd->deltify_properties = FALSE;
1199       ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
1200       ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
1201     }
1202
1203   /* Initialize revprop packing settings in ffd. */
1204   if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
1205     {
1206       SVN_ERR(svn_config_get_bool(ffd->config, &ffd->compress_packed_revprops,
1207                                   CONFIG_SECTION_PACKED_REVPROPS,
1208                                   CONFIG_OPTION_COMPRESS_PACKED_REVPROPS,
1209                                   FALSE));
1210       SVN_ERR(svn_config_get_int64(ffd->config, &ffd->revprop_pack_size,
1211                                    CONFIG_SECTION_PACKED_REVPROPS,
1212                                    CONFIG_OPTION_REVPROP_PACK_SIZE,
1213                                    ffd->compress_packed_revprops
1214                                        ? 0x100
1215                                        : 0x40));
1216
1217       ffd->revprop_pack_size *= 1024;
1218     }
1219   else
1220     {
1221       ffd->revprop_pack_size = 0x10000;
1222       ffd->compress_packed_revprops = FALSE;
1223     }
1224
1225   return SVN_NO_ERROR;
1226 }
1227
1228 static svn_error_t *
1229 write_config(svn_fs_t *fs,
1230              apr_pool_t *pool)
1231 {
1232 #define NL APR_EOL_STR
1233   static const char * const fsfs_conf_contents =
1234 "### This file controls the configuration of the FSFS filesystem."           NL
1235 ""                                                                           NL
1236 "[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]"                          NL
1237 "### These options name memcached servers used to cache internal FSFS"       NL
1238 "### data.  See http://www.danga.com/memcached/ for more information on"     NL
1239 "### memcached.  To use memcached with FSFS, run one or more memcached"      NL
1240 "### servers, and specify each of them as an option like so:"                NL
1241 "# first-server = 127.0.0.1:11211"                                           NL
1242 "# remote-memcached = mymemcached.corp.example.com:11212"                    NL
1243 "### The option name is ignored; the value is of the form HOST:PORT."        NL
1244 "### memcached servers can be shared between multiple repositories;"         NL
1245 "### however, if you do this, you *must* ensure that repositories have"      NL
1246 "### distinct UUIDs and paths, or else cached data from one repository"      NL
1247 "### might be used by another accidentally.  Note also that memcached has"   NL
1248 "### no authentication for reads or writes, so you must ensure that your"    NL
1249 "### memcached servers are only accessible by trusted users."                NL
1250 ""                                                                           NL
1251 "[" CONFIG_SECTION_CACHES "]"                                                NL
1252 "### When a cache-related error occurs, normally Subversion ignores it"      NL
1253 "### and continues, logging an error if the server is appropriately"         NL
1254 "### configured (and ignoring it with file:// access).  To make"             NL
1255 "### Subversion never ignore cache errors, uncomment this line."             NL
1256 "# " CONFIG_OPTION_FAIL_STOP " = true"                                       NL
1257 ""                                                                           NL
1258 "[" CONFIG_SECTION_REP_SHARING "]"                                           NL
1259 "### To conserve space, the filesystem can optionally avoid storing"         NL
1260 "### duplicate representations.  This comes at a slight cost in"             NL
1261 "### performance, as maintaining a database of shared representations can"   NL
1262 "### increase commit times.  The space savings are dependent upon the size"  NL
1263 "### of the repository, the number of objects it contains and the amount of" NL
1264 "### duplication between them, usually a function of the branching and"      NL
1265 "### merging process."                                                       NL
1266 "###"                                                                        NL
1267 "### The following parameter enables rep-sharing in the repository.  It can" NL
1268 "### be switched on and off at will, but for best space-saving results"      NL
1269 "### should be enabled consistently over the life of the repository."        NL
1270 "### 'svnadmin verify' will check the rep-cache regardless of this setting." NL
1271 "### rep-sharing is enabled by default."                                     NL
1272 "# " CONFIG_OPTION_ENABLE_REP_SHARING " = true"                              NL
1273 ""                                                                           NL
1274 "[" CONFIG_SECTION_DELTIFICATION "]"                                         NL
1275 "### To conserve space, the filesystem stores data as differences against"   NL
1276 "### existing representations.  This comes at a slight cost in performance," NL
1277 "### as calculating differences can increase commit times.  Reading data"    NL
1278 "### will also create higher CPU load and the data will be fragmented."      NL
1279 "### Since deltification tends to save significant amounts of disk space,"   NL
1280 "### the overall I/O load can actually be lower."                            NL
1281 "###"                                                                        NL
1282 "### The options in this section allow for tuning the deltification"         NL
1283 "### strategy.  Their effects on data size and server performance may vary"  NL
1284 "### from one repository to another.  Versions prior to 1.8 will ignore"     NL
1285 "### this section."                                                          NL
1286 "###"                                                                        NL
1287 "### The following parameter enables deltification for directories. It can"  NL
1288 "### be switched on and off at will, but for best space-saving results"      NL
1289 "### should be enabled consistently over the life of the repository."        NL
1290 "### Repositories containing large directories will benefit greatly."        NL
1291 "### In rarely read repositories, the I/O overhead may be significant as"    NL
1292 "### cache hit rates will most likely be low"                                NL
1293 "### directory deltification is disabled by default."                        NL
1294 "# " CONFIG_OPTION_ENABLE_DIR_DELTIFICATION " = false"                       NL
1295 "###"                                                                        NL
1296 "### The following parameter enables deltification for properties on files"  NL
1297 "### and directories.  Overall, this is a minor tuning option but can save"  NL
1298 "### some disk space if you merge frequently or frequently change node"      NL
1299 "### properties.  You should not activate this if rep-sharing has been"      NL
1300 "### disabled because this may result in a net increase in repository size." NL
1301 "### property deltification is disabled by default."                         NL
1302 "# " CONFIG_OPTION_ENABLE_PROPS_DELTIFICATION " = false"                     NL
1303 "###"                                                                        NL
1304 "### During commit, the server may need to walk the whole change history of" NL
1305 "### of a given node to find a suitable deltification base.  This linear"    NL
1306 "### process can impact commit times, svnadmin load and similar operations." NL
1307 "### This setting limits the depth of the deltification history.  If the"    NL
1308 "### threshold has been reached, the node will be stored as fulltext and a"  NL
1309 "### new deltification history begins."                                      NL
1310 "### Note, this is unrelated to svn log."                                    NL
1311 "### Very large values rarely provide significant additional savings but"    NL
1312 "### can impact performance greatly - in particular if directory"            NL
1313 "### deltification has been activated.  Very small values may be useful in"  NL
1314 "### repositories that are dominated by large, changing binaries."           NL
1315 "### Should be a power of two minus 1.  A value of 0 will effectively"       NL
1316 "### disable deltification."                                                 NL
1317 "### For 1.8, the default value is 1023; earlier versions have no limit."    NL
1318 "# " CONFIG_OPTION_MAX_DELTIFICATION_WALK " = 1023"                          NL
1319 "###"                                                                        NL
1320 "### The skip-delta scheme used by FSFS tends to repeatably store redundant" NL
1321 "### delta information where a simple delta against the latest version is"   NL
1322 "### often smaller.  By default, 1.8+ will therefore use skip deltas only"   NL
1323 "### after the linear chain of deltas has grown beyond the threshold"        NL
1324 "### specified by this setting."                                             NL
1325 "### Values up to 64 can result in some reduction in repository size for"    NL
1326 "### the cost of quickly increasing I/O and CPU costs. Similarly, smaller"   NL
1327 "### numbers can reduce those costs at the cost of more disk space.  For"    NL
1328 "### rarely read repositories or those containing larger binaries, this may" NL
1329 "### present a better trade-off."                                            NL
1330 "### Should be a power of two.  A value of 1 or smaller will cause the"      NL
1331 "### exclusive use of skip-deltas (as in pre-1.8)."                          NL
1332 "### For 1.8, the default value is 16; earlier versions use 1."              NL
1333 "# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16"                          NL
1334 ""                                                                           NL
1335 "[" CONFIG_SECTION_PACKED_REVPROPS "]"                                       NL
1336 "### This parameter controls the size (in kBytes) of packed revprop files."  NL
1337 "### Revprops of consecutive revisions will be concatenated into a single"   NL
1338 "### file up to but not exceeding the threshold given here.  However, each"  NL
1339 "### pack file may be much smaller and revprops of a single revision may be" NL
1340 "### much larger than the limit set here.  The threshold will be applied"    NL
1341 "### before optional compression takes place."                               NL
1342 "### Large values will reduce disk space usage at the expense of increased"  NL
1343 "### latency and CPU usage reading and changing individual revprops.  They"  NL
1344 "### become an advantage when revprop caching has been enabled because a"    NL
1345 "### lot of data can be read in one go.  Values smaller than 4 kByte will"   NL
1346 "### not improve latency any further and quickly render revprop packing"     NL
1347 "### ineffective."                                                           NL
1348 "### revprop-pack-size is 64 kBytes by default for non-compressed revprop"   NL
1349 "### pack files and 256 kBytes when compression has been enabled."           NL
1350 "# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 64"                                 NL
1351 "###"                                                                        NL
1352 "### To save disk space, packed revprop files may be compressed.  Standard"  NL
1353 "### revprops tend to allow for very effective compression.  Reading and"    NL
1354 "### even more so writing, become significantly more CPU intensive.  With"   NL
1355 "### revprop caching enabled, the overhead can be offset by reduced I/O"     NL
1356 "### unless you often modify revprops after packing."                        NL
1357 "### Compressing packed revprops is disabled by default."                    NL
1358 "# " CONFIG_OPTION_COMPRESS_PACKED_REVPROPS " = false"                       NL
1359 ;
1360 #undef NL
1361   return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1362                             fsfs_conf_contents, pool);
1363 }
1364
1365 static svn_error_t *
1366 read_min_unpacked_rev(svn_revnum_t *min_unpacked_rev,
1367                       const char *path,
1368                       apr_pool_t *pool)
1369 {
1370   char buf[80];
1371   apr_file_t *file;
1372   apr_size_t len;
1373
1374   SVN_ERR(svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
1375                            APR_OS_DEFAULT, pool));
1376   len = sizeof(buf);
1377   SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
1378   SVN_ERR(svn_io_file_close(file, pool));
1379
1380   *min_unpacked_rev = SVN_STR_TO_REV(buf);
1381   return SVN_NO_ERROR;
1382 }
1383
1384 static svn_error_t *
1385 update_min_unpacked_rev(svn_fs_t *fs, apr_pool_t *pool)
1386 {
1387   fs_fs_data_t *ffd = fs->fsap_data;
1388
1389   SVN_ERR_ASSERT(ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT);
1390
1391   return read_min_unpacked_rev(&ffd->min_unpacked_rev,
1392                                path_min_unpacked_rev(fs, pool),
1393                                pool);
1394 }
1395
1396 svn_error_t *
1397 svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
1398 {
1399   fs_fs_data_t *ffd = fs->fsap_data;
1400   apr_file_t *uuid_file;
1401   int format, max_files_per_dir;
1402   char buf[APR_UUID_FORMATTED_LENGTH + 2];
1403   apr_size_t limit;
1404
1405   fs->path = apr_pstrdup(fs->pool, path);
1406
1407   /* Read the FS format number. */
1408   SVN_ERR(read_format(&format, &max_files_per_dir,
1409                       path_format(fs, pool), pool));
1410
1411   /* Now we've got a format number no matter what. */
1412   ffd->format = format;
1413   ffd->max_files_per_dir = max_files_per_dir;
1414
1415   /* Read in and cache the repository uuid. */
1416   SVN_ERR(svn_io_file_open(&uuid_file, path_uuid(fs, pool),
1417                            APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
1418
1419   limit = sizeof(buf);
1420   SVN_ERR(svn_io_read_length_line(uuid_file, buf, &limit, pool));
1421   fs->uuid = apr_pstrdup(fs->pool, buf);
1422
1423   SVN_ERR(svn_io_file_close(uuid_file, pool));
1424
1425   /* Read the min unpacked revision. */
1426   if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1427     SVN_ERR(update_min_unpacked_rev(fs, pool));
1428
1429   /* Read the configuration file. */
1430   SVN_ERR(read_config(ffd, fs->path, pool));
1431
1432   return get_youngest(&(ffd->youngest_rev_cache), path, pool);
1433 }
1434
1435 /* Wrapper around svn_io_file_create which ignores EEXIST. */
1436 static svn_error_t *
1437 create_file_ignore_eexist(const char *file,
1438                           const char *contents,
1439                           apr_pool_t *pool)
1440 {
1441   svn_error_t *err = svn_io_file_create(file, contents, pool);
1442   if (err && APR_STATUS_IS_EEXIST(err->apr_err))
1443     {
1444       svn_error_clear(err);
1445       err = SVN_NO_ERROR;
1446     }
1447   return svn_error_trace(err);
1448 }
1449
1450 /* forward declarations */
1451
1452 static svn_error_t *
1453 pack_revprops_shard(const char *pack_file_dir,
1454                     const char *shard_path,
1455                     apr_int64_t shard,
1456                     int max_files_per_dir,
1457                     apr_off_t max_pack_size,
1458                     int compression_level,
1459                     svn_cancel_func_t cancel_func,
1460                     void *cancel_baton,
1461                     apr_pool_t *scratch_pool);
1462
1463 static svn_error_t *
1464 delete_revprops_shard(const char *shard_path,
1465                       apr_int64_t shard,
1466                       int max_files_per_dir,
1467                       svn_cancel_func_t cancel_func,
1468                       void *cancel_baton,
1469                       apr_pool_t *scratch_pool);
1470
1471 /* In the filesystem FS, pack all revprop shards up to min_unpacked_rev.
1472  * 
1473  * NOTE: Keep the old non-packed shards around until after the format bump.
1474  * Otherwise, re-running upgrade will drop the packed revprop shard but
1475  * have no unpacked data anymore.  Call upgrade_cleanup_pack_revprops after
1476  * the bump.
1477  * 
1478  * Use SCRATCH_POOL for temporary allocations.
1479  */
1480 static svn_error_t *
1481 upgrade_pack_revprops(svn_fs_t *fs,
1482                       apr_pool_t *scratch_pool)
1483 {
1484   fs_fs_data_t *ffd = fs->fsap_data;
1485   const char *revprops_shard_path;
1486   const char *revprops_pack_file_dir;
1487   apr_int64_t shard;
1488   apr_int64_t first_unpacked_shard
1489     =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
1490
1491   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1492   const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
1493                                               scratch_pool);
1494   int compression_level = ffd->compress_packed_revprops
1495                            ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
1496                            : SVN_DELTA_COMPRESSION_LEVEL_NONE;
1497
1498   /* first, pack all revprops shards to match the packed revision shards */
1499   for (shard = 0; shard < first_unpacked_shard; ++shard)
1500     {
1501       revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
1502                    apr_psprintf(iterpool,
1503                                 "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
1504                                 shard),
1505                    iterpool);
1506       revprops_shard_path = svn_dirent_join(revsprops_dir,
1507                        apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
1508                        iterpool);
1509
1510       SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
1511                                   shard, ffd->max_files_per_dir,
1512                                   (int)(0.9 * ffd->revprop_pack_size),
1513                                   compression_level,
1514                                   NULL, NULL, iterpool));
1515       svn_pool_clear(iterpool);
1516     }
1517
1518   svn_pool_destroy(iterpool);
1519
1520   return SVN_NO_ERROR;
1521 }
1522
1523 /* In the filesystem FS, remove all non-packed revprop shards up to
1524  * min_unpacked_rev.  Use SCRATCH_POOL for temporary allocations.
1525  * See upgrade_pack_revprops for more info.
1526  */
1527 static svn_error_t *
1528 upgrade_cleanup_pack_revprops(svn_fs_t *fs,
1529                               apr_pool_t *scratch_pool)
1530 {
1531   fs_fs_data_t *ffd = fs->fsap_data;
1532   const char *revprops_shard_path;
1533   apr_int64_t shard;
1534   apr_int64_t first_unpacked_shard
1535     =  ffd->min_unpacked_rev / ffd->max_files_per_dir;
1536
1537   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1538   const char *revsprops_dir = svn_dirent_join(fs->path, PATH_REVPROPS_DIR,
1539                                               scratch_pool);
1540   
1541   /* delete the non-packed revprops shards afterwards */
1542   for (shard = 0; shard < first_unpacked_shard; ++shard)
1543     {
1544       revprops_shard_path = svn_dirent_join(revsprops_dir,
1545                        apr_psprintf(iterpool, "%" APR_INT64_T_FMT, shard),
1546                        iterpool);
1547       SVN_ERR(delete_revprops_shard(revprops_shard_path,
1548                                     shard, ffd->max_files_per_dir,
1549                                     NULL, NULL, iterpool));
1550       svn_pool_clear(iterpool);
1551     }
1552
1553   svn_pool_destroy(iterpool);
1554
1555   return SVN_NO_ERROR;
1556 }
1557
1558 static svn_error_t *
1559 upgrade_body(void *baton, apr_pool_t *pool)
1560 {
1561   svn_fs_t *fs = baton;
1562   int format, max_files_per_dir;
1563   const char *format_path = path_format(fs, pool);
1564   svn_node_kind_t kind;
1565   svn_boolean_t needs_revprop_shard_cleanup = FALSE;
1566
1567   /* Read the FS format number and max-files-per-dir setting. */
1568   SVN_ERR(read_format(&format, &max_files_per_dir, format_path, pool));
1569
1570   /* If the config file does not exist, create one. */
1571   SVN_ERR(svn_io_check_path(svn_dirent_join(fs->path, PATH_CONFIG, pool),
1572                             &kind, pool));
1573   switch (kind)
1574     {
1575     case svn_node_none:
1576       SVN_ERR(write_config(fs, pool));
1577       break;
1578     case svn_node_file:
1579       break;
1580     default:
1581       return svn_error_createf(SVN_ERR_FS_GENERAL, NULL,
1582                                _("'%s' is not a regular file."
1583                                  " Please move it out of "
1584                                  "the way and try again"),
1585                                svn_dirent_join(fs->path, PATH_CONFIG, pool));
1586     }
1587
1588   /* If we're already up-to-date, there's nothing else to be done here. */
1589   if (format == SVN_FS_FS__FORMAT_NUMBER)
1590     return SVN_NO_ERROR;
1591
1592   /* If our filesystem predates the existance of the 'txn-current
1593      file', make that file and its corresponding lock file. */
1594   if (format < SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1595     {
1596       SVN_ERR(create_file_ignore_eexist(path_txn_current(fs, pool), "0\n",
1597                                         pool));
1598       SVN_ERR(create_file_ignore_eexist(path_txn_current_lock(fs, pool), "",
1599                                         pool));
1600     }
1601
1602   /* If our filesystem predates the existance of the 'txn-protorevs'
1603      dir, make that directory.  */
1604   if (format < SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1605     {
1606       /* We don't use path_txn_proto_rev() here because it expects
1607          we've already bumped our format. */
1608       SVN_ERR(svn_io_make_dir_recursively(
1609           svn_dirent_join(fs->path, PATH_TXN_PROTOS_DIR, pool), pool));
1610     }
1611
1612   /* If our filesystem is new enough, write the min unpacked rev file. */
1613   if (format < SVN_FS_FS__MIN_PACKED_FORMAT)
1614     SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
1615
1616   /* If the file system supports revision packing but not revprop packing
1617      *and* the FS has been sharded, pack the revprops up to the point that
1618      revision data has been packed.  However, keep the non-packed revprop
1619      files around until after the format bump */
1620   if (   format >= SVN_FS_FS__MIN_PACKED_FORMAT
1621       && format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
1622       && max_files_per_dir > 0)
1623     {
1624       needs_revprop_shard_cleanup = TRUE;
1625       SVN_ERR(upgrade_pack_revprops(fs, pool));
1626     }
1627
1628   /* Bump the format file. */
1629   SVN_ERR(write_format(format_path, SVN_FS_FS__FORMAT_NUMBER,
1630                        max_files_per_dir, TRUE, pool));
1631
1632   /* Now, it is safe to remove the redundant revprop files. */
1633   if (needs_revprop_shard_cleanup)
1634     SVN_ERR(upgrade_cleanup_pack_revprops(fs, pool));
1635
1636   /* Done */
1637   return SVN_NO_ERROR;
1638 }
1639
1640
1641 svn_error_t *
1642 svn_fs_fs__upgrade(svn_fs_t *fs, apr_pool_t *pool)
1643 {
1644   return svn_fs_fs__with_write_lock(fs, upgrade_body, (void *)fs, pool);
1645 }
1646
1647
1648 /* Functions for dealing with recoverable errors on mutable files
1649  *
1650  * Revprops, current, and txn-current files are mutable; that is, they
1651  * change as part of normal fsfs operation, in constrat to revs files, or
1652  * the format file, which are written once at create (or upgrade) time.
1653  * When more than one host writes to the same repository, we will
1654  * sometimes see these recoverable errors when accesssing these files.
1655  *
1656  * These errors all relate to NFS, and thus we only use this retry code if
1657  * ESTALE is defined.
1658  *
1659  ** ESTALE
1660  *
1661  * In NFS v3 and under, the server doesn't track opened files.  If you
1662  * unlink(2) or rename(2) a file held open by another process *on the
1663  * same host*, that host's kernel typically renames the file to
1664  * .nfsXXXX and automatically deletes that when it's no longer open,
1665  * but this behavior is not required.
1666  *
1667  * For obvious reasons, this does not work *across hosts*.  No one
1668  * knows about the opened file; not the server, and not the deleting
1669  * client.  So the file vanishes, and the reader gets stale NFS file
1670  * handle.
1671  *
1672  ** EIO, ENOENT
1673  *
1674  * Some client implementations (at least the 2.6.18.5 kernel that ships
1675  * with Ubuntu Dapper) sometimes give spurious ENOENT (only on open) or
1676  * even EIO errors when trying to read these files that have been renamed
1677  * over on some other host.
1678  *
1679  ** Solution
1680  *
1681  * Try open and read of such files in try_stringbuf_from_file().  Call
1682  * this function within a loop of RECOVERABLE_RETRY_COUNT iterations
1683  * (though, realistically, the second try will succeed).
1684  */
1685
1686 #define RECOVERABLE_RETRY_COUNT 10
1687
1688 /* Read the file at PATH and return its content in *CONTENT. *CONTENT will
1689  * not be modified unless the whole file was read successfully.
1690  *
1691  * ESTALE, EIO and ENOENT will not cause this function to return an error
1692  * unless LAST_ATTEMPT has been set.  If MISSING is not NULL, indicate
1693  * missing files (ENOENT) there.
1694  *
1695  * Use POOL for allocations.
1696  */
1697 static svn_error_t *
1698 try_stringbuf_from_file(svn_stringbuf_t **content,
1699                         svn_boolean_t *missing,
1700                         const char *path,
1701                         svn_boolean_t last_attempt,
1702                         apr_pool_t *pool)
1703 {
1704   svn_error_t *err = svn_stringbuf_from_file2(content, path, pool);
1705   if (missing)
1706     *missing = FALSE;
1707
1708   if (err)
1709     {
1710       *content = NULL;
1711
1712       if (APR_STATUS_IS_ENOENT(err->apr_err))
1713         {
1714           if (!last_attempt)
1715             {
1716               svn_error_clear(err);
1717               if (missing)
1718                 *missing = TRUE;
1719               return SVN_NO_ERROR;
1720             }
1721         }
1722 #ifdef ESTALE
1723       else if (APR_TO_OS_ERROR(err->apr_err) == ESTALE
1724                 || APR_TO_OS_ERROR(err->apr_err) == EIO)
1725         {
1726           if (!last_attempt)
1727             {
1728               svn_error_clear(err);
1729               return SVN_NO_ERROR;
1730             }
1731         }
1732 #endif
1733     }
1734
1735   return svn_error_trace(err);
1736 }
1737
1738 /* Read the 'current' file FNAME and store the contents in *BUF.
1739    Allocations are performed in POOL. */
1740 static svn_error_t *
1741 read_content(svn_stringbuf_t **content, const char *fname, apr_pool_t *pool)
1742 {
1743   int i;
1744   *content = NULL;
1745
1746   for (i = 0; !*content && (i < RECOVERABLE_RETRY_COUNT); ++i)
1747     SVN_ERR(try_stringbuf_from_file(content, NULL,
1748                                     fname, i + 1 < RECOVERABLE_RETRY_COUNT,
1749                                     pool));
1750
1751   if (!*content)
1752     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1753                              _("Can't read '%s'"),
1754                              svn_dirent_local_style(fname, pool));
1755
1756   return SVN_NO_ERROR;
1757 }
1758
1759 /* Find the youngest revision in a repository at path FS_PATH and
1760    return it in *YOUNGEST_P.  Perform temporary allocations in
1761    POOL. */
1762 static svn_error_t *
1763 get_youngest(svn_revnum_t *youngest_p,
1764              const char *fs_path,
1765              apr_pool_t *pool)
1766 {
1767   svn_stringbuf_t *buf;
1768   SVN_ERR(read_content(&buf, svn_dirent_join(fs_path, PATH_CURRENT, pool),
1769                        pool));
1770
1771   *youngest_p = SVN_STR_TO_REV(buf->data);
1772
1773   return SVN_NO_ERROR;
1774 }
1775
1776
1777 svn_error_t *
1778 svn_fs_fs__youngest_rev(svn_revnum_t *youngest_p,
1779                         svn_fs_t *fs,
1780                         apr_pool_t *pool)
1781 {
1782   fs_fs_data_t *ffd = fs->fsap_data;
1783
1784   SVN_ERR(get_youngest(youngest_p, fs->path, pool));
1785   ffd->youngest_rev_cache = *youngest_p;
1786
1787   return SVN_NO_ERROR;
1788 }
1789
1790 /* Given a revision file FILE that has been pre-positioned at the
1791    beginning of a Node-Rev header block, read in that header block and
1792    store it in the apr_hash_t HEADERS.  All allocations will be from
1793    POOL. */
1794 static svn_error_t * read_header_block(apr_hash_t **headers,
1795                                        svn_stream_t *stream,
1796                                        apr_pool_t *pool)
1797 {
1798   *headers = apr_hash_make(pool);
1799
1800   while (1)
1801     {
1802       svn_stringbuf_t *header_str;
1803       const char *name, *value;
1804       apr_size_t i = 0;
1805       svn_boolean_t eof;
1806
1807       SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
1808
1809       if (eof || header_str->len == 0)
1810         break; /* end of header block */
1811
1812       while (header_str->data[i] != ':')
1813         {
1814           if (header_str->data[i] == '\0')
1815             return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1816                                      _("Found malformed header '%s' in "
1817                                        "revision file"),
1818                                      header_str->data);
1819           i++;
1820         }
1821
1822       /* Create a 'name' string and point to it. */
1823       header_str->data[i] = '\0';
1824       name = header_str->data;
1825
1826       /* Skip over the NULL byte and the space following it. */
1827       i += 2;
1828
1829       if (i > header_str->len)
1830         {
1831           /* Restore the original line for the error. */
1832           i -= 2;
1833           header_str->data[i] = ':';
1834           return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
1835                                    _("Found malformed header '%s' in "
1836                                      "revision file"),
1837                                    header_str->data);
1838         }
1839
1840       value = header_str->data + i;
1841
1842       /* header_str is safely in our pool, so we can use bits of it as
1843          key and value. */
1844       svn_hash_sets(*headers, name, value);
1845     }
1846
1847   return SVN_NO_ERROR;
1848 }
1849
1850 /* Return SVN_ERR_FS_NO_SUCH_REVISION if the given revision is newer
1851    than the current youngest revision or is simply not a valid
1852    revision number, else return success.
1853
1854    FSFS is based around the concept that commits only take effect when
1855    the number in "current" is bumped.  Thus if there happens to be a rev
1856    or revprops file installed for a revision higher than the one recorded
1857    in "current" (because a commit failed between installing the rev file
1858    and bumping "current", or because an administrator rolled back the
1859    repository by resetting "current" without deleting rev files, etc), it
1860    ought to be completely ignored.  This function provides the check
1861    by which callers can make that decision. */
1862 static svn_error_t *
1863 ensure_revision_exists(svn_fs_t *fs,
1864                        svn_revnum_t rev,
1865                        apr_pool_t *pool)
1866 {
1867   fs_fs_data_t *ffd = fs->fsap_data;
1868
1869   if (! SVN_IS_VALID_REVNUM(rev))
1870     return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1871                              _("Invalid revision number '%ld'"), rev);
1872
1873
1874   /* Did the revision exist the last time we checked the current
1875      file? */
1876   if (rev <= ffd->youngest_rev_cache)
1877     return SVN_NO_ERROR;
1878
1879   SVN_ERR(get_youngest(&(ffd->youngest_rev_cache), fs->path, pool));
1880
1881   /* Check again. */
1882   if (rev <= ffd->youngest_rev_cache)
1883     return SVN_NO_ERROR;
1884
1885   return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1886                            _("No such revision %ld"), rev);
1887 }
1888
1889 svn_error_t *
1890 svn_fs_fs__revision_exists(svn_revnum_t rev,
1891                            svn_fs_t *fs,
1892                            apr_pool_t *pool)
1893 {
1894   /* Different order of parameters. */
1895   SVN_ERR(ensure_revision_exists(fs, rev, pool));
1896   return SVN_NO_ERROR;
1897 }
1898
1899 /* Open the correct revision file for REV.  If the filesystem FS has
1900    been packed, *FILE will be set to the packed file; otherwise, set *FILE
1901    to the revision file for REV.  Return SVN_ERR_FS_NO_SUCH_REVISION if the
1902    file doesn't exist.
1903
1904    TODO: Consider returning an indication of whether this is a packed rev
1905          file, so the caller need not rely on is_packed_rev() which in turn
1906          relies on the cached FFD->min_unpacked_rev value not having changed
1907          since the rev file was opened.
1908
1909    Use POOL for allocations. */
1910 static svn_error_t *
1911 open_pack_or_rev_file(apr_file_t **file,
1912                       svn_fs_t *fs,
1913                       svn_revnum_t rev,
1914                       apr_pool_t *pool)
1915 {
1916   fs_fs_data_t *ffd = fs->fsap_data;
1917   svn_error_t *err;
1918   const char *path;
1919   svn_boolean_t retry = FALSE;
1920
1921   do
1922     {
1923       err = svn_fs_fs__path_rev_absolute(&path, fs, rev, pool);
1924
1925       /* open the revision file in buffered r/o mode */
1926       if (! err)
1927         err = svn_io_file_open(file, path,
1928                               APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
1929
1930       if (err && APR_STATUS_IS_ENOENT(err->apr_err))
1931         {
1932           if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
1933             {
1934               /* Could not open the file. This may happen if the
1935                * file once existed but got packed later. */
1936               svn_error_clear(err);
1937
1938               /* if that was our 2nd attempt, leave it at that. */
1939               if (retry)
1940                 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1941                                          _("No such revision %ld"), rev);
1942
1943               /* We failed for the first time. Refresh cache & retry. */
1944               SVN_ERR(update_min_unpacked_rev(fs, pool));
1945
1946               retry = TRUE;
1947             }
1948           else
1949             {
1950               svn_error_clear(err);
1951               return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1952                                        _("No such revision %ld"), rev);
1953             }
1954         }
1955       else
1956         {
1957           retry = FALSE;
1958         }
1959     }
1960   while (retry);
1961
1962   return svn_error_trace(err);
1963 }
1964
1965 /* Reads a line from STREAM and converts it to a 64 bit integer to be
1966  * returned in *RESULT.  If we encounter eof, set *HIT_EOF and leave
1967  * *RESULT unchanged.  If HIT_EOF is NULL, EOF causes an "corrupt FS"
1968  * error return.
1969  * SCRATCH_POOL is used for temporary allocations.
1970  */
1971 static svn_error_t *
1972 read_number_from_stream(apr_int64_t *result,
1973                         svn_boolean_t *hit_eof,
1974                         svn_stream_t *stream,
1975                         apr_pool_t *scratch_pool)
1976 {
1977   svn_stringbuf_t *sb;
1978   svn_boolean_t eof;
1979   svn_error_t *err;
1980
1981   SVN_ERR(svn_stream_readline(stream, &sb, "\n", &eof, scratch_pool));
1982   if (hit_eof)
1983     *hit_eof = eof;
1984   else
1985     if (eof)
1986       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL, _("Unexpected EOF"));
1987
1988   if (!eof)
1989     {
1990       err = svn_cstring_atoi64(result, sb->data);
1991       if (err)
1992         return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
1993                                  _("Number '%s' invalid or too large"),
1994                                  sb->data);
1995     }
1996
1997   return SVN_NO_ERROR;
1998 }
1999
2000 /* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
2001    Use POOL for temporary allocations. */
2002 static svn_error_t *
2003 get_packed_offset(apr_off_t *rev_offset,
2004                   svn_fs_t *fs,
2005                   svn_revnum_t rev,
2006                   apr_pool_t *pool)
2007 {
2008   fs_fs_data_t *ffd = fs->fsap_data;
2009   svn_stream_t *manifest_stream;
2010   svn_boolean_t is_cached;
2011   svn_revnum_t shard;
2012   apr_int64_t shard_pos;
2013   apr_array_header_t *manifest;
2014   apr_pool_t *iterpool;
2015
2016   shard = rev / ffd->max_files_per_dir;
2017
2018   /* position of the shard within the manifest */
2019   shard_pos = rev % ffd->max_files_per_dir;
2020
2021   /* fetch exactly that element into *rev_offset, if the manifest is found
2022      in the cache */
2023   SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached,
2024                                  ffd->packed_offset_cache, &shard,
2025                                  svn_fs_fs__get_sharded_offset, &shard_pos,
2026                                  pool));
2027
2028   if (is_cached)
2029       return SVN_NO_ERROR;
2030
2031   /* Open the manifest file. */
2032   SVN_ERR(svn_stream_open_readonly(&manifest_stream,
2033                                    path_rev_packed(fs, rev, PATH_MANIFEST,
2034                                                    pool),
2035                                    pool, pool));
2036
2037   /* While we're here, let's just read the entire manifest file into an array,
2038      so we can cache the entire thing. */
2039   iterpool = svn_pool_create(pool);
2040   manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t));
2041   while (1)
2042     {
2043       svn_boolean_t eof;
2044       apr_int64_t val;
2045
2046       svn_pool_clear(iterpool);
2047       SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
2048       if (eof)
2049         break;
2050
2051       APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
2052     }
2053   svn_pool_destroy(iterpool);
2054
2055   *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
2056                               apr_off_t);
2057
2058   /* Close up shop and cache the array. */
2059   SVN_ERR(svn_stream_close(manifest_stream));
2060   return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool);
2061 }
2062
2063 /* Open the revision file for revision REV in filesystem FS and store
2064    the newly opened file in FILE.  Seek to location OFFSET before
2065    returning.  Perform temporary allocations in POOL. */
2066 static svn_error_t *
2067 open_and_seek_revision(apr_file_t **file,
2068                        svn_fs_t *fs,
2069                        svn_revnum_t rev,
2070                        apr_off_t offset,
2071                        apr_pool_t *pool)
2072 {
2073   apr_file_t *rev_file;
2074
2075   SVN_ERR(ensure_revision_exists(fs, rev, pool));
2076
2077   SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, pool));
2078
2079   if (is_packed_rev(fs, rev))
2080     {
2081       apr_off_t rev_offset;
2082
2083       SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
2084       offset += rev_offset;
2085     }
2086
2087   SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2088
2089   *file = rev_file;
2090
2091   return SVN_NO_ERROR;
2092 }
2093
2094 /* Open the representation for a node-revision in transaction TXN_ID
2095    in filesystem FS and store the newly opened file in FILE.  Seek to
2096    location OFFSET before returning.  Perform temporary allocations in
2097    POOL.  Only appropriate for file contents, nor props or directory
2098    contents. */
2099 static svn_error_t *
2100 open_and_seek_transaction(apr_file_t **file,
2101                           svn_fs_t *fs,
2102                           const char *txn_id,
2103                           representation_t *rep,
2104                           apr_pool_t *pool)
2105 {
2106   apr_file_t *rev_file;
2107   apr_off_t offset;
2108
2109   SVN_ERR(svn_io_file_open(&rev_file, path_txn_proto_rev(fs, txn_id, pool),
2110                            APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
2111
2112   offset = rep->offset;
2113   SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2114
2115   *file = rev_file;
2116
2117   return SVN_NO_ERROR;
2118 }
2119
2120 /* Given a node-id ID, and a representation REP in filesystem FS, open
2121    the correct file and seek to the correction location.  Store this
2122    file in *FILE_P.  Perform any allocations in POOL. */
2123 static svn_error_t *
2124 open_and_seek_representation(apr_file_t **file_p,
2125                              svn_fs_t *fs,
2126                              representation_t *rep,
2127                              apr_pool_t *pool)
2128 {
2129   if (! rep->txn_id)
2130     return open_and_seek_revision(file_p, fs, rep->revision, rep->offset,
2131                                   pool);
2132   else
2133     return open_and_seek_transaction(file_p, fs, rep->txn_id, rep, pool);
2134 }
2135
2136 /* Parse the description of a representation from STRING and store it
2137    into *REP_P.  If the representation is mutable (the revision is
2138    given as -1), then use TXN_ID for the representation's txn_id
2139    field.  If MUTABLE_REP_TRUNCATED is true, then this representation
2140    is for property or directory contents, and no information will be
2141    expected except the "-1" revision number for a mutable
2142    representation.  Allocate *REP_P in POOL. */
2143 static svn_error_t *
2144 read_rep_offsets_body(representation_t **rep_p,
2145                       char *string,
2146                       const char *txn_id,
2147                       svn_boolean_t mutable_rep_truncated,
2148                       apr_pool_t *pool)
2149 {
2150   representation_t *rep;
2151   char *str;
2152   apr_int64_t val;
2153
2154   rep = apr_pcalloc(pool, sizeof(*rep));
2155   *rep_p = rep;
2156
2157   str = svn_cstring_tokenize(" ", &string);
2158   if (str == NULL)
2159     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2160                             _("Malformed text representation offset line in node-rev"));
2161
2162
2163   rep->revision = SVN_STR_TO_REV(str);
2164   if (rep->revision == SVN_INVALID_REVNUM)
2165     {
2166       rep->txn_id = txn_id;
2167       if (mutable_rep_truncated)
2168         return SVN_NO_ERROR;
2169     }
2170
2171   str = svn_cstring_tokenize(" ", &string);
2172   if (str == NULL)
2173     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2174                             _("Malformed text representation offset line in node-rev"));
2175
2176   SVN_ERR(svn_cstring_atoi64(&val, str));
2177   rep->offset = (apr_off_t)val;
2178
2179   str = svn_cstring_tokenize(" ", &string);
2180   if (str == NULL)
2181     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2182                             _("Malformed text representation offset line in node-rev"));
2183
2184   SVN_ERR(svn_cstring_atoi64(&val, str));
2185   rep->size = (svn_filesize_t)val;
2186
2187   str = svn_cstring_tokenize(" ", &string);
2188   if (str == NULL)
2189     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2190                             _("Malformed text representation offset line in node-rev"));
2191
2192   SVN_ERR(svn_cstring_atoi64(&val, str));
2193   rep->expanded_size = (svn_filesize_t)val;
2194
2195   /* Read in the MD5 hash. */
2196   str = svn_cstring_tokenize(" ", &string);
2197   if ((str == NULL) || (strlen(str) != (APR_MD5_DIGESTSIZE * 2)))
2198     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2199                             _("Malformed text representation offset line in node-rev"));
2200
2201   SVN_ERR(svn_checksum_parse_hex(&rep->md5_checksum, svn_checksum_md5, str,
2202                                  pool));
2203
2204   /* The remaining fields are only used for formats >= 4, so check that. */
2205   str = svn_cstring_tokenize(" ", &string);
2206   if (str == NULL)
2207     return SVN_NO_ERROR;
2208
2209   /* Read the SHA1 hash. */
2210   if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
2211     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2212                             _("Malformed text representation offset line in node-rev"));
2213
2214   SVN_ERR(svn_checksum_parse_hex(&rep->sha1_checksum, svn_checksum_sha1, str,
2215                                  pool));
2216
2217   /* Read the uniquifier. */
2218   str = svn_cstring_tokenize(" ", &string);
2219   if (str == NULL)
2220     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2221                             _("Malformed text representation offset line in node-rev"));
2222
2223   rep->uniquifier = apr_pstrdup(pool, str);
2224
2225   return SVN_NO_ERROR;
2226 }
2227
2228 /* Wrap read_rep_offsets_body(), extracting its TXN_ID from our NODEREV_ID,
2229    and adding an error message. */
2230 static svn_error_t *
2231 read_rep_offsets(representation_t **rep_p,
2232                  char *string,
2233                  const svn_fs_id_t *noderev_id,
2234                  svn_boolean_t mutable_rep_truncated,
2235                  apr_pool_t *pool)
2236 {
2237   svn_error_t *err;
2238   const char *txn_id;
2239
2240   if (noderev_id)
2241     txn_id = svn_fs_fs__id_txn_id(noderev_id);
2242   else
2243     txn_id = NULL;
2244
2245   err = read_rep_offsets_body(rep_p, string, txn_id, mutable_rep_truncated,
2246                               pool);
2247   if (err)
2248     {
2249       const svn_string_t *id_unparsed = svn_fs_fs__id_unparse(noderev_id, pool);
2250       const char *where;
2251       where = apr_psprintf(pool,
2252                            _("While reading representation offsets "
2253                              "for node-revision '%s':"),
2254                            noderev_id ? id_unparsed->data : "(null)");
2255
2256       return svn_error_quick_wrap(err, where);
2257     }
2258   else
2259     return SVN_NO_ERROR;
2260 }
2261
2262 static svn_error_t *
2263 err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
2264 {
2265   svn_string_t *id_str = svn_fs_fs__id_unparse(id, fs->pool);
2266   return svn_error_createf
2267     (SVN_ERR_FS_ID_NOT_FOUND, 0,
2268      _("Reference to non-existent node '%s' in filesystem '%s'"),
2269      id_str->data, fs->path);
2270 }
2271
2272 /* Look up the NODEREV_P for ID in FS' node revsion cache. If noderev
2273  * caching has been enabled and the data can be found, IS_CACHED will
2274  * be set to TRUE. The noderev will be allocated from POOL.
2275  *
2276  * Non-permanent ids (e.g. ids within a TXN) will not be cached.
2277  */
2278 static svn_error_t *
2279 get_cached_node_revision_body(node_revision_t **noderev_p,
2280                               svn_fs_t *fs,
2281                               const svn_fs_id_t *id,
2282                               svn_boolean_t *is_cached,
2283                               apr_pool_t *pool)
2284 {
2285   fs_fs_data_t *ffd = fs->fsap_data;
2286   if (! ffd->node_revision_cache || svn_fs_fs__id_txn_id(id))
2287     {
2288       *is_cached = FALSE;
2289     }
2290   else
2291     {
2292       pair_cache_key_t key = { 0 };
2293
2294       key.revision = svn_fs_fs__id_rev(id);
2295       key.second = svn_fs_fs__id_offset(id);
2296       SVN_ERR(svn_cache__get((void **) noderev_p,
2297                             is_cached,
2298                             ffd->node_revision_cache,
2299                             &key,
2300                             pool));
2301     }
2302
2303   return SVN_NO_ERROR;
2304 }
2305
2306 /* If noderev caching has been enabled, store the NODEREV_P for the given ID
2307  * in FS' node revsion cache. SCRATCH_POOL is used for temporary allcations.
2308  *
2309  * Non-permanent ids (e.g. ids within a TXN) will not be cached.
2310  */
2311 static svn_error_t *
2312 set_cached_node_revision_body(node_revision_t *noderev_p,
2313                               svn_fs_t *fs,
2314                               const svn_fs_id_t *id,
2315                               apr_pool_t *scratch_pool)
2316 {
2317   fs_fs_data_t *ffd = fs->fsap_data;
2318
2319   if (ffd->node_revision_cache && !svn_fs_fs__id_txn_id(id))
2320     {
2321       pair_cache_key_t key = { 0 };
2322
2323       key.revision = svn_fs_fs__id_rev(id);
2324       key.second = svn_fs_fs__id_offset(id);
2325       return svn_cache__set(ffd->node_revision_cache,
2326                             &key,
2327                             noderev_p,
2328                             scratch_pool);
2329     }
2330
2331   return SVN_NO_ERROR;
2332 }
2333
2334 /* Get the node-revision for the node ID in FS.
2335    Set *NODEREV_P to the new node-revision structure, allocated in POOL.
2336    See svn_fs_fs__get_node_revision, which wraps this and adds another
2337    error. */
2338 static svn_error_t *
2339 get_node_revision_body(node_revision_t **noderev_p,
2340                        svn_fs_t *fs,
2341                        const svn_fs_id_t *id,
2342                        apr_pool_t *pool)
2343 {
2344   apr_file_t *revision_file;
2345   svn_error_t *err;
2346   svn_boolean_t is_cached = FALSE;
2347
2348   /* First, try a cache lookup. If that succeeds, we are done here. */
2349   SVN_ERR(get_cached_node_revision_body(noderev_p, fs, id, &is_cached, pool));
2350   if (is_cached)
2351     return SVN_NO_ERROR;
2352
2353   if (svn_fs_fs__id_txn_id(id))
2354     {
2355       /* This is a transaction node-rev. */
2356       err = svn_io_file_open(&revision_file, path_txn_node_rev(fs, id, pool),
2357                              APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
2358     }
2359   else
2360     {
2361       /* This is a revision node-rev. */
2362       err = open_and_seek_revision(&revision_file, fs,
2363                                    svn_fs_fs__id_rev(id),
2364                                    svn_fs_fs__id_offset(id),
2365                                    pool);
2366     }
2367
2368   if (err)
2369     {
2370       if (APR_STATUS_IS_ENOENT(err->apr_err))
2371         {
2372           svn_error_clear(err);
2373           return svn_error_trace(err_dangling_id(fs, id));
2374         }
2375
2376       return svn_error_trace(err);
2377     }
2378
2379   SVN_ERR(svn_fs_fs__read_noderev(noderev_p,
2380                                   svn_stream_from_aprfile2(revision_file, FALSE,
2381                                                            pool),
2382                                   pool));
2383
2384   /* The noderev is not in cache, yet. Add it, if caching has been enabled. */
2385   return set_cached_node_revision_body(*noderev_p, fs, id, pool);
2386 }
2387
2388 svn_error_t *
2389 svn_fs_fs__read_noderev(node_revision_t **noderev_p,
2390                         svn_stream_t *stream,
2391                         apr_pool_t *pool)
2392 {
2393   apr_hash_t *headers;
2394   node_revision_t *noderev;
2395   char *value;
2396   const char *noderev_id;
2397
2398   SVN_ERR(read_header_block(&headers, stream, pool));
2399
2400   noderev = apr_pcalloc(pool, sizeof(*noderev));
2401
2402   /* Read the node-rev id. */
2403   value = svn_hash_gets(headers, HEADER_ID);
2404   if (value == NULL)
2405       /* ### More information: filename/offset coordinates */
2406       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2407                               _("Missing id field in node-rev"));
2408
2409   SVN_ERR(svn_stream_close(stream));
2410
2411   noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
2412   noderev_id = value; /* for error messages later */
2413
2414   /* Read the type. */
2415   value = svn_hash_gets(headers, HEADER_TYPE);
2416
2417   if ((value == NULL) ||
2418       (strcmp(value, KIND_FILE) != 0 && strcmp(value, KIND_DIR)))
2419     /* ### s/kind/type/ */
2420     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2421                              _("Missing kind field in node-rev '%s'"),
2422                              noderev_id);
2423
2424   noderev->kind = (strcmp(value, KIND_FILE) == 0) ? svn_node_file
2425     : svn_node_dir;
2426
2427   /* Read the 'count' field. */
2428   value = svn_hash_gets(headers, HEADER_COUNT);
2429   if (value)
2430     SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, value));
2431   else
2432     noderev->predecessor_count = 0;
2433
2434   /* Get the properties location. */
2435   value = svn_hash_gets(headers, HEADER_PROPS);
2436   if (value)
2437     {
2438       SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
2439                                noderev->id, TRUE, pool));
2440     }
2441
2442   /* Get the data location. */
2443   value = svn_hash_gets(headers, HEADER_TEXT);
2444   if (value)
2445     {
2446       SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
2447                                noderev->id,
2448                                (noderev->kind == svn_node_dir), pool));
2449     }
2450
2451   /* Get the created path. */
2452   value = svn_hash_gets(headers, HEADER_CPATH);
2453   if (value == NULL)
2454     {
2455       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2456                                _("Missing cpath field in node-rev '%s'"),
2457                                noderev_id);
2458     }
2459   else
2460     {
2461       noderev->created_path = apr_pstrdup(pool, value);
2462     }
2463
2464   /* Get the predecessor ID. */
2465   value = svn_hash_gets(headers, HEADER_PRED);
2466   if (value)
2467     noderev->predecessor_id = svn_fs_fs__id_parse(value, strlen(value),
2468                                                   pool);
2469
2470   /* Get the copyroot. */
2471   value = svn_hash_gets(headers, HEADER_COPYROOT);
2472   if (value == NULL)
2473     {
2474       noderev->copyroot_path = apr_pstrdup(pool, noderev->created_path);
2475       noderev->copyroot_rev = svn_fs_fs__id_rev(noderev->id);
2476     }
2477   else
2478     {
2479       char *str;
2480
2481       str = svn_cstring_tokenize(" ", &value);
2482       if (str == NULL)
2483         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2484                                  _("Malformed copyroot line in node-rev '%s'"),
2485                                  noderev_id);
2486
2487       noderev->copyroot_rev = SVN_STR_TO_REV(str);
2488
2489       if (*value == '\0')
2490         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2491                                  _("Malformed copyroot line in node-rev '%s'"),
2492                                  noderev_id);
2493       noderev->copyroot_path = apr_pstrdup(pool, value);
2494     }
2495
2496   /* Get the copyfrom. */
2497   value = svn_hash_gets(headers, HEADER_COPYFROM);
2498   if (value == NULL)
2499     {
2500       noderev->copyfrom_path = NULL;
2501       noderev->copyfrom_rev = SVN_INVALID_REVNUM;
2502     }
2503   else
2504     {
2505       char *str = svn_cstring_tokenize(" ", &value);
2506       if (str == NULL)
2507         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2508                                  _("Malformed copyfrom line in node-rev '%s'"),
2509                                  noderev_id);
2510
2511       noderev->copyfrom_rev = SVN_STR_TO_REV(str);
2512
2513       if (*value == 0)
2514         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2515                                  _("Malformed copyfrom line in node-rev '%s'"),
2516                                  noderev_id);
2517       noderev->copyfrom_path = apr_pstrdup(pool, value);
2518     }
2519
2520   /* Get whether this is a fresh txn root. */
2521   value = svn_hash_gets(headers, HEADER_FRESHTXNRT);
2522   noderev->is_fresh_txn_root = (value != NULL);
2523
2524   /* Get the mergeinfo count. */
2525   value = svn_hash_gets(headers, HEADER_MINFO_CNT);
2526   if (value)
2527     SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, value));
2528   else
2529     noderev->mergeinfo_count = 0;
2530
2531   /* Get whether *this* node has mergeinfo. */
2532   value = svn_hash_gets(headers, HEADER_MINFO_HERE);
2533   noderev->has_mergeinfo = (value != NULL);
2534
2535   *noderev_p = noderev;
2536
2537   return SVN_NO_ERROR;
2538 }
2539
2540 svn_error_t *
2541 svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
2542                              svn_fs_t *fs,
2543                              const svn_fs_id_t *id,
2544                              apr_pool_t *pool)
2545 {
2546   svn_error_t *err = get_node_revision_body(noderev_p, fs, id, pool);
2547   if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
2548     {
2549       svn_string_t *id_string = svn_fs_fs__id_unparse(id, pool);
2550       return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
2551                                "Corrupt node-revision '%s'",
2552                                id_string->data);
2553     }
2554   return svn_error_trace(err);
2555 }
2556
2557
2558 /* Return a formatted string, compatible with filesystem format FORMAT,
2559    that represents the location of representation REP.  If
2560    MUTABLE_REP_TRUNCATED is given, the rep is for props or dir contents,
2561    and only a "-1" revision number will be given for a mutable rep.
2562    If MAY_BE_CORRUPT is true, guard for NULL when constructing the string.
2563    Perform the allocation from POOL.  */
2564 static const char *
2565 representation_string(representation_t *rep,
2566                       int format,
2567                       svn_boolean_t mutable_rep_truncated,
2568                       svn_boolean_t may_be_corrupt,
2569                       apr_pool_t *pool)
2570 {
2571   if (rep->txn_id && mutable_rep_truncated)
2572     return "-1";
2573
2574 #define DISPLAY_MAYBE_NULL_CHECKSUM(checksum)          \
2575   ((!may_be_corrupt || (checksum) != NULL)     \
2576    ? svn_checksum_to_cstring_display((checksum), pool) \
2577    : "(null)")
2578
2579   if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || rep->sha1_checksum == NULL)
2580     return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
2581                         " %" SVN_FILESIZE_T_FMT " %s",
2582                         rep->revision, rep->offset, rep->size,
2583                         rep->expanded_size,
2584                         DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum));
2585
2586   return apr_psprintf(pool, "%ld %" APR_OFF_T_FMT " %" SVN_FILESIZE_T_FMT
2587                       " %" SVN_FILESIZE_T_FMT " %s %s %s",
2588                       rep->revision, rep->offset, rep->size,
2589                       rep->expanded_size,
2590                       DISPLAY_MAYBE_NULL_CHECKSUM(rep->md5_checksum),
2591                       DISPLAY_MAYBE_NULL_CHECKSUM(rep->sha1_checksum),
2592                       rep->uniquifier);
2593
2594 #undef DISPLAY_MAYBE_NULL_CHECKSUM
2595
2596 }
2597
2598
2599 svn_error_t *
2600 svn_fs_fs__write_noderev(svn_stream_t *outfile,
2601                          node_revision_t *noderev,
2602                          int format,
2603                          svn_boolean_t include_mergeinfo,
2604                          apr_pool_t *pool)
2605 {
2606   SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n",
2607                             svn_fs_fs__id_unparse(noderev->id,
2608                                                   pool)->data));
2609
2610   SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TYPE ": %s\n",
2611                             (noderev->kind == svn_node_file) ?
2612                             KIND_FILE : KIND_DIR));
2613
2614   if (noderev->predecessor_id)
2615     SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PRED ": %s\n",
2616                               svn_fs_fs__id_unparse(noderev->predecessor_id,
2617                                                     pool)->data));
2618
2619   SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COUNT ": %d\n",
2620                             noderev->predecessor_count));
2621
2622   if (noderev->data_rep)
2623     SVN_ERR(svn_stream_printf(outfile, pool, HEADER_TEXT ": %s\n",
2624                               representation_string(noderev->data_rep,
2625                                                     format,
2626                                                     (noderev->kind
2627                                                      == svn_node_dir),
2628                                                     FALSE,
2629                                                     pool)));
2630
2631   if (noderev->prop_rep)
2632     SVN_ERR(svn_stream_printf(outfile, pool, HEADER_PROPS ": %s\n",
2633                               representation_string(noderev->prop_rep, format,
2634                                                     TRUE, FALSE, pool)));
2635
2636   SVN_ERR(svn_stream_printf(outfile, pool, HEADER_CPATH ": %s\n",
2637                             noderev->created_path));
2638
2639   if (noderev->copyfrom_path)
2640     SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYFROM ": %ld"
2641                               " %s\n",
2642                               noderev->copyfrom_rev,
2643                               noderev->copyfrom_path));
2644
2645   if ((noderev->copyroot_rev != svn_fs_fs__id_rev(noderev->id)) ||
2646       (strcmp(noderev->copyroot_path, noderev->created_path) != 0))
2647     SVN_ERR(svn_stream_printf(outfile, pool, HEADER_COPYROOT ": %ld"
2648                               " %s\n",
2649                               noderev->copyroot_rev,
2650                               noderev->copyroot_path));
2651
2652   if (noderev->is_fresh_txn_root)
2653     SVN_ERR(svn_stream_puts(outfile, HEADER_FRESHTXNRT ": y\n"));
2654
2655   if (include_mergeinfo)
2656     {
2657       if (noderev->mergeinfo_count > 0)
2658         SVN_ERR(svn_stream_printf(outfile, pool, HEADER_MINFO_CNT ": %"
2659                                   APR_INT64_T_FMT "\n",
2660                                   noderev->mergeinfo_count));
2661
2662       if (noderev->has_mergeinfo)
2663         SVN_ERR(svn_stream_puts(outfile, HEADER_MINFO_HERE ": y\n"));
2664     }
2665
2666   return svn_stream_puts(outfile, "\n");
2667 }
2668
2669 svn_error_t *
2670 svn_fs_fs__put_node_revision(svn_fs_t *fs,
2671                              const svn_fs_id_t *id,
2672                              node_revision_t *noderev,
2673                              svn_boolean_t fresh_txn_root,
2674                              apr_pool_t *pool)
2675 {
2676   fs_fs_data_t *ffd = fs->fsap_data;
2677   apr_file_t *noderev_file;
2678   const char *txn_id = svn_fs_fs__id_txn_id(id);
2679
2680   noderev->is_fresh_txn_root = fresh_txn_root;
2681
2682   if (! txn_id)
2683     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2684                              _("Attempted to write to non-transaction '%s'"),
2685                              svn_fs_fs__id_unparse(id, pool)->data);
2686
2687   SVN_ERR(svn_io_file_open(&noderev_file, path_txn_node_rev(fs, id, pool),
2688                            APR_WRITE | APR_CREATE | APR_TRUNCATE
2689                            | APR_BUFFERED, APR_OS_DEFAULT, pool));
2690
2691   SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
2692                                                             pool),
2693                                    noderev, ffd->format,
2694                                    svn_fs_fs__fs_supports_mergeinfo(fs),
2695                                    pool));
2696
2697   SVN_ERR(svn_io_file_close(noderev_file, pool));
2698
2699   return SVN_NO_ERROR;
2700 }
2701
2702 /* For the in-transaction NODEREV within FS, write the sha1->rep mapping
2703  * file in the respective transaction, if rep sharing has been enabled etc.
2704  * Use POOL for temporary allocations.
2705  */
2706 static svn_error_t *
2707 store_sha1_rep_mapping(svn_fs_t *fs,
2708                        node_revision_t *noderev,
2709                        apr_pool_t *pool)
2710 {
2711   fs_fs_data_t *ffd = fs->fsap_data;
2712
2713   /* if rep sharing has been enabled and the noderev has a data rep and
2714    * its SHA-1 is known, store the rep struct under its SHA1. */
2715   if (   ffd->rep_sharing_allowed
2716       && noderev->data_rep
2717       && noderev->data_rep->sha1_checksum)
2718     {
2719       apr_file_t *rep_file;
2720       const char *file_name = path_txn_sha1(fs,
2721                                             svn_fs_fs__id_txn_id(noderev->id),
2722                                             noderev->data_rep->sha1_checksum,
2723                                             pool);
2724       const char *rep_string = representation_string(noderev->data_rep,
2725                                                      ffd->format,
2726                                                      (noderev->kind
2727                                                       == svn_node_dir),
2728                                                      FALSE,
2729                                                      pool);
2730       SVN_ERR(svn_io_file_open(&rep_file, file_name,
2731                                APR_WRITE | APR_CREATE | APR_TRUNCATE
2732                                | APR_BUFFERED, APR_OS_DEFAULT, pool));
2733
2734       SVN_ERR(svn_io_file_write_full(rep_file, rep_string,
2735                                      strlen(rep_string), NULL, pool));
2736
2737       SVN_ERR(svn_io_file_close(rep_file, pool));
2738     }
2739
2740   return SVN_NO_ERROR;
2741 }
2742
2743
2744 /* This structure is used to hold the information associated with a
2745    REP line. */
2746 struct rep_args
2747 {
2748   svn_boolean_t is_delta;
2749   svn_boolean_t is_delta_vs_empty;
2750
2751   svn_revnum_t base_revision;
2752   apr_off_t base_offset;
2753   svn_filesize_t base_length;
2754 };
2755
2756 /* Read the next line from file FILE and parse it as a text
2757    representation entry.  Return the parsed entry in *REP_ARGS_P.
2758    Perform all allocations in POOL. */
2759 static svn_error_t *
2760 read_rep_line(struct rep_args **rep_args_p,
2761               apr_file_t *file,
2762               apr_pool_t *pool)
2763 {
2764   char buffer[160];
2765   apr_size_t limit;
2766   struct rep_args *rep_args;
2767   char *str, *last_str = buffer;
2768   apr_int64_t val;
2769
2770   limit = sizeof(buffer);
2771   SVN_ERR(svn_io_read_length_line(file, buffer, &limit, pool));
2772
2773   rep_args = apr_pcalloc(pool, sizeof(*rep_args));
2774   rep_args->is_delta = FALSE;
2775
2776   if (strcmp(buffer, REP_PLAIN) == 0)
2777     {
2778       *rep_args_p = rep_args;
2779       return SVN_NO_ERROR;
2780     }
2781
2782   if (strcmp(buffer, REP_DELTA) == 0)
2783     {
2784       /* This is a delta against the empty stream. */
2785       rep_args->is_delta = TRUE;
2786       rep_args->is_delta_vs_empty = TRUE;
2787       *rep_args_p = rep_args;
2788       return SVN_NO_ERROR;
2789     }
2790
2791   rep_args->is_delta = TRUE;
2792   rep_args->is_delta_vs_empty = FALSE;
2793
2794   /* We have hopefully a DELTA vs. a non-empty base revision. */
2795   str = svn_cstring_tokenize(" ", &last_str);
2796   if (! str || (strcmp(str, REP_DELTA) != 0))
2797     goto error;
2798
2799   str = svn_cstring_tokenize(" ", &last_str);
2800   if (! str)
2801     goto error;
2802   rep_args->base_revision = SVN_STR_TO_REV(str);
2803
2804   str = svn_cstring_tokenize(" ", &last_str);
2805   if (! str)
2806     goto error;
2807   SVN_ERR(svn_cstring_atoi64(&val, str));
2808   rep_args->base_offset = (apr_off_t)val;
2809
2810   str = svn_cstring_tokenize(" ", &last_str);
2811   if (! str)
2812     goto error;
2813   SVN_ERR(svn_cstring_atoi64(&val, str));
2814   rep_args->base_length = (svn_filesize_t)val;
2815
2816   *rep_args_p = rep_args;
2817   return SVN_NO_ERROR;
2818
2819  error:
2820   return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2821                            _("Malformed representation header at %s"),
2822                            path_and_offset_of(file, pool));
2823 }
2824
2825 /* Given a revision file REV_FILE, opened to REV in FS, find the Node-ID
2826    of the header located at OFFSET and store it in *ID_P.  Allocate
2827    temporary variables from POOL. */
2828 static svn_error_t *
2829 get_fs_id_at_offset(svn_fs_id_t **id_p,
2830                     apr_file_t *rev_file,
2831                     svn_fs_t *fs,
2832                     svn_revnum_t rev,
2833                     apr_off_t offset,
2834                     apr_pool_t *pool)
2835 {
2836   svn_fs_id_t *id;
2837   apr_hash_t *headers;
2838   const char *node_id_str;
2839
2840   SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2841
2842   SVN_ERR(read_header_block(&headers,
2843                             svn_stream_from_aprfile2(rev_file, TRUE, pool),
2844                             pool));
2845
2846   /* In error messages, the offset is relative to the pack file,
2847      not to the rev file. */
2848
2849   node_id_str = svn_hash_gets(headers, HEADER_ID);
2850
2851   if (node_id_str == NULL)
2852     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2853                              _("Missing node-id in node-rev at r%ld "
2854                              "(offset %s)"),
2855                              rev,
2856                              apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
2857
2858   id = svn_fs_fs__id_parse(node_id_str, strlen(node_id_str), pool);
2859
2860   if (id == NULL)
2861     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2862                              _("Corrupt node-id '%s' in node-rev at r%ld "
2863                                "(offset %s)"),
2864                              node_id_str, rev,
2865                              apr_psprintf(pool, "%" APR_OFF_T_FMT, offset));
2866
2867   *id_p = id;
2868
2869   /* ### assert that the txn_id is REV/OFFSET ? */
2870
2871   return SVN_NO_ERROR;
2872 }
2873
2874
2875 /* Given an open revision file REV_FILE in FS for REV, locate the trailer that
2876    specifies the offset to the root node-id and to the changed path
2877    information.  Store the root node offset in *ROOT_OFFSET and the
2878    changed path offset in *CHANGES_OFFSET.  If either of these
2879    pointers is NULL, do nothing with it.
2880
2881    If PACKED is true, REV_FILE should be a packed shard file.
2882    ### There is currently no such parameter.  This function assumes that
2883        is_packed_rev(FS, REV) will indicate whether REV_FILE is a packed
2884        file.  Therefore FS->fsap_data->min_unpacked_rev must not have been
2885        refreshed since REV_FILE was opened if there is a possibility that
2886        revision REV may have become packed since then.
2887        TODO: Take an IS_PACKED parameter instead, in order to remove this
2888        requirement.
2889
2890    Allocate temporary variables from POOL. */
2891 static svn_error_t *
2892 get_root_changes_offset(apr_off_t *root_offset,
2893                         apr_off_t *changes_offset,
2894                         apr_file_t *rev_file,
2895                         svn_fs_t *fs,
2896                         svn_revnum_t rev,
2897                         apr_pool_t *pool)
2898 {
2899   fs_fs_data_t *ffd = fs->fsap_data;
2900   apr_off_t offset;
2901   apr_off_t rev_offset;
2902   char buf[64];
2903   int i, num_bytes;
2904   const char *str;
2905   apr_size_t len;
2906   apr_seek_where_t seek_relative;
2907
2908   /* Determine where to seek to in the file.
2909
2910      If we've got a pack file, we want to seek to the end of the desired
2911      revision.  But we don't track that, so we seek to the beginning of the
2912      next revision.
2913
2914      Unless the next revision is in a different file, in which case, we can
2915      just seek to the end of the pack file -- just like we do in the
2916      non-packed case. */
2917   if (is_packed_rev(fs, rev) && ((rev + 1) % ffd->max_files_per_dir != 0))
2918     {
2919       SVN_ERR(get_packed_offset(&offset, fs, rev + 1, pool));
2920       seek_relative = APR_SET;
2921     }
2922   else
2923     {
2924       seek_relative = APR_END;
2925       offset = 0;
2926     }
2927
2928   /* Offset of the revision from the start of the pack file, if applicable. */
2929   if (is_packed_rev(fs, rev))
2930     SVN_ERR(get_packed_offset(&rev_offset, fs, rev, pool));
2931   else
2932     rev_offset = 0;
2933
2934   /* We will assume that the last line containing the two offsets
2935      will never be longer than 64 characters. */
2936   SVN_ERR(svn_io_file_seek(rev_file, seek_relative, &offset, pool));
2937
2938   offset -= sizeof(buf);
2939   SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
2940
2941   /* Read in this last block, from which we will identify the last line. */
2942   len = sizeof(buf);
2943   SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
2944
2945   /* This cast should be safe since the maximum amount read, 64, will
2946      never be bigger than the size of an int. */
2947   num_bytes = (int) len;
2948
2949   /* The last byte should be a newline. */
2950   if (buf[num_bytes - 1] != '\n')
2951     {
2952       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2953                                _("Revision file (r%ld) lacks trailing newline"),
2954                                rev);
2955     }
2956
2957   /* Look for the next previous newline. */
2958   for (i = num_bytes - 2; i >= 0; i--)
2959     {
2960       if (buf[i] == '\n')
2961         break;
2962     }
2963
2964   if (i < 0)
2965     {
2966       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2967                                _("Final line in revision file (r%ld) longer "
2968                                  "than 64 characters"),
2969                                rev);
2970     }
2971
2972   i++;
2973   str = &buf[i];
2974
2975   /* find the next space */
2976   for ( ; i < (num_bytes - 2) ; i++)
2977     if (buf[i] == ' ')
2978       break;
2979
2980   if (i == (num_bytes - 2))
2981     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2982                              _("Final line in revision file r%ld missing space"),
2983                              rev);
2984
2985   if (root_offset)
2986     {
2987       apr_int64_t val;
2988
2989       buf[i] = '\0';
2990       SVN_ERR(svn_cstring_atoi64(&val, str));
2991       *root_offset = rev_offset + (apr_off_t)val;
2992     }
2993
2994   i++;
2995   str = &buf[i];
2996
2997   /* find the next newline */
2998   for ( ; i < num_bytes; i++)
2999     if (buf[i] == '\n')
3000       break;
3001
3002   if (changes_offset)
3003     {
3004       apr_int64_t val;
3005
3006       buf[i] = '\0';
3007       SVN_ERR(svn_cstring_atoi64(&val, str));
3008       *changes_offset = rev_offset + (apr_off_t)val;
3009     }
3010
3011   return SVN_NO_ERROR;
3012 }
3013
3014 /* Move a file into place from OLD_FILENAME in the transactions
3015    directory to its final location NEW_FILENAME in the repository.  On
3016    Unix, match the permissions of the new file to the permissions of
3017    PERMS_REFERENCE.  Temporary allocations are from POOL.
3018
3019    This function almost duplicates svn_io_file_move(), but it tries to
3020    guarantee a flush. */
3021 static svn_error_t *
3022 move_into_place(const char *old_filename,
3023                 const char *new_filename,
3024                 const char *perms_reference,
3025                 apr_pool_t *pool)
3026 {
3027   svn_error_t *err;
3028
3029   SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool));
3030
3031   /* Move the file into place. */
3032   err = svn_io_file_rename(old_filename, new_filename, pool);
3033   if (err && APR_STATUS_IS_EXDEV(err->apr_err))
3034     {
3035       apr_file_t *file;
3036
3037       /* Can't rename across devices; fall back to copying. */
3038       svn_error_clear(err);
3039       err = SVN_NO_ERROR;
3040       SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
3041
3042       /* Flush the target of the copy to disk. */
3043       SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
3044                                APR_OS_DEFAULT, pool));
3045       /* ### BH: Does this really guarantee a flush of the data written
3046          ### via a completely different handle on all operating systems?
3047          ###
3048          ### Maybe we should perform the copy ourselves instead of making
3049          ### apr do that and flush the real handle? */
3050       SVN_ERR(svn_io_file_flush_to_disk(file, pool));
3051       SVN_ERR(svn_io_file_close(file, pool));
3052     }
3053   if (err)
3054     return svn_error_trace(err);
3055
3056 #ifdef __linux__
3057   {
3058     /* Linux has the unusual feature that fsync() on a file is not
3059        enough to ensure that a file's directory entries have been
3060        flushed to disk; you have to fsync the directory as well.
3061        On other operating systems, we'd only be asking for trouble
3062        by trying to open and fsync a directory. */
3063     const char *dirname;
3064     apr_file_t *file;
3065
3066     dirname = svn_dirent_dirname(new_filename, pool);
3067     SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
3068                              pool));
3069     SVN_ERR(svn_io_file_flush_to_disk(file, pool));
3070     SVN_ERR(svn_io_file_close(file, pool));
3071   }
3072 #endif
3073
3074   return SVN_NO_ERROR;
3075 }
3076
3077 svn_error_t *
3078 svn_fs_fs__rev_get_root(svn_fs_id_t **root_id_p,
3079                         svn_fs_t *fs,
3080                         svn_revnum_t rev,
3081                         apr_pool_t *pool)
3082 {
3083   fs_fs_data_t *ffd = fs->fsap_data;
3084   apr_file_t *revision_file;
3085   apr_off_t root_offset;
3086   svn_fs_id_t *root_id = NULL;
3087   svn_boolean_t is_cached;
3088
3089   SVN_ERR(ensure_revision_exists(fs, rev, pool));
3090
3091   SVN_ERR(svn_cache__get((void **) root_id_p, &is_cached,
3092                          ffd->rev_root_id_cache, &rev, pool));
3093   if (is_cached)
3094     return SVN_NO_ERROR;
3095
3096   SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
3097   SVN_ERR(get_root_changes_offset(&root_offset, NULL, revision_file, fs, rev,
3098                                   pool));
3099
3100   SVN_ERR(get_fs_id_at_offset(&root_id, revision_file, fs, rev,
3101                               root_offset, pool));
3102
3103   SVN_ERR(svn_io_file_close(revision_file, pool));
3104
3105   SVN_ERR(svn_cache__set(ffd->rev_root_id_cache, &rev, root_id, pool));
3106
3107   *root_id_p = root_id;
3108
3109   return SVN_NO_ERROR;
3110 }
3111
3112 /* Revprop caching management.
3113  *
3114  * Mechanism:
3115  * ----------
3116  *
3117  * Revprop caching needs to be activated and will be deactivated for the
3118  * respective FS instance if the necessary infrastructure could not be
3119  * initialized.  In deactivated mode, there is almost no runtime overhead
3120  * associated with revprop caching.  As long as no revprops are being read
3121  * or changed, revprop caching imposes no overhead.
3122  *
3123  * When activated, we cache revprops using (revision, generation) pairs
3124  * as keys with the generation being incremented upon every revprop change.
3125  * Since the cache is process-local, the generation needs to be tracked
3126  * for at least as long as the process lives but may be reset afterwards.
3127  *
3128  * To track the revprop generation, we use two-layer approach. On the lower
3129  * level, we use named atomics to have a system-wide consistent value for
3130  * the current revprop generation.  However, those named atomics will only
3131  * remain valid for as long as at least one process / thread in the system
3132  * accesses revprops in the respective repository.  The underlying shared
3133  * memory gets cleaned up afterwards.
3134  *
3135  * On the second level, we will use a persistent file to track the latest
3136  * revprop generation.  It will be written upon each revprop change but
3137  * only be read if we are the first process to initialize the named atomics
3138  * with that value.
3139  *
3140  * The overhead for the second and following accesses to revprops is
3141  * almost zero on most systems.
3142  *
3143  *
3144  * Tech aspects:
3145  * -------------
3146  *
3147  * A problem is that we need to provide a globally available file name to
3148  * back the SHM implementation on OSes that need it.  We can only assume
3149  * write access to some file within the respective repositories.  Because
3150  * a given server process may access thousands of repositories during its
3151  * lifetime, keeping the SHM data alive for all of them is also not an
3152  * option.
3153  *
3154  * So, we store the new revprop generation on disk as part of each
3155  * setrevprop call, i.e. this write will be serialized and the write order
3156  * be guaranteed by the repository write lock.
3157  *
3158  * The only racy situation occurs when the data is being read again by two
3159  * processes concurrently but in that situation, the first process to
3160  * finish that procedure is guaranteed to be the only one that initializes
3161  * the SHM data.  Since even writers will first go through that
3162  * initialization phase, they will never operate on stale data.
3163  */
3164
3165 /* Read revprop generation as stored on disk for repository FS. The result
3166  * is returned in *CURRENT. Default to 2 if no such file is available.
3167  */
3168 static svn_error_t *
3169 read_revprop_generation_file(apr_int64_t *current,
3170                              svn_fs_t *fs,
3171                              apr_pool_t *pool)
3172 {
3173   svn_error_t *err;
3174   apr_file_t *file;
3175   char buf[80];
3176   apr_size_t len;
3177   const char *path = path_revprop_generation(fs, pool);
3178
3179   err = svn_io_file_open(&file, path,
3180                          APR_READ | APR_BUFFERED,
3181                          APR_OS_DEFAULT, pool);
3182   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
3183     {
3184       svn_error_clear(err);
3185       *current = 2;
3186
3187       return SVN_NO_ERROR;
3188     }
3189   SVN_ERR(err);
3190
3191   len = sizeof(buf);
3192   SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
3193
3194   /* Check that the first line contains only digits. */
3195   SVN_ERR(check_file_buffer_numeric(buf, 0, path,
3196                                     "Revprop Generation", pool));
3197   SVN_ERR(svn_cstring_atoi64(current, buf));
3198
3199   return svn_io_file_close(file, pool);
3200 }
3201
3202 /* Write the CURRENT revprop generation to disk for repository FS.
3203  */
3204 static svn_error_t *
3205 write_revprop_generation_file(svn_fs_t *fs,
3206                               apr_int64_t current,
3207                               apr_pool_t *pool)
3208 {
3209   apr_file_t *file;
3210   const char *tmp_path;
3211
3212   char buf[SVN_INT64_BUFFER_SIZE];
3213   apr_size_t len = svn__i64toa(buf, current);
3214   buf[len] = '\n';
3215
3216   SVN_ERR(svn_io_open_unique_file3(&file, &tmp_path, fs->path,
3217                                    svn_io_file_del_none, pool, pool));
3218   SVN_ERR(svn_io_file_write_full(file, buf, len + 1, NULL, pool));
3219   SVN_ERR(svn_io_file_close(file, pool));
3220
3221   return move_into_place(tmp_path, path_revprop_generation(fs, pool),
3222                          tmp_path, pool);
3223 }
3224
3225 /* Make sure the revprop_namespace member in FS is set. */
3226 static svn_error_t *
3227 ensure_revprop_namespace(svn_fs_t *fs)
3228 {
3229   fs_fs_data_t *ffd = fs->fsap_data;
3230
3231   return ffd->revprop_namespace == NULL
3232     ? svn_atomic_namespace__create(&ffd->revprop_namespace,
3233                                    svn_dirent_join(fs->path,
3234                                                    ATOMIC_REVPROP_NAMESPACE,
3235                                                    fs->pool),
3236                                    fs->pool)
3237     : SVN_NO_ERROR;
3238 }
3239
3240 /* Make sure the revprop_namespace member in FS is set. */
3241 static svn_error_t *
3242 cleanup_revprop_namespace(svn_fs_t *fs)
3243 {
3244   const char *name = svn_dirent_join(fs->path,
3245                                      ATOMIC_REVPROP_NAMESPACE,
3246                                      fs->pool);
3247   return svn_error_trace(svn_atomic_namespace__cleanup(name, fs->pool));
3248 }
3249
3250 /* Make sure the revprop_generation member in FS is set and, if necessary,
3251  * initialized with the latest value stored on disk.
3252  */
3253 static svn_error_t *
3254 ensure_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
3255 {
3256   fs_fs_data_t *ffd = fs->fsap_data;
3257
3258   SVN_ERR(ensure_revprop_namespace(fs));
3259   if (ffd->revprop_generation == NULL)
3260     {
3261       apr_int64_t current = 0;
3262
3263       SVN_ERR(svn_named_atomic__get(&ffd->revprop_generation,
3264                                     ffd->revprop_namespace,
3265                                     ATOMIC_REVPROP_GENERATION,
3266                                     TRUE));
3267
3268       /* If the generation is at 0, we just created a new namespace
3269        * (it would be at least 2 otherwise). Read the latest generation
3270        * from disk and if we are the first one to initialize the atomic
3271        * (i.e. is still 0), set it to the value just gotten.
3272        */
3273       SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
3274       if (current == 0)
3275         {
3276           SVN_ERR(read_revprop_generation_file(&current, fs, pool));
3277           SVN_ERR(svn_named_atomic__cmpxchg(NULL, current, 0,
3278                                             ffd->revprop_generation));
3279         }
3280     }
3281
3282   return SVN_NO_ERROR;
3283 }
3284
3285 /* Make sure the revprop_timeout member in FS is set. */
3286 static svn_error_t *
3287 ensure_revprop_timeout(svn_fs_t *fs)
3288 {
3289   fs_fs_data_t *ffd = fs->fsap_data;
3290
3291   SVN_ERR(ensure_revprop_namespace(fs));
3292   return ffd->revprop_timeout == NULL
3293     ? svn_named_atomic__get(&ffd->revprop_timeout,
3294                             ffd->revprop_namespace,
3295                             ATOMIC_REVPROP_TIMEOUT,
3296                             TRUE)
3297     : SVN_NO_ERROR;
3298 }
3299
3300 /* Create an error object with the given MESSAGE and pass it to the
3301    WARNING member of FS. */
3302 static void
3303 log_revprop_cache_init_warning(svn_fs_t *fs,
3304                                svn_error_t *underlying_err,
3305                                const char *message)
3306 {
3307   svn_error_t *err = svn_error_createf(SVN_ERR_FS_REVPROP_CACHE_INIT_FAILURE,
3308                                        underlying_err,
3309                                        message, fs->path);
3310
3311   if (fs->warning)
3312     (fs->warning)(fs->warning_baton, err);
3313
3314   svn_error_clear(err);
3315 }
3316
3317 /* Test whether revprop cache and necessary infrastructure are
3318    available in FS. */
3319 static svn_boolean_t
3320 has_revprop_cache(svn_fs_t *fs, apr_pool_t *pool)
3321 {
3322   fs_fs_data_t *ffd = fs->fsap_data;
3323   svn_error_t *error;
3324
3325   /* is the cache (still) enabled? */
3326   if (ffd->revprop_cache == NULL)
3327     return FALSE;
3328
3329   /* is it efficient? */
3330   if (!svn_named_atomic__is_efficient())
3331     {
3332       /* access to it would be quite slow
3333        * -> disable the revprop cache for good
3334        */
3335       ffd->revprop_cache = NULL;
3336       log_revprop_cache_init_warning(fs, NULL,
3337                                      "Revprop caching for '%s' disabled"
3338                                      " because it would be inefficient.");
3339
3340       return FALSE;
3341     }
3342
3343   /* try to access our SHM-backed infrastructure */
3344   error = ensure_revprop_generation(fs, pool);
3345   if (error)
3346     {
3347       /* failure -> disable revprop cache for good */
3348
3349       ffd->revprop_cache = NULL;
3350       log_revprop_cache_init_warning(fs, error,
3351                                      "Revprop caching for '%s' disabled "
3352                                      "because SHM infrastructure for revprop "
3353                                      "caching failed to initialize.");
3354
3355       return FALSE;
3356     }
3357
3358   return TRUE;
3359 }
3360
3361 /* Baton structure for revprop_generation_fixup. */
3362 typedef struct revprop_generation_fixup_t
3363 {
3364   /* revprop generation to read */
3365   apr_int64_t *generation;
3366
3367   /* containing the revprop_generation member to query */
3368   fs_fs_data_t *ffd;
3369 } revprop_generation_upgrade_t;
3370
3371 /* If the revprop generation has an odd value, it means the original writer
3372    of the revprop got killed. We don't know whether that process as able
3373    to change the revprop data but we assume that it was. Therefore, we
3374    increase the generation in that case to basically invalidate everyones
3375    cache content.
3376    Execute this onlx while holding the write lock to the repo in baton->FFD.
3377  */
3378 static svn_error_t *
3379 revprop_generation_fixup(void *void_baton,
3380                          apr_pool_t *pool)
3381 {
3382   revprop_generation_upgrade_t *baton = void_baton;
3383   assert(baton->ffd->has_write_lock);
3384
3385   /* Maybe, either the original revprop writer or some other reader has
3386      already corrected / bumped the revprop generation.  Thus, we need
3387      to read it again. */
3388   SVN_ERR(svn_named_atomic__read(baton->generation,
3389                                  baton->ffd->revprop_generation));
3390
3391   /* Cause everyone to re-read revprops upon their next access, if the
3392      last revprop write did not complete properly. */
3393   while (*baton->generation % 2)
3394     SVN_ERR(svn_named_atomic__add(baton->generation,
3395                                   1,
3396                                   baton->ffd->revprop_generation));
3397
3398   return SVN_NO_ERROR;
3399 }
3400
3401 /* Read the current revprop generation and return it in *GENERATION.
3402    Also, detect aborted / crashed writers and recover from that.
3403    Use the access object in FS to set the shared mem values. */
3404 static svn_error_t *
3405 read_revprop_generation(apr_int64_t *generation,
3406                         svn_fs_t *fs,
3407                         apr_pool_t *pool)
3408 {
3409   apr_int64_t current = 0;
3410   fs_fs_data_t *ffd = fs->fsap_data;
3411
3412   /* read the current revprop generation number */
3413   SVN_ERR(ensure_revprop_generation(fs, pool));
3414   SVN_ERR(svn_named_atomic__read(&current, ffd->revprop_generation));
3415
3416   /* is an unfinished revprop write under the way? */
3417   if (current % 2)
3418     {
3419       apr_int64_t timeout = 0;
3420
3421       /* read timeout for the write operation */
3422       SVN_ERR(ensure_revprop_timeout(fs));
3423       SVN_ERR(svn_named_atomic__read(&timeout, ffd->revprop_timeout));
3424
3425       /* has the writer process been aborted,
3426        * i.e. has the timeout been reached?
3427        */
3428       if (apr_time_now() > timeout)
3429         {
3430           revprop_generation_upgrade_t baton;
3431           baton.generation = &current;
3432           baton.ffd = ffd;
3433
3434           /* Ensure that the original writer process no longer exists by
3435            * acquiring the write lock to this repository.  Then, fix up
3436            * the revprop generation.
3437            */
3438           if (ffd->has_write_lock)
3439             SVN_ERR(revprop_generation_fixup(&baton, pool));
3440           else
3441             SVN_ERR(svn_fs_fs__with_write_lock(fs, revprop_generation_fixup,
3442                                                &baton, pool));
3443         }
3444     }
3445
3446   /* return the value we just got */
3447   *generation = current;
3448   return SVN_NO_ERROR;
3449 }
3450
3451 /* Set the revprop generation to the next odd number to indicate that
3452    there is a revprop write process under way. If that times out,
3453    readers shall recover from that state & re-read revprops.
3454    Use the access object in FS to set the shared mem value. */
3455 static svn_error_t *
3456 begin_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
3457 {
3458   apr_int64_t current;
3459   fs_fs_data_t *ffd = fs->fsap_data;
3460
3461   /* set the timeout for the write operation */
3462   SVN_ERR(ensure_revprop_timeout(fs));
3463   SVN_ERR(svn_named_atomic__write(NULL,
3464                                   apr_time_now() + REVPROP_CHANGE_TIMEOUT,
3465                                   ffd->revprop_timeout));
3466
3467   /* set the revprop generation to an odd value to indicate
3468    * that a write is in progress
3469    */
3470   SVN_ERR(ensure_revprop_generation(fs, pool));
3471   do
3472     {
3473       SVN_ERR(svn_named_atomic__add(&current,
3474                                     1,
3475                                     ffd->revprop_generation));
3476     }
3477   while (current % 2 == 0);
3478
3479   return SVN_NO_ERROR;
3480 }
3481
3482 /* Set the revprop generation to the next even number to indicate that
3483    a) readers shall re-read revprops, and
3484    b) the write process has been completed (no recovery required)
3485    Use the access object in FS to set the shared mem value. */
3486 static svn_error_t *
3487 end_revprop_change(svn_fs_t *fs, apr_pool_t *pool)
3488 {
3489   apr_int64_t current = 1;
3490   fs_fs_data_t *ffd = fs->fsap_data;
3491
3492   /* set the revprop generation to an even value to indicate
3493    * that a write has been completed
3494    */
3495   SVN_ERR(ensure_revprop_generation(fs, pool));
3496   do
3497     {
3498       SVN_ERR(svn_named_atomic__add(&current,
3499                                     1,
3500                                     ffd->revprop_generation));
3501     }
3502   while (current % 2);
3503
3504   /* Save the latest generation to disk. FS is currently in a "locked"
3505    * state such that we can be sure the be the only ones to write that
3506    * file.
3507    */
3508   return write_revprop_generation_file(fs, current, pool);
3509 }
3510
3511 /* Container for all data required to access the packed revprop file
3512  * for a given REVISION.  This structure will be filled incrementally
3513  * by read_pack_revprops() its sub-routines.
3514  */
3515 typedef struct packed_revprops_t
3516 {
3517   /* revision number to read (not necessarily the first in the pack) */
3518   svn_revnum_t revision;
3519
3520   /* current revprop generation. Used when populating the revprop cache */
3521   apr_int64_t generation;
3522
3523   /* the actual revision properties */
3524   apr_hash_t *properties;
3525
3526   /* their size when serialized to a single string
3527    * (as found in PACKED_REVPROPS) */
3528   apr_size_t serialized_size;
3529
3530
3531   /* name of the pack file (without folder path) */
3532   const char *filename;
3533
3534   /* packed shard folder path */
3535   const char *folder;
3536
3537   /* sum of values in SIZES */
3538   apr_size_t total_size;
3539
3540   /* first revision in the pack (>= MANIFEST_START) */
3541   svn_revnum_t start_revision;
3542
3543   /* size of the revprops in PACKED_REVPROPS */
3544   apr_array_header_t *sizes;
3545
3546   /* offset of the revprops in PACKED_REVPROPS */
3547   apr_array_header_t *offsets;
3548
3549
3550   /* concatenation of the serialized representation of all revprops
3551    * in the pack, i.e. the pack content without header and compression */
3552   svn_stringbuf_t *packed_revprops;
3553
3554   /* First revision covered by MANIFEST.
3555    * Will equal the shard start revision or 1, for the 1st shard. */
3556   svn_revnum_t manifest_start;
3557
3558   /* content of the manifest.
3559    * Maps long(rev - MANIFEST_START) to const char* pack file name */
3560   apr_array_header_t *manifest;
3561 } packed_revprops_t;
3562
3563 /* Parse the serialized revprops in CONTENT and return them in *PROPERTIES.
3564  * Also, put them into the revprop cache, if activated, for future use.
3565  * Three more parameters are being used to update the revprop cache: FS is
3566  * our file system, the revprops belong to REVISION and the global revprop
3567  * GENERATION is used as well.
3568  *
3569  * The returned hash will be allocated in POOL, SCRATCH_POOL is being used
3570  * for temporary allocations.
3571  */
3572 static svn_error_t *
3573 parse_revprop(apr_hash_t **properties,
3574               svn_fs_t *fs,
3575               svn_revnum_t revision,
3576               apr_int64_t generation,
3577               svn_string_t *content,
3578               apr_pool_t *pool,
3579               apr_pool_t *scratch_pool)
3580 {
3581   svn_stream_t *stream = svn_stream_from_string(content, scratch_pool);
3582   *properties = apr_hash_make(pool);
3583
3584   SVN_ERR(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool));
3585   if (has_revprop_cache(fs, pool))
3586     {
3587       fs_fs_data_t *ffd = fs->fsap_data;
3588       pair_cache_key_t key = { 0 };
3589
3590       key.revision = revision;
3591       key.second = generation;
3592       SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, *properties,
3593                              scratch_pool));
3594     }
3595
3596   return SVN_NO_ERROR;
3597 }
3598
3599 /* Read the non-packed revprops for revision REV in FS, put them into the
3600  * revprop cache if activated and return them in *PROPERTIES.  GENERATION
3601  * is the current revprop generation.
3602  *
3603  * If the data could not be read due to an otherwise recoverable error,
3604  * leave *PROPERTIES unchanged. No error will be returned in that case.
3605  *
3606  * Allocations will be done in POOL.
3607  */
3608 static svn_error_t *
3609 read_non_packed_revprop(apr_hash_t **properties,
3610                         svn_fs_t *fs,
3611                         svn_revnum_t rev,
3612                         apr_int64_t generation,
3613                         apr_pool_t *pool)
3614 {
3615   svn_stringbuf_t *content = NULL;
3616   apr_pool_t *iterpool = svn_pool_create(pool);
3617   svn_boolean_t missing = FALSE;
3618   int i;
3619
3620   for (i = 0; i < RECOVERABLE_RETRY_COUNT && !missing && !content; ++i)
3621     {
3622       svn_pool_clear(iterpool);
3623       SVN_ERR(try_stringbuf_from_file(&content,
3624                                       &missing,
3625                                       path_revprops(fs, rev, iterpool),
3626                                       i + 1 < RECOVERABLE_RETRY_COUNT,
3627                                       iterpool));
3628     }
3629
3630   if (content)
3631     SVN_ERR(parse_revprop(properties, fs, rev, generation,
3632                           svn_stringbuf__morph_into_string(content),
3633                           pool, iterpool));
3634
3635   svn_pool_clear(iterpool);
3636
3637   return SVN_NO_ERROR;
3638 }
3639
3640 /* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST
3641  * members. Use POOL for allocating results and SCRATCH_POOL for temporaries.
3642  */
3643 static svn_error_t *
3644 get_revprop_packname(svn_fs_t *fs,
3645                      packed_revprops_t *revprops,
3646                      apr_pool_t *pool,
3647                      apr_pool_t *scratch_pool)
3648 {
3649   fs_fs_data_t *ffd = fs->fsap_data;
3650   svn_stringbuf_t *content = NULL;
3651   const char *manifest_file_path;
3652   int idx;
3653
3654   /* read content of the manifest file */
3655   revprops->folder = path_revprops_pack_shard(fs, revprops->revision, pool);
3656   manifest_file_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
3657
3658   SVN_ERR(read_content(&content, manifest_file_path, pool));
3659
3660   /* parse the manifest. Every line is a file name */
3661   revprops->manifest = apr_array_make(pool, ffd->max_files_per_dir,
3662                                       sizeof(const char*));
3663
3664   /* Read all lines.  Since the last line ends with a newline, we will
3665      end up with a valid but empty string after the last entry. */
3666   while (content->data && *content->data)
3667     {
3668       APR_ARRAY_PUSH(revprops->manifest, const char*) = content->data;
3669       content->data = strchr(content->data, '\n');
3670       if (content->data)
3671         {
3672           *content->data = 0;
3673           content->data++;
3674         }
3675     }
3676
3677   /* Index for our revision. Rev 0 is excluded from the first shard. */
3678   revprops->manifest_start = revprops->revision
3679                            - (revprops->revision % ffd->max_files_per_dir);
3680   if (revprops->manifest_start == 0)
3681     ++revprops->manifest_start;
3682   idx = (int)(revprops->revision - revprops->manifest_start);
3683
3684   if (revprops->manifest->nelts <= idx)
3685     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3686                              _("Packed revprop manifest for r%ld too "
3687                                "small"), revprops->revision);
3688
3689   /* Now get the file name */
3690   revprops->filename = APR_ARRAY_IDX(revprops->manifest, idx, const char*);
3691
3692   return SVN_NO_ERROR;
3693 }
3694
3695 /* Return TRUE, if revision R1 and R2 refer to the same shard in FS.
3696  */
3697 static svn_boolean_t
3698 same_shard(svn_fs_t *fs,
3699            svn_revnum_t r1,
3700            svn_revnum_t r2)
3701 {
3702   fs_fs_data_t *ffd = fs->fsap_data;
3703   return (r1 / ffd->max_files_per_dir) == (r2 / ffd->max_files_per_dir);
3704 }
3705
3706 /* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS,
3707  * fill the START_REVISION, SIZES, OFFSETS members. Also, make
3708  * PACKED_REVPROPS point to the first serialized revprop.
3709  *
3710  * Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as
3711  * well as the SERIALIZED_SIZE member.  If revprop caching has been
3712  * enabled, parse all revprops in the pack and cache them.
3713  */
3714 static svn_error_t *
3715 parse_packed_revprops(svn_fs_t *fs,
3716                       packed_revprops_t *revprops,
3717                       apr_pool_t *pool,
3718                       apr_pool_t *scratch_pool)
3719 {
3720   svn_stream_t *stream;
3721   apr_int64_t first_rev, count, i;
3722   apr_off_t offset;
3723   const char *header_end;
3724   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
3725
3726   /* decompress (even if the data is only "stored", there is still a
3727    * length header to remove) */
3728   svn_string_t *compressed
3729       = svn_stringbuf__morph_into_string(revprops->packed_revprops);
3730   svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(pool);
3731   SVN_ERR(svn__decompress(compressed, uncompressed, APR_SIZE_MAX));
3732
3733   /* read first revision number and number of revisions in the pack */
3734   stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
3735   SVN_ERR(read_number_from_stream(&first_rev, NULL, stream, iterpool));
3736   SVN_ERR(read_number_from_stream(&count, NULL, stream, iterpool));
3737
3738   /* Check revision range for validity. */
3739   if (   !same_shard(fs, revprops->revision, first_rev)
3740       || !same_shard(fs, revprops->revision, first_rev + count - 1)
3741       || count < 1)
3742     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3743                              _("Revprop pack for revision r%ld"
3744                                " contains revprops for r%ld .. r%ld"),
3745                              revprops->revision,
3746                              (svn_revnum_t)first_rev,
3747                              (svn_revnum_t)(first_rev + count -1));
3748
3749   /* Since start & end are in the same shard, it is enough to just test
3750    * the FIRST_REV for being actually packed.  That will also cover the
3751    * special case of rev 0 never being packed. */
3752   if (!is_packed_revprop(fs, first_rev))
3753     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
3754                              _("Revprop pack for revision r%ld"
3755                                " starts at non-packed revisions r%ld"),
3756                              revprops->revision, (svn_revnum_t)first_rev);
3757
3758   /* make PACKED_REVPROPS point to the first char after the header.
3759    * This is where the serialized revprops are. */
3760   header_end = strstr(uncompressed->data, "\n\n");
3761   if (header_end == NULL)
3762     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3763                             _("Header end not found"));
3764
3765   offset = header_end - uncompressed->data + 2;
3766
3767   revprops->packed_revprops = svn_stringbuf_create_empty(pool);
3768   revprops->packed_revprops->data = uncompressed->data + offset;
3769   revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
3770   revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset);
3771
3772   /* STREAM still points to the first entry in the sizes list.
3773    * Init / construct REVPROPS members. */
3774   revprops->start_revision = (svn_revnum_t)first_rev;
3775   revprops->sizes = apr_array_make(pool, (int)count, sizeof(offset));
3776   revprops->offsets = apr_array_make(pool, (int)count, sizeof(offset));
3777
3778   /* Now parse, revision by revision, the size and content of each
3779    * revisions' revprops. */
3780   for (i = 0, offset = 0, revprops->total_size = 0; i < count; ++i)
3781     {
3782       apr_int64_t size;
3783       svn_string_t serialized;
3784       apr_hash_t *properties;
3785       svn_revnum_t revision = (svn_revnum_t)(first_rev + i);
3786
3787       /* read & check the serialized size */
3788       SVN_ERR(read_number_from_stream(&size, NULL, stream, iterpool));
3789       if (size + offset > (apr_int64_t)revprops->packed_revprops->len)
3790         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
3791                         _("Packed revprop size exceeds pack file size"));
3792
3793       /* Parse this revprops list, if necessary */
3794       serialized.data = revprops->packed_revprops->data + offset;
3795       serialized.len = (apr_size_t)size;
3796
3797       if (revision == revprops->revision)
3798         {
3799           SVN_ERR(parse_revprop(&revprops->properties, fs, revision,
3800                                 revprops->generation, &serialized,
3801                                 pool, iterpool));
3802           revprops->serialized_size = serialized.len;
3803         }
3804       else
3805         {
3806           /* If revprop caching is enabled, parse any revprops.
3807            * They will get cached as a side-effect of this. */
3808           if (has_revprop_cache(fs, pool))
3809             SVN_ERR(parse_revprop(&properties, fs, revision,
3810                                   revprops->generation, &serialized,
3811                                   iterpool, iterpool));
3812         }
3813
3814       /* fill REVPROPS data structures */
3815       APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len;
3816       APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset;
3817       revprops->total_size += serialized.len;
3818
3819       offset += serialized.len;
3820
3821       svn_pool_clear(iterpool);
3822     }
3823
3824   return SVN_NO_ERROR;
3825 }
3826
3827 /* In filesystem FS, read the packed revprops for revision REV into
3828  * *REVPROPS.  Use GENERATION to populate the revprop cache, if enabled.
3829  * Allocate data in POOL.
3830  */
3831 static svn_error_t *
3832 read_pack_revprop(packed_revprops_t **revprops,
3833                   svn_fs_t *fs,
3834                   svn_revnum_t rev,
3835                   apr_int64_t generation,
3836                   apr_pool_t *pool)
3837 {
3838   apr_pool_t *iterpool = svn_pool_create(pool);
3839   svn_boolean_t missing = FALSE;
3840   svn_error_t *err;
3841   packed_revprops_t *result;
3842   int i;
3843
3844   /* someone insisted that REV is packed. Double-check if necessary */
3845   if (!is_packed_revprop(fs, rev))
3846      SVN_ERR(update_min_unpacked_rev(fs, iterpool));
3847
3848   if (!is_packed_revprop(fs, rev))
3849     return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
3850                               _("No such packed revision %ld"), rev);
3851
3852   /* initialize the result data structure */
3853   result = apr_pcalloc(pool, sizeof(*result));
3854   result->revision = rev;
3855   result->generation = generation;
3856
3857   /* try to read the packed revprops. This may require retries if we have
3858    * concurrent writers. */
3859   for (i = 0; i < RECOVERABLE_RETRY_COUNT && !result->packed_revprops; ++i)
3860     {
3861       const char *file_path;
3862
3863       /* there might have been concurrent writes.
3864        * Re-read the manifest and the pack file.
3865        */
3866       SVN_ERR(get_revprop_packname(fs, result, pool, iterpool));
3867       file_path  = svn_dirent_join(result->folder,
3868                                    result->filename,
3869                                    iterpool);
3870       SVN_ERR(try_stringbuf_from_file(&result->packed_revprops,
3871                                       &missing,
3872                                       file_path,
3873                                       i + 1 < RECOVERABLE_RETRY_COUNT,
3874                                       pool));
3875
3876       /* If we could not find the file, there was a write.
3877        * So, we should refresh our revprop generation info as well such
3878        * that others may find data we will put into the cache.  They would
3879        * consider it outdated, otherwise.
3880        */
3881       if (missing && has_revprop_cache(fs, pool))
3882         SVN_ERR(read_revprop_generation(&result->generation, fs, pool));
3883
3884       svn_pool_clear(iterpool);
3885     }
3886
3887   /* the file content should be available now */
3888   if (!result->packed_revprops)
3889     return svn_error_createf(SVN_ERR_FS_PACKED_REVPROP_READ_FAILURE, NULL,
3890                   _("Failed to read revprop pack file for r%ld"), rev);
3891
3892   /* parse it. RESULT will be complete afterwards. */
3893   err = parse_packed_revprops(fs, result, pool, iterpool);
3894   svn_pool_destroy(iterpool);
3895   if (err)
3896     return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
3897                   _("Revprop pack file for r%ld is corrupt"), rev);
3898
3899   *revprops = result;
3900
3901   return SVN_NO_ERROR;
3902 }
3903
3904 /* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
3905  *
3906  * Allocations will be done in POOL.
3907  */
3908 static svn_error_t *
3909 get_revision_proplist(apr_hash_t **proplist_p,
3910                       svn_fs_t *fs,
3911                       svn_revnum_t rev,
3912                       apr_pool_t *pool)
3913 {
3914   fs_fs_data_t *ffd = fs->fsap_data;
3915   apr_int64_t generation = 0;
3916
3917   /* not found, yet */
3918   *proplist_p = NULL;
3919
3920   /* should they be available at all? */
3921   SVN_ERR(ensure_revision_exists(fs, rev, pool));
3922
3923   /* Try cache lookup first. */
3924   if (has_revprop_cache(fs, pool))
3925     {
3926       svn_boolean_t is_cached;
3927       pair_cache_key_t key = { 0 };
3928
3929       SVN_ERR(read_revprop_generation(&generation, fs, pool));
3930
3931       key.revision = rev;
3932       key.second = generation;
3933       SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
3934                              ffd->revprop_cache, &key, pool));
3935       if (is_cached)
3936         return SVN_NO_ERROR;
3937     }
3938
3939   /* if REV had not been packed when we began, try reading it from the
3940    * non-packed shard.  If that fails, we will fall through to packed
3941    * shard reads. */
3942   if (!is_packed_revprop(fs, rev))
3943     {
3944       svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
3945                                                  generation, pool);
3946       if (err)
3947         {
3948           if (!APR_STATUS_IS_ENOENT(err->apr_err)
3949               || ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
3950             return svn_error_trace(err);
3951
3952           svn_error_clear(err);
3953           *proplist_p = NULL; /* in case read_non_packed_revprop changed it */
3954         }
3955     }
3956
3957   /* if revprop packing is available and we have not read the revprops, yet,
3958    * try reading them from a packed shard.  If that fails, REV is most
3959    * likely invalid (or its revprops highly contested). */
3960   if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p)
3961     {
3962       packed_revprops_t *packed_revprops;
3963       SVN_ERR(read_pack_revprop(&packed_revprops, fs, rev, generation, pool));
3964       *proplist_p = packed_revprops->properties;
3965     }
3966
3967   /* The revprops should have been there. Did we get them? */
3968   if (!*proplist_p)
3969     return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
3970                              _("Could not read revprops for revision %ld"),
3971                              rev);
3972
3973   return SVN_NO_ERROR;
3974 }
3975
3976 /* Serialize the revision property list PROPLIST of revision REV in
3977  * filesystem FS to a non-packed file.  Return the name of that temporary
3978  * file in *TMP_PATH and the file path that it must be moved to in
3979  * *FINAL_PATH.
3980  *
3981  * Use POOL for allocations.
3982  */
3983 static svn_error_t *
3984 write_non_packed_revprop(const char **final_path,
3985                          const char **tmp_path,
3986                          svn_fs_t *fs,
3987                          svn_revnum_t rev,
3988                          apr_hash_t *proplist,
3989                          apr_pool_t *pool)
3990 {
3991   svn_stream_t *stream;
3992   *final_path = path_revprops(fs, rev, pool);
3993
3994   /* ### do we have a directory sitting around already? we really shouldn't
3995      ### have to get the dirname here. */
3996   SVN_ERR(svn_stream_open_unique(&stream, tmp_path,
3997                                  svn_dirent_dirname(*final_path, pool),
3998                                  svn_io_file_del_none, pool, pool));
3999   SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4000   SVN_ERR(svn_stream_close(stream));
4001
4002   return SVN_NO_ERROR;
4003 }
4004
4005 /* After writing the new revprop file(s), call this function to move the
4006  * file at TMP_PATH to FINAL_PATH and give it the permissions from
4007  * PERMS_REFERENCE.
4008  *
4009  * If indicated in BUMP_GENERATION, increase FS' revprop generation.
4010  * Finally, delete all the temporary files given in FILES_TO_DELETE.
4011  * The latter may be NULL.
4012  *
4013  * Use POOL for temporary allocations.
4014  */
4015 static svn_error_t *
4016 switch_to_new_revprop(svn_fs_t *fs,
4017                       const char *final_path,
4018                       const char *tmp_path,
4019                       const char *perms_reference,
4020                       apr_array_header_t *files_to_delete,
4021                       svn_boolean_t bump_generation,
4022                       apr_pool_t *pool)
4023 {
4024   /* Now, we may actually be replacing revprops. Make sure that all other
4025      threads and processes will know about this. */
4026   if (bump_generation)
4027     SVN_ERR(begin_revprop_change(fs, pool));
4028
4029   SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool));
4030
4031   /* Indicate that the update (if relevant) has been completed. */
4032   if (bump_generation)
4033     SVN_ERR(end_revprop_change(fs, pool));
4034
4035   /* Clean up temporary files, if necessary. */
4036   if (files_to_delete)
4037     {
4038       apr_pool_t *iterpool = svn_pool_create(pool);
4039       int i;
4040
4041       for (i = 0; i < files_to_delete->nelts; ++i)
4042         {
4043           const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
4044           SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
4045           svn_pool_clear(iterpool);
4046         }
4047
4048       svn_pool_destroy(iterpool);
4049     }
4050   return SVN_NO_ERROR;
4051 }
4052
4053 /* Write a pack file header to STREAM that starts at revision START_REVISION
4054  * and contains the indexes [START,END) of SIZES.
4055  */
4056 static svn_error_t *
4057 serialize_revprops_header(svn_stream_t *stream,
4058                           svn_revnum_t start_revision,
4059                           apr_array_header_t *sizes,
4060                           int start,
4061                           int end,
4062                           apr_pool_t *pool)
4063 {
4064   apr_pool_t *iterpool = svn_pool_create(pool);
4065   int i;
4066
4067   SVN_ERR_ASSERT(start < end);
4068
4069   /* start revision and entry count */
4070   SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision));
4071   SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start));
4072
4073   /* the sizes array */
4074   for (i = start; i < end; ++i)
4075     {
4076       apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
4077       SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
4078                                 size));
4079     }
4080
4081   /* the double newline char indicates the end of the header */
4082   SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
4083
4084   svn_pool_clear(iterpool);
4085   return SVN_NO_ERROR;
4086 }
4087
4088 /* Writes the a pack file to FILE_STREAM.  It copies the serialized data
4089  * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
4090  *
4091  * The data for the latter is taken from NEW_SERIALIZED.  Note, that
4092  * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
4093  * taken in that case but only a subset of the old data will be copied.
4094  *
4095  * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
4096  * POOL is used for temporary allocations.
4097  */
4098 static svn_error_t *
4099 repack_revprops(svn_fs_t *fs,
4100                 packed_revprops_t *revprops,
4101                 int start,
4102                 int end,
4103                 int changed_index,
4104                 svn_stringbuf_t *new_serialized,
4105                 apr_off_t new_total_size,
4106                 svn_stream_t *file_stream,
4107                 apr_pool_t *pool)
4108 {
4109   fs_fs_data_t *ffd = fs->fsap_data;
4110   svn_stream_t *stream;
4111   int i;
4112
4113   /* create data empty buffers and the stream object */
4114   svn_stringbuf_t *uncompressed
4115     = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool);
4116   svn_stringbuf_t *compressed
4117     = svn_stringbuf_create_empty(pool);
4118   stream = svn_stream_from_stringbuf(uncompressed, pool);
4119
4120   /* write the header*/
4121   SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
4122                                     revprops->sizes, start, end, pool));
4123
4124   /* append the serialized revprops */
4125   for (i = start; i < end; ++i)
4126     if (i == changed_index)
4127       {
4128         SVN_ERR(svn_stream_write(stream,
4129                                  new_serialized->data,
4130                                  &new_serialized->len));
4131       }
4132     else
4133       {
4134         apr_size_t size
4135             = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
4136         apr_size_t offset
4137             = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
4138
4139         SVN_ERR(svn_stream_write(stream,
4140                                  revprops->packed_revprops->data + offset,
4141                                  &size));
4142       }
4143
4144   /* flush the stream buffer (if any) to our underlying data buffer */
4145   SVN_ERR(svn_stream_close(stream));
4146
4147   /* compress / store the data */
4148   SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
4149                         compressed,
4150                         ffd->compress_packed_revprops
4151                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
4152                           : SVN_DELTA_COMPRESSION_LEVEL_NONE));
4153
4154   /* finally, write the content to the target stream and close it */
4155   SVN_ERR(svn_stream_write(file_stream, compressed->data, &compressed->len));
4156   SVN_ERR(svn_stream_close(file_stream));
4157
4158   return SVN_NO_ERROR;
4159 }
4160
4161 /* Allocate a new pack file name for revisions
4162  *     [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1]
4163  * of REVPROPS->MANIFEST.  Add the name of old file to FILES_TO_DELETE,
4164  * auto-create that array if necessary.  Return an open file stream to
4165  * the new file in *STREAM allocated in POOL.
4166  */
4167 static svn_error_t *
4168 repack_stream_open(svn_stream_t **stream,
4169                    svn_fs_t *fs,
4170                    packed_revprops_t *revprops,
4171                    int start,
4172                    int end,
4173                    apr_array_header_t **files_to_delete,
4174                    apr_pool_t *pool)
4175 {
4176   apr_int64_t tag;
4177   const char *tag_string;
4178   svn_string_t *new_filename;
4179   int i;
4180   apr_file_t *file;
4181   int manifest_offset
4182     = (int)(revprops->start_revision - revprops->manifest_start);
4183
4184   /* get the old (= current) file name and enlist it for later deletion */
4185   const char *old_filename = APR_ARRAY_IDX(revprops->manifest,
4186                                            start + manifest_offset,
4187                                            const char*);
4188
4189   if (*files_to_delete == NULL)
4190     *files_to_delete = apr_array_make(pool, 3, sizeof(const char*));
4191
4192   APR_ARRAY_PUSH(*files_to_delete, const char*)
4193     = svn_dirent_join(revprops->folder, old_filename, pool);
4194
4195   /* increase the tag part, i.e. the counter after the dot */
4196   tag_string = strchr(old_filename, '.');
4197   if (tag_string == NULL)
4198     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4199                              _("Packed file '%s' misses a tag"),
4200                              old_filename);
4201
4202   SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
4203   new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT,
4204                                     revprops->start_revision + start,
4205                                     ++tag);
4206
4207   /* update the manifest to point to the new file */
4208   for (i = start; i < end; ++i)
4209     APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
4210       = new_filename->data;
4211
4212   /* create a file stream for the new file */
4213   SVN_ERR(svn_io_file_open(&file, svn_dirent_join(revprops->folder,
4214                                                   new_filename->data,
4215                                                   pool),
4216                            APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
4217   *stream = svn_stream_from_aprfile2(file, FALSE, pool);
4218
4219   return SVN_NO_ERROR;
4220 }
4221
4222 /* For revision REV in filesystem FS, set the revision properties to
4223  * PROPLIST.  Return a new file in *TMP_PATH that the caller shall move
4224  * to *FINAL_PATH to make the change visible.  Files to be deleted will
4225  * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
4226  * Use POOL for allocations.
4227  */
4228 static svn_error_t *
4229 write_packed_revprop(const char **final_path,
4230                      const char **tmp_path,
4231                      apr_array_header_t **files_to_delete,
4232                      svn_fs_t *fs,
4233                      svn_revnum_t rev,
4234                      apr_hash_t *proplist,
4235                      apr_pool_t *pool)
4236 {
4237   fs_fs_data_t *ffd = fs->fsap_data;
4238   packed_revprops_t *revprops;
4239   apr_int64_t generation = 0;
4240   svn_stream_t *stream;
4241   svn_stringbuf_t *serialized;
4242   apr_off_t new_total_size;
4243   int changed_index;
4244
4245   /* read the current revprop generation. This value will not change
4246    * while we hold the global write lock to this FS. */
4247   if (has_revprop_cache(fs, pool))
4248     SVN_ERR(read_revprop_generation(&generation, fs, pool));
4249
4250   /* read contents of the current pack file */
4251   SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool));
4252
4253   /* serialize the new revprops */
4254   serialized = svn_stringbuf_create_empty(pool);
4255   stream = svn_stream_from_stringbuf(serialized, pool);
4256   SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4257   SVN_ERR(svn_stream_close(stream));
4258
4259   /* calculate the size of the new data */
4260   changed_index = (int)(rev - revprops->start_revision);
4261   new_total_size = revprops->total_size - revprops->serialized_size
4262                  + serialized->len
4263                  + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
4264
4265   APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
4266
4267   /* can we put the new data into the same pack as the before? */
4268   if (   new_total_size < ffd->revprop_pack_size
4269       || revprops->sizes->nelts == 1)
4270     {
4271       /* simply replace the old pack file with new content as we do it
4272        * in the non-packed case */
4273
4274       *final_path = svn_dirent_join(revprops->folder, revprops->filename,
4275                                     pool);
4276       SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
4277                                      svn_io_file_del_none, pool, pool));
4278       SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
4279                               changed_index, serialized, new_total_size,
4280                               stream, pool));
4281     }
4282   else
4283     {
4284       /* split the pack file into two of roughly equal size */
4285       int right_count, left_count, i;
4286
4287       int left = 0;
4288       int right = revprops->sizes->nelts - 1;
4289       apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
4290       apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
4291
4292       /* let left and right side grow such that their size difference
4293        * is minimal after each step. */
4294       while (left <= right)
4295         if (  left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4296             < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
4297           {
4298             left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4299                       + SVN_INT64_BUFFER_SIZE;
4300             ++left;
4301           }
4302         else
4303           {
4304             right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
4305                         + SVN_INT64_BUFFER_SIZE;
4306             --right;
4307           }
4308
4309        /* since the items need much less than SVN_INT64_BUFFER_SIZE
4310         * bytes to represent their length, the split may not be optimal */
4311       left_count = left;
4312       right_count = revprops->sizes->nelts - left;
4313
4314       /* if new_size is large, one side may exceed the pack size limit.
4315        * In that case, split before and after the modified revprop.*/
4316       if (   left_size > ffd->revprop_pack_size
4317           || right_size > ffd->revprop_pack_size)
4318         {
4319           left_count = changed_index;
4320           right_count = revprops->sizes->nelts - left_count - 1;
4321         }
4322
4323       /* write the new, split files */
4324       if (left_count)
4325         {
4326           SVN_ERR(repack_stream_open(&stream, fs, revprops, 0,
4327                                      left_count, files_to_delete, pool));
4328           SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
4329                                   changed_index, serialized, new_total_size,
4330                                   stream, pool));
4331         }
4332
4333       if (left_count + right_count < revprops->sizes->nelts)
4334         {
4335           SVN_ERR(repack_stream_open(&stream, fs, revprops, changed_index,
4336                                      changed_index + 1, files_to_delete,
4337                                      pool));
4338           SVN_ERR(repack_revprops(fs, revprops, changed_index,
4339                                   changed_index + 1,
4340                                   changed_index, serialized, new_total_size,
4341                                   stream, pool));
4342         }
4343
4344       if (right_count)
4345         {
4346           SVN_ERR(repack_stream_open(&stream, fs, revprops,
4347                                      revprops->sizes->nelts - right_count,
4348                                      revprops->sizes->nelts,
4349                                      files_to_delete, pool));
4350           SVN_ERR(repack_revprops(fs, revprops,
4351                                   revprops->sizes->nelts - right_count,
4352                                   revprops->sizes->nelts, changed_index,
4353                                   serialized, new_total_size, stream,
4354                                   pool));
4355         }
4356
4357       /* write the new manifest */
4358       *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
4359       SVN_ERR(svn_stream_open_unique(&stream, tmp_path, revprops->folder,
4360                                      svn_io_file_del_none, pool, pool));
4361
4362       for (i = 0; i < revprops->manifest->nelts; ++i)
4363         {
4364           const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
4365                                                const char*);
4366           SVN_ERR(svn_stream_printf(stream, pool, "%s\n", filename));
4367         }
4368
4369       SVN_ERR(svn_stream_close(stream));
4370     }
4371
4372   return SVN_NO_ERROR;
4373 }
4374
4375 /* Set the revision property list of revision REV in filesystem FS to
4376    PROPLIST.  Use POOL for temporary allocations. */
4377 static svn_error_t *
4378 set_revision_proplist(svn_fs_t *fs,
4379                       svn_revnum_t rev,
4380                       apr_hash_t *proplist,
4381                       apr_pool_t *pool)
4382 {
4383   svn_boolean_t is_packed;
4384   svn_boolean_t bump_generation = FALSE;
4385   const char *final_path;
4386   const char *tmp_path;
4387   const char *perms_reference;
4388   apr_array_header_t *files_to_delete = NULL;
4389
4390   SVN_ERR(ensure_revision_exists(fs, rev, pool));
4391
4392   /* this info will not change while we hold the global FS write lock */
4393   is_packed = is_packed_revprop(fs, rev);
4394
4395   /* Test whether revprops already exist for this revision.
4396    * Only then will we need to bump the revprop generation. */
4397   if (has_revprop_cache(fs, pool))
4398     {
4399       if (is_packed)
4400         {
4401           bump_generation = TRUE;
4402         }
4403       else
4404         {
4405           svn_node_kind_t kind;
4406           SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind,
4407                                     pool));
4408           bump_generation = kind != svn_node_none;
4409         }
4410     }
4411
4412   /* Serialize the new revprop data */
4413   if (is_packed)
4414     SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
4415                                  fs, rev, proplist, pool));
4416   else
4417     SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
4418                                      fs, rev, proplist, pool));
4419
4420   /* We use the rev file of this revision as the perms reference,
4421    * because when setting revprops for the first time, the revprop
4422    * file won't exist and therefore can't serve as its own reference.
4423    * (Whereas the rev file should already exist at this point.)
4424    */
4425   SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool));
4426
4427   /* Now, switch to the new revprop data. */
4428   SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
4429                                 files_to_delete, bump_generation, pool));
4430
4431   return SVN_NO_ERROR;
4432 }
4433
4434 svn_error_t *
4435 svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
4436                              svn_fs_t *fs,
4437                              svn_revnum_t rev,
4438                              apr_pool_t *pool)
4439 {
4440   SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool));
4441
4442   return SVN_NO_ERROR;
4443 }
4444
4445 /* Represents where in the current svndiff data block each
4446    representation is. */
4447 struct rep_state
4448 {
4449   apr_file_t *file;
4450                     /* The txdelta window cache to use or NULL. */
4451   svn_cache__t *window_cache;
4452                     /* Caches un-deltified windows. May be NULL. */
4453   svn_cache__t *combined_cache;
4454   apr_off_t start;  /* The starting offset for the raw
4455                        svndiff/plaintext data minus header. */
4456   apr_off_t off;    /* The current offset into the file. */
4457   apr_off_t end;    /* The end offset of the raw data. */
4458   int ver;          /* If a delta, what svndiff version? */
4459   int chunk_index;
4460 };
4461
4462 /* See create_rep_state, which wraps this and adds another error. */
4463 static svn_error_t *
4464 create_rep_state_body(struct rep_state **rep_state,
4465                       struct rep_args **rep_args,
4466                       apr_file_t **file_hint,
4467                       svn_revnum_t *rev_hint,
4468                       representation_t *rep,
4469                       svn_fs_t *fs,
4470                       apr_pool_t *pool)
4471 {
4472   fs_fs_data_t *ffd = fs->fsap_data;
4473   struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
4474   struct rep_args *ra;
4475   unsigned char buf[4];
4476
4477   /* If the hint is
4478    * - given,
4479    * - refers to a valid revision,
4480    * - refers to a packed revision,
4481    * - as does the rep we want to read, and
4482    * - refers to the same pack file as the rep
4483    * ...
4484    */
4485   if (   file_hint && rev_hint && *file_hint
4486       && SVN_IS_VALID_REVNUM(*rev_hint)
4487       && *rev_hint < ffd->min_unpacked_rev
4488       && rep->revision < ffd->min_unpacked_rev
4489       && (   (*rev_hint / ffd->max_files_per_dir)
4490           == (rep->revision / ffd->max_files_per_dir)))
4491     {
4492       /* ... we can re-use the same, already open file object
4493        */
4494       apr_off_t offset;
4495       SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool));
4496
4497       offset += rep->offset;
4498       SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
4499
4500       rs->file = *file_hint;
4501     }
4502   else
4503     {
4504       /* otherwise, create a new file object
4505        */
4506       SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
4507     }
4508
4509   /* remember the current file, if suggested by the caller */
4510   if (file_hint)
4511     *file_hint = rs->file;
4512   if (rev_hint)
4513     *rev_hint = rep->revision;
4514
4515   /* continue constructing RS and RA */
4516   rs->window_cache = ffd->txdelta_window_cache;
4517   rs->combined_cache = ffd->combined_window_cache;
4518
4519   SVN_ERR(read_rep_line(&ra, rs->file, pool));
4520   SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
4521   rs->off = rs->start;
4522   rs->end = rs->start + rep->size;
4523   *rep_state = rs;
4524   *rep_args = ra;
4525
4526   if (!ra->is_delta)
4527     /* This is a plaintext, so just return the current rep_state. */
4528     return SVN_NO_ERROR;
4529
4530   /* We are dealing with a delta, find out what version. */
4531   SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf),
4532                                  NULL, NULL, pool));
4533   /* ### Layering violation */
4534   if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
4535     return svn_error_create
4536       (SVN_ERR_FS_CORRUPT, NULL,
4537        _("Malformed svndiff data in representation"));
4538   rs->ver = buf[3];
4539   rs->chunk_index = 0;
4540   rs->off += 4;
4541
4542   return SVN_NO_ERROR;
4543 }
4544
4545 /* Read the rep args for REP in filesystem FS and create a rep_state
4546    for reading the representation.  Return the rep_state in *REP_STATE
4547    and the rep args in *REP_ARGS, both allocated in POOL.
4548
4549    When reading multiple reps, i.e. a skip delta chain, you may provide
4550    non-NULL FILE_HINT and REV_HINT.  (If FILE_HINT is not NULL, in the first
4551    call it should be a pointer to NULL.)  The function will use these variables
4552    to store the previous call results and tries to re-use them.  This may
4553    result in significant savings in I/O for packed files.
4554  */
4555 static svn_error_t *
4556 create_rep_state(struct rep_state **rep_state,
4557                  struct rep_args **rep_args,
4558                  apr_file_t **file_hint,
4559                  svn_revnum_t *rev_hint,
4560                  representation_t *rep,
4561                  svn_fs_t *fs,
4562                  apr_pool_t *pool)
4563 {
4564   svn_error_t *err = create_rep_state_body(rep_state, rep_args,
4565                                            file_hint, rev_hint,
4566                                            rep, fs, pool);
4567   if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
4568     {
4569       fs_fs_data_t *ffd = fs->fsap_data;
4570
4571       /* ### This always returns "-1" for transaction reps, because
4572          ### this particular bit of code doesn't know if the rep is
4573          ### stored in the protorev or in the mutable area (for props
4574          ### or dir contents).  It is pretty rare for FSFS to *read*
4575          ### from the protorev file, though, so this is probably OK.
4576          ### And anyone going to debug corruption errors is probably
4577          ### going to jump straight to this comment anyway! */
4578       return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
4579                                "Corrupt representation '%s'",
4580                                rep
4581                                ? representation_string(rep, ffd->format, TRUE,
4582                                                        TRUE, pool)
4583                                : "(null)");
4584     }
4585   /* ### Call representation_string() ? */
4586   return svn_error_trace(err);
4587 }
4588
4589 struct rep_read_baton
4590 {
4591   /* The FS from which we're reading. */
4592   svn_fs_t *fs;
4593
4594   /* If not NULL, this is the base for the first delta window in rs_list */
4595   svn_stringbuf_t *base_window;
4596
4597   /* The state of all prior delta representations. */
4598   apr_array_header_t *rs_list;
4599
4600   /* The plaintext state, if there is a plaintext. */
4601   struct rep_state *src_state;
4602
4603   /* The index of the current delta chunk, if we are reading a delta. */
4604   int chunk_index;
4605
4606   /* The buffer where we store undeltified data. */
4607   char *buf;
4608   apr_size_t buf_pos;
4609   apr_size_t buf_len;
4610
4611   /* A checksum context for summing the data read in order to verify it.
4612      Note: we don't need to use the sha1 checksum because we're only doing
4613      data verification, for which md5 is perfectly safe.  */
4614   svn_checksum_ctx_t *md5_checksum_ctx;
4615
4616   svn_boolean_t checksum_finalized;
4617
4618   /* The stored checksum of the representation we are reading, its
4619      length, and the amount we've read so far.  Some of this
4620      information is redundant with rs_list and src_state, but it's
4621      convenient for the checksumming code to have it here. */
4622   svn_checksum_t *md5_checksum;
4623
4624   svn_filesize_t len;
4625   svn_filesize_t off;
4626
4627   /* The key for the fulltext cache for this rep, if there is a
4628      fulltext cache. */
4629   pair_cache_key_t fulltext_cache_key;
4630   /* The text we've been reading, if we're going to cache it. */
4631   svn_stringbuf_t *current_fulltext;
4632
4633   /* Used for temporary allocations during the read. */
4634   apr_pool_t *pool;
4635
4636   /* Pool used to store file handles and other data that is persistant
4637      for the entire stream read. */
4638   apr_pool_t *filehandle_pool;
4639 };
4640
4641 /* Combine the name of the rev file in RS with the given OFFSET to form
4642  * a cache lookup key.  Allocations will be made from POOL.  May return
4643  * NULL if the key cannot be constructed. */
4644 static const char*
4645 get_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool)
4646 {
4647   const char *name;
4648   const char *last_part;
4649   const char *name_last;
4650
4651   /* the rev file name containing the txdelta window.
4652    * If this fails we are in serious trouble anyways.
4653    * And if nobody else detects the problems, the file content checksum
4654    * comparison _will_ find them.
4655    */
4656   if (apr_file_name_get(&name, rs->file))
4657     return NULL;
4658
4659   /* Handle packed files as well by scanning backwards until we find the
4660    * revision or pack number. */
4661   name_last = name + strlen(name) - 1;
4662   while (! svn_ctype_isdigit(*name_last))
4663     --name_last;
4664
4665   last_part = name_last;
4666   while (svn_ctype_isdigit(*last_part))
4667     --last_part;
4668
4669   /* We must differentiate between packed files (as of today, the number
4670    * is being followed by a dot) and non-packed files (followed by \0).
4671    * Otherwise, there might be overlaps in the numbering range if the
4672    * repo gets packed after caching the txdeltas of non-packed revs.
4673    * => add the first non-digit char to the packed number. */
4674   if (name_last[1] != '\0')
4675     ++name_last;
4676
4677   /* copy one char MORE than the actual number to mark packed files,
4678    * i.e. packed revision file content uses different key space then
4679    * non-packed ones: keys for packed rev file content ends with a dot
4680    * for non-packed rev files they end with a digit. */
4681   name = apr_pstrndup(pool, last_part + 1, name_last - last_part);
4682   return svn_fs_fs__combine_number_and_string(offset, name, pool);
4683 }
4684
4685 /* Read the WINDOW_P for the rep state RS from the current FSFS session's
4686  * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4687  * cache has been given. If a cache is available IS_CACHED will inform
4688  * the caller about the success of the lookup. Allocations (of the window
4689  * in particualar) will be made from POOL.
4690  *
4691  * If the information could be found, put RS and the position within the
4692  * rev file into the same state as if the data had just been read from it.
4693  */
4694 static svn_error_t *
4695 get_cached_window(svn_txdelta_window_t **window_p,
4696                   struct rep_state *rs,
4697                   svn_boolean_t *is_cached,
4698                   apr_pool_t *pool)
4699 {
4700   if (! rs->window_cache)
4701     {
4702       /* txdelta window has not been enabled */
4703       *is_cached = FALSE;
4704     }
4705   else
4706     {
4707       /* ask the cache for the desired txdelta window */
4708       svn_fs_fs__txdelta_cached_window_t *cached_window;
4709       SVN_ERR(svn_cache__get((void **) &cached_window,
4710                              is_cached,
4711                              rs->window_cache,
4712                              get_window_key(rs, rs->off, pool),
4713                              pool));
4714
4715       if (*is_cached)
4716         {
4717           /* found it. Pass it back to the caller. */
4718           *window_p = cached_window->window;
4719
4720           /* manipulate the RS as if we just read the data */
4721           rs->chunk_index++;
4722           rs->off = cached_window->end_offset;
4723
4724           /* manipulate the rev file as if we just read from it */
4725           SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4726         }
4727     }
4728
4729   return SVN_NO_ERROR;
4730 }
4731
4732 /* Store the WINDOW read at OFFSET for the rep state RS in the current
4733  * FSFS session's cache. This will be a no-op if no cache has been given.
4734  * Temporary allocations will be made from SCRATCH_POOL. */
4735 static svn_error_t *
4736 set_cached_window(svn_txdelta_window_t *window,
4737                   struct rep_state *rs,
4738                   apr_off_t offset,
4739                   apr_pool_t *scratch_pool)
4740 {
4741   if (rs->window_cache)
4742     {
4743       /* store the window and the first offset _past_ it */
4744       svn_fs_fs__txdelta_cached_window_t cached_window;
4745
4746       cached_window.window = window;
4747       cached_window.end_offset = rs->off;
4748
4749       /* but key it with the start offset because that is the known state
4750        * when we will look it up */
4751       return svn_cache__set(rs->window_cache,
4752                             get_window_key(rs, offset, scratch_pool),
4753                             &cached_window,
4754                             scratch_pool);
4755     }
4756
4757   return SVN_NO_ERROR;
4758 }
4759
4760 /* Read the WINDOW_P for the rep state RS from the current FSFS session's
4761  * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4762  * cache has been given. If a cache is available IS_CACHED will inform
4763  * the caller about the success of the lookup. Allocations (of the window
4764  * in particualar) will be made from POOL.
4765  */
4766 static svn_error_t *
4767 get_cached_combined_window(svn_stringbuf_t **window_p,
4768                            struct rep_state *rs,
4769                            svn_boolean_t *is_cached,
4770                            apr_pool_t *pool)
4771 {
4772   if (! rs->combined_cache)
4773     {
4774       /* txdelta window has not been enabled */
4775       *is_cached = FALSE;
4776     }
4777   else
4778     {
4779       /* ask the cache for the desired txdelta window */
4780       return svn_cache__get((void **)window_p,
4781                             is_cached,
4782                             rs->combined_cache,
4783                             get_window_key(rs, rs->start, pool),
4784                             pool);
4785     }
4786
4787   return SVN_NO_ERROR;
4788 }
4789
4790 /* Store the WINDOW read at OFFSET for the rep state RS in the current
4791  * FSFS session's cache. This will be a no-op if no cache has been given.
4792  * Temporary allocations will be made from SCRATCH_POOL. */
4793 static svn_error_t *
4794 set_cached_combined_window(svn_stringbuf_t *window,
4795                            struct rep_state *rs,
4796                            apr_off_t offset,
4797                            apr_pool_t *scratch_pool)
4798 {
4799   if (rs->combined_cache)
4800     {
4801       /* but key it with the start offset because that is the known state
4802        * when we will look it up */
4803       return svn_cache__set(rs->combined_cache,
4804                             get_window_key(rs, offset, scratch_pool),
4805                             window,
4806                             scratch_pool);
4807     }
4808
4809   return SVN_NO_ERROR;
4810 }
4811
4812 /* Build an array of rep_state structures in *LIST giving the delta
4813    reps from first_rep to a plain-text or self-compressed rep.  Set
4814    *SRC_STATE to the plain-text rep we find at the end of the chain,
4815    or to NULL if the final delta representation is self-compressed.
4816    The representation to start from is designated by filesystem FS, id
4817    ID, and representation REP.
4818    Also, set *WINDOW_P to the base window content for *LIST, if it
4819    could be found in cache. Otherwise, *LIST will contain the base
4820    representation for the whole delta chain.
4821    Finally, return the expanded size of the representation in
4822    *EXPANDED_SIZE. It will take care of cases where only the on-disk
4823    size is known.  */
4824 static svn_error_t *
4825 build_rep_list(apr_array_header_t **list,
4826                svn_stringbuf_t **window_p,
4827                struct rep_state **src_state,
4828                svn_filesize_t *expanded_size,
4829                svn_fs_t *fs,
4830                representation_t *first_rep,
4831                apr_pool_t *pool)
4832 {
4833   representation_t rep;
4834   struct rep_state *rs = NULL;
4835   struct rep_args *rep_args;
4836   svn_boolean_t is_cached = FALSE;
4837   apr_file_t *last_file = NULL;
4838   svn_revnum_t last_revision;
4839
4840   *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
4841   rep = *first_rep;
4842
4843   /* The value as stored in the data struct.
4844      0 is either for unknown length or actually zero length. */
4845   *expanded_size = first_rep->expanded_size;
4846
4847   /* for the top-level rep, we need the rep_args */
4848   SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4849                            &last_revision, &rep, fs, pool));
4850
4851   /* Unknown size or empty representation?
4852      That implies the this being the first iteration.
4853      Usually size equals on-disk size, except for empty,
4854      compressed representations (delta, size = 4).
4855      Please note that for all non-empty deltas have
4856      a 4-byte header _plus_ some data. */
4857   if (*expanded_size == 0)
4858     if (! rep_args->is_delta || first_rep->size != 4)
4859       *expanded_size = first_rep->size;
4860
4861   while (1)
4862     {
4863       /* fetch state, if that has not been done already */
4864       if (!rs)
4865         SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4866                                 &last_revision, &rep, fs, pool));
4867
4868       SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
4869       if (is_cached)
4870         {
4871           /* We already have a reconstructed window in our cache.
4872              Write a pseudo rep_state with the full length. */
4873           rs->off = rs->start;
4874           rs->end = rs->start + (*window_p)->len;
4875           *src_state = rs;
4876           return SVN_NO_ERROR;
4877         }
4878
4879       if (!rep_args->is_delta)
4880         {
4881           /* This is a plaintext, so just return the current rep_state. */
4882           *src_state = rs;
4883           return SVN_NO_ERROR;
4884         }
4885
4886       /* Push this rep onto the list.  If it's self-compressed, we're done. */
4887       APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
4888       if (rep_args->is_delta_vs_empty)
4889         {
4890           *src_state = NULL;
4891           return SVN_NO_ERROR;
4892         }
4893
4894       rep.revision = rep_args->base_revision;
4895       rep.offset = rep_args->base_offset;
4896       rep.size = rep_args->base_length;
4897       rep.txn_id = NULL;
4898
4899       rs = NULL;
4900     }
4901 }
4902
4903
4904 /* Create a rep_read_baton structure for node revision NODEREV in
4905    filesystem FS and store it in *RB_P.  If FULLTEXT_CACHE_KEY is not
4906    NULL, it is the rep's key in the fulltext cache, and a stringbuf
4907    must be allocated to store the text.  Perform all allocations in
4908    POOL.  If rep is mutable, it must be for file contents. */
4909 static svn_error_t *
4910 rep_read_get_baton(struct rep_read_baton **rb_p,
4911                    svn_fs_t *fs,
4912                    representation_t *rep,
4913                    pair_cache_key_t fulltext_cache_key,
4914                    apr_pool_t *pool)
4915 {
4916   struct rep_read_baton *b;
4917
4918   b = apr_pcalloc(pool, sizeof(*b));
4919   b->fs = fs;
4920   b->base_window = NULL;
4921   b->chunk_index = 0;
4922   b->buf = NULL;
4923   b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
4924   b->checksum_finalized = FALSE;
4925   b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
4926   b->len = rep->expanded_size;
4927   b->off = 0;
4928   b->fulltext_cache_key = fulltext_cache_key;
4929   b->pool = svn_pool_create(pool);
4930   b->filehandle_pool = svn_pool_create(pool);
4931
4932   SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
4933                          &b->src_state, &b->len, fs, rep,
4934                          b->filehandle_pool));
4935
4936   if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
4937     b->current_fulltext = svn_stringbuf_create_ensure
4938                             ((apr_size_t)b->len,
4939                              b->filehandle_pool);
4940   else
4941     b->current_fulltext = NULL;
4942
4943   /* Save our output baton. */
4944   *rb_p = b;
4945
4946   return SVN_NO_ERROR;
4947 }
4948
4949 /* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
4950    window into *NWIN. */
4951 static svn_error_t *
4952 read_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
4953                   struct rep_state *rs, apr_pool_t *pool)
4954 {
4955   svn_stream_t *stream;
4956   svn_boolean_t is_cached;
4957   apr_off_t old_offset;
4958
4959   SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
4960
4961   /* RS->FILE may be shared between RS instances -> make sure we point
4962    * to the right data. */
4963   SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4964
4965   /* Skip windows to reach the current chunk if we aren't there yet. */
4966   while (rs->chunk_index < this_chunk)
4967     {
4968       SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
4969       rs->chunk_index++;
4970       SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4971       if (rs->off >= rs->end)
4972         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4973                                 _("Reading one svndiff window read "
4974                                   "beyond the end of the "
4975                                   "representation"));
4976     }
4977
4978   /* Read the next window. But first, try to find it in the cache. */
4979   SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool));
4980   if (is_cached)
4981     return SVN_NO_ERROR;
4982
4983   /* Actually read the next window. */
4984   old_offset = rs->off;
4985   stream = svn_stream_from_aprfile2(rs->file, TRUE, pool);
4986   SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
4987   rs->chunk_index++;
4988   SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4989
4990   if (rs->off > rs->end)
4991     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4992                             _("Reading one svndiff window read beyond "
4993                               "the end of the representation"));
4994
4995   /* the window has not been cached before, thus cache it now
4996    * (if caching is used for them at all) */
4997   return set_cached_window(*nwin, rs, old_offset, pool);
4998 }
4999
5000 /* Read SIZE bytes from the representation RS and return it in *NWIN. */
5001 static svn_error_t *
5002 read_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs,
5003                   apr_size_t size, apr_pool_t *pool)
5004 {
5005   /* RS->FILE may be shared between RS instances -> make sure we point
5006    * to the right data. */
5007   SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
5008
5009   /* Read the plain data. */
5010   *nwin = svn_stringbuf_create_ensure(size, pool);
5011   SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL,
5012                                  pool));
5013   (*nwin)->data[size] = 0;
5014
5015   /* Update RS. */
5016   rs->off += (apr_off_t)size;
5017
5018   return SVN_NO_ERROR;
5019 }
5020
5021 /* Get the undeltified window that is a result of combining all deltas
5022    from the current desired representation identified in *RB with its
5023    base representation.  Store the window in *RESULT. */
5024 static svn_error_t *
5025 get_combined_window(svn_stringbuf_t **result,
5026                     struct rep_read_baton *rb)
5027 {
5028   apr_pool_t *pool, *new_pool, *window_pool;
5029   int i;
5030   svn_txdelta_window_t *window;
5031   apr_array_header_t *windows;
5032   svn_stringbuf_t *source, *buf = rb->base_window;
5033   struct rep_state *rs;
5034
5035   /* Read all windows that we need to combine. This is fine because
5036      the size of each window is relatively small (100kB) and skip-
5037      delta limits the number of deltas in a chain to well under 100.
5038      Stop early if one of them does not depend on its predecessors. */
5039   window_pool = svn_pool_create(rb->pool);
5040   windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
5041   for (i = 0; i < rb->rs_list->nelts; ++i)
5042     {
5043       rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5044       SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool));
5045
5046       APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
5047       if (window->src_ops == 0)
5048         {
5049           ++i;
5050           break;
5051         }
5052     }
5053
5054   /* Combine in the windows from the other delta reps. */
5055   pool = svn_pool_create(rb->pool);
5056   for (--i; i >= 0; --i)
5057     {
5058
5059       rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5060       window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
5061
5062       /* Maybe, we've got a PLAIN start representation.  If we do, read
5063          as much data from it as the needed for the txdelta window's source
5064          view.
5065          Note that BUF / SOURCE may only be NULL in the first iteration. */
5066       source = buf;
5067       if (source == NULL && rb->src_state != NULL)
5068         SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len,
5069                                   pool));
5070
5071       /* Combine this window with the current one. */
5072       new_pool = svn_pool_create(rb->pool);
5073       buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
5074       buf->len = window->tview_len;
5075
5076       svn_txdelta_apply_instructions(window, source ? source->data : NULL,
5077                                      buf->data, &buf->len);
5078       if (buf->len != window->tview_len)
5079         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5080                                 _("svndiff window length is "
5081                                   "corrupt"));
5082
5083       /* Cache windows only if the whole rep content could be read as a
5084          single chunk.  Only then will no other chunk need a deeper RS
5085          list than the cached chunk. */
5086       if ((rb->chunk_index == 0) && (rs->off == rs->end))
5087         SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
5088
5089       /* Cycle pools so that we only need to hold three windows at a time. */
5090       svn_pool_destroy(pool);
5091       pool = new_pool;
5092     }
5093
5094   svn_pool_destroy(window_pool);
5095
5096   *result = buf;
5097   return SVN_NO_ERROR;
5098 }
5099
5100 /* Returns whether or not the expanded fulltext of the file is cachable
5101  * based on its size SIZE.  The decision depends on the cache used by RB.
5102  */
5103 static svn_boolean_t
5104 fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
5105 {
5106   return (size < APR_SIZE_MAX)
5107       && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
5108 }
5109
5110 /* Close method used on streams returned by read_representation().
5111  */
5112 static svn_error_t *
5113 rep_read_contents_close(void *baton)
5114 {
5115   struct rep_read_baton *rb = baton;
5116
5117   svn_pool_destroy(rb->pool);
5118   svn_pool_destroy(rb->filehandle_pool);
5119
5120   return SVN_NO_ERROR;
5121 }
5122
5123 /* Return the next *LEN bytes of the rep and store them in *BUF. */
5124 static svn_error_t *
5125 get_contents(struct rep_read_baton *rb,
5126              char *buf,
5127              apr_size_t *len)
5128 {
5129   apr_size_t copy_len, remaining = *len;
5130   char *cur = buf;
5131   struct rep_state *rs;
5132
5133   /* Special case for when there are no delta reps, only a plain
5134      text. */
5135   if (rb->rs_list->nelts == 0)
5136     {
5137       copy_len = remaining;
5138       rs = rb->src_state;
5139
5140       if (rb->base_window != NULL)
5141         {
5142           /* We got the desired rep directly from the cache.
5143              This is where we need the pseudo rep_state created
5144              by build_rep_list(). */
5145           apr_size_t offset = (apr_size_t)(rs->off - rs->start);
5146           if (copy_len + offset > rb->base_window->len)
5147             copy_len = offset < rb->base_window->len
5148                      ? rb->base_window->len - offset
5149                      : 0ul;
5150
5151           memcpy (cur, rb->base_window->data + offset, copy_len);
5152         }
5153       else
5154         {
5155           if (((apr_off_t) copy_len) > rs->end - rs->off)
5156             copy_len = (apr_size_t) (rs->end - rs->off);
5157           SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
5158                                          NULL, rb->pool));
5159         }
5160
5161       rs->off += copy_len;
5162       *len = copy_len;
5163       return SVN_NO_ERROR;
5164     }
5165
5166   while (remaining > 0)
5167     {
5168       /* If we have buffered data from a previous chunk, use that. */
5169       if (rb->buf)
5170         {
5171           /* Determine how much to copy from the buffer. */
5172           copy_len = rb->buf_len - rb->buf_pos;
5173           if (copy_len > remaining)
5174             copy_len = remaining;
5175
5176           /* Actually copy the data. */
5177           memcpy(cur, rb->buf + rb->buf_pos, copy_len);
5178           rb->buf_pos += copy_len;
5179           cur += copy_len;
5180           remaining -= copy_len;
5181
5182           /* If the buffer is all used up, clear it and empty the
5183              local pool. */
5184           if (rb->buf_pos == rb->buf_len)
5185             {
5186               svn_pool_clear(rb->pool);
5187               rb->buf = NULL;
5188             }
5189         }
5190       else
5191         {
5192           svn_stringbuf_t *sbuf = NULL;
5193
5194           rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
5195           if (rs->off == rs->end)
5196             break;
5197
5198           /* Get more buffered data by evaluating a chunk. */
5199           SVN_ERR(get_combined_window(&sbuf, rb));
5200
5201           rb->chunk_index++;
5202           rb->buf_len = sbuf->len;
5203           rb->buf = sbuf->data;
5204           rb->buf_pos = 0;
5205         }
5206     }
5207
5208   *len = cur - buf;
5209
5210   return SVN_NO_ERROR;
5211 }
5212
5213 /* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
5214    representation and store them in *BUF.  Sum as we read and verify
5215    the MD5 sum at the end. */
5216 static svn_error_t *
5217 rep_read_contents(void *baton,
5218                   char *buf,
5219                   apr_size_t *len)
5220 {
5221   struct rep_read_baton *rb = baton;
5222
5223   /* Get the next block of data. */
5224   SVN_ERR(get_contents(rb, buf, len));
5225
5226   if (rb->current_fulltext)
5227     svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
5228
5229   /* Perform checksumming.  We want to check the checksum as soon as
5230      the last byte of data is read, in case the caller never performs
5231      a short read, but we don't want to finalize the MD5 context
5232      twice. */
5233   if (!rb->checksum_finalized)
5234     {
5235       SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
5236       rb->off += *len;
5237       if (rb->off == rb->len)
5238         {
5239           svn_checksum_t *md5_checksum;
5240
5241           rb->checksum_finalized = TRUE;
5242           SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
5243                                      rb->pool));
5244           if (!svn_checksum_match(md5_checksum, rb->md5_checksum))
5245             return svn_error_create(SVN_ERR_FS_CORRUPT,
5246                     svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum,
5247                         rb->pool,
5248                         _("Checksum mismatch while reading representation")),
5249                     NULL);
5250         }
5251     }
5252
5253   if (rb->off == rb->len && rb->current_fulltext)
5254     {
5255       fs_fs_data_t *ffd = rb->fs->fsap_data;
5256       SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
5257                              rb->current_fulltext, rb->pool));
5258       rb->current_fulltext = NULL;
5259     }
5260
5261   return SVN_NO_ERROR;
5262 }
5263
5264
5265 /* Return a stream in *CONTENTS_P that will read the contents of a
5266    representation stored at the location given by REP.  Appropriate
5267    for any kind of immutable representation, but only for file
5268    contents (not props or directory contents) in mutable
5269    representations.
5270
5271    If REP is NULL, the representation is assumed to be empty, and the
5272    empty stream is returned.
5273 */
5274 static svn_error_t *
5275 read_representation(svn_stream_t **contents_p,
5276                     svn_fs_t *fs,
5277                     representation_t *rep,
5278                     apr_pool_t *pool)
5279 {
5280   if (! rep)
5281     {
5282       *contents_p = svn_stream_empty(pool);
5283     }
5284   else
5285     {
5286       fs_fs_data_t *ffd = fs->fsap_data;
5287       pair_cache_key_t fulltext_cache_key = { 0 };
5288       svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
5289       struct rep_read_baton *rb;
5290
5291       fulltext_cache_key.revision = rep->revision;
5292       fulltext_cache_key.second = rep->offset;
5293       if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5294           && fulltext_size_is_cachable(ffd, len))
5295         {
5296           svn_stringbuf_t *fulltext;
5297           svn_boolean_t is_cached;
5298           SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
5299                                  ffd->fulltext_cache, &fulltext_cache_key,
5300                                  pool));
5301           if (is_cached)
5302             {
5303               *contents_p = svn_stream_from_stringbuf(fulltext, pool);
5304               return SVN_NO_ERROR;
5305             }
5306         }
5307       else
5308         fulltext_cache_key.revision = SVN_INVALID_REVNUM;
5309
5310       SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
5311
5312       *contents_p = svn_stream_create(rb, pool);
5313       svn_stream_set_read(*contents_p, rep_read_contents);
5314       svn_stream_set_close(*contents_p, rep_read_contents_close);
5315     }
5316
5317   return SVN_NO_ERROR;
5318 }
5319
5320 svn_error_t *
5321 svn_fs_fs__get_contents(svn_stream_t **contents_p,
5322                         svn_fs_t *fs,
5323                         node_revision_t *noderev,
5324                         apr_pool_t *pool)
5325 {
5326   return read_representation(contents_p, fs, noderev->data_rep, pool);
5327 }
5328
5329 /* Baton used when reading delta windows. */
5330 struct delta_read_baton
5331 {
5332   struct rep_state *rs;
5333   svn_checksum_t *checksum;
5334 };
5335
5336 /* This implements the svn_txdelta_next_window_fn_t interface. */
5337 static svn_error_t *
5338 delta_read_next_window(svn_txdelta_window_t **window, void *baton,
5339                        apr_pool_t *pool)
5340 {
5341   struct delta_read_baton *drb = baton;
5342
5343   if (drb->rs->off == drb->rs->end)
5344     {
5345       *window = NULL;
5346       return SVN_NO_ERROR;
5347     }
5348
5349   return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool);
5350 }
5351
5352 /* This implements the svn_txdelta_md5_digest_fn_t interface. */
5353 static const unsigned char *
5354 delta_read_md5_digest(void *baton)
5355 {
5356   struct delta_read_baton *drb = baton;
5357
5358   if (drb->checksum->kind == svn_checksum_md5)
5359     return drb->checksum->digest;
5360   else
5361     return NULL;
5362 }
5363
5364 svn_error_t *
5365 svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
5366                                  svn_fs_t *fs,
5367                                  node_revision_t *source,
5368                                  node_revision_t *target,
5369                                  apr_pool_t *pool)
5370 {
5371   svn_stream_t *source_stream, *target_stream;
5372
5373   /* Try a shortcut: if the target is stored as a delta against the source,
5374      then just use that delta. */
5375   if (source && source->data_rep && target->data_rep)
5376     {
5377       struct rep_state *rep_state;
5378       struct rep_args *rep_args;
5379
5380       /* Read target's base rep if any. */
5381       SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
5382                                target->data_rep, fs, pool));
5383
5384       /* If that matches source, then use this delta as is.
5385          Note that we want an actual delta here.  E.g. a self-delta would
5386          not be good enough. */
5387       if (rep_args->is_delta
5388           && rep_args->base_revision == source->data_rep->revision
5389           && rep_args->base_offset == source->data_rep->offset)
5390         {
5391           /* Create the delta read baton. */
5392           struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
5393           drb->rs = rep_state;
5394           drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum,
5395                                            pool);
5396           *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
5397                                                 delta_read_md5_digest, pool);
5398           return SVN_NO_ERROR;
5399         }
5400       else
5401         SVN_ERR(svn_io_file_close(rep_state->file, pool));
5402     }
5403
5404   /* Read both fulltexts and construct a delta. */
5405   if (source)
5406     SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
5407   else
5408     source_stream = svn_stream_empty(pool);
5409   SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
5410
5411   /* Because source and target stream will already verify their content,
5412    * there is no need to do this once more.  In particular if the stream
5413    * content is being fetched from cache. */
5414   svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
5415
5416   return SVN_NO_ERROR;
5417 }
5418
5419 /* Baton for cache_access_wrapper. Wraps the original parameters of
5420  * svn_fs_fs__try_process_file_content().
5421  */
5422 typedef struct cache_access_wrapper_baton_t
5423 {
5424   svn_fs_process_contents_func_t func;
5425   void* baton;
5426 } cache_access_wrapper_baton_t;
5427
5428 /* Wrapper to translate between svn_fs_process_contents_func_t and
5429  * svn_cache__partial_getter_func_t.
5430  */
5431 static svn_error_t *
5432 cache_access_wrapper(void **out,
5433                      const void *data,
5434                      apr_size_t data_len,
5435                      void *baton,
5436                      apr_pool_t *pool)
5437 {
5438   cache_access_wrapper_baton_t *wrapper_baton = baton;
5439
5440   SVN_ERR(wrapper_baton->func((const unsigned char *)data,
5441                               data_len - 1, /* cache adds terminating 0 */
5442                               wrapper_baton->baton,
5443                               pool));
5444
5445   /* non-NULL value to signal the calling cache that all went well */
5446   *out = baton;
5447
5448   return SVN_NO_ERROR;
5449 }
5450
5451 svn_error_t *
5452 svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
5453                                      svn_fs_t *fs,
5454                                      node_revision_t *noderev,
5455                                      svn_fs_process_contents_func_t processor,
5456                                      void* baton,
5457                                      apr_pool_t *pool)
5458 {
5459   representation_t *rep = noderev->data_rep;
5460   if (rep)
5461     {
5462       fs_fs_data_t *ffd = fs->fsap_data;
5463       pair_cache_key_t fulltext_cache_key = { 0 };
5464
5465       fulltext_cache_key.revision = rep->revision;
5466       fulltext_cache_key.second = rep->offset;
5467       if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5468           && fulltext_size_is_cachable(ffd, rep->expanded_size))
5469         {
5470           cache_access_wrapper_baton_t wrapper_baton;
5471           void *dummy = NULL;
5472
5473           wrapper_baton.func = processor;
5474           wrapper_baton.baton = baton;
5475           return svn_cache__get_partial(&dummy, success,
5476                                         ffd->fulltext_cache,
5477                                         &fulltext_cache_key,
5478                                         cache_access_wrapper,
5479                                         &wrapper_baton,
5480                                         pool);
5481         }
5482     }
5483
5484   *success = FALSE;
5485   return SVN_NO_ERROR;
5486 }
5487
5488 /* Fetch the contents of a directory into ENTRIES.  Values are stored
5489    as filename to string mappings; further conversion is necessary to
5490    convert them into svn_fs_dirent_t values. */
5491 static svn_error_t *
5492 get_dir_contents(apr_hash_t *entries,
5493                  svn_fs_t *fs,
5494                  node_revision_t *noderev,
5495                  apr_pool_t *pool)
5496 {
5497   svn_stream_t *contents;
5498
5499   if (noderev->data_rep && noderev->data_rep->txn_id)
5500     {
5501       const char *filename = path_txn_node_children(fs, noderev->id, pool);
5502
5503       /* The representation is mutable.  Read the old directory
5504          contents from the mutable children file, followed by the
5505          changes we've made in this transaction. */
5506       SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
5507       SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5508       SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
5509       SVN_ERR(svn_stream_close(contents));
5510     }
5511   else if (noderev->data_rep)
5512     {
5513       /* use a temporary pool for temp objects.
5514        * Also undeltify content before parsing it. Otherwise, we could only
5515        * parse it byte-by-byte.
5516        */
5517       apr_pool_t *text_pool = svn_pool_create(pool);
5518       apr_size_t len = noderev->data_rep->expanded_size
5519                      ? (apr_size_t)noderev->data_rep->expanded_size
5520                      : (apr_size_t)noderev->data_rep->size;
5521       svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
5522       text->len = len;
5523
5524       /* The representation is immutable.  Read it normally. */
5525       SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool));
5526       SVN_ERR(svn_stream_read(contents, text->data, &text->len));
5527       SVN_ERR(svn_stream_close(contents));
5528
5529       /* de-serialize hash */
5530       contents = svn_stream_from_stringbuf(text, text_pool);
5531       SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5532
5533       svn_pool_destroy(text_pool);
5534     }
5535
5536   return SVN_NO_ERROR;
5537 }
5538
5539
5540 static const char *
5541 unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
5542                   apr_pool_t *pool)
5543 {
5544   return apr_psprintf(pool, "%s %s",
5545                       (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
5546                       svn_fs_fs__id_unparse(id, pool)->data);
5547 }
5548
5549 /* Given a hash ENTRIES of dirent structions, return a hash in
5550    *STR_ENTRIES_P, that has svn_string_t as the values in the format
5551    specified by the fs_fs directory contents file.  Perform
5552    allocations in POOL. */
5553 static svn_error_t *
5554 unparse_dir_entries(apr_hash_t **str_entries_p,
5555                     apr_hash_t *entries,
5556                     apr_pool_t *pool)
5557 {
5558   apr_hash_index_t *hi;
5559
5560   /* For now, we use a our own hash function to ensure that we get a
5561    * (largely) stable order when serializing the data.  It also gives
5562    * us some performance improvement.
5563    *
5564    * ### TODO ###
5565    * Use some sorted or other fixed order data container.
5566    */
5567   *str_entries_p = svn_hash__make(pool);
5568
5569   for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
5570     {
5571       const void *key;
5572       apr_ssize_t klen;
5573       svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
5574       const char *new_val;
5575
5576       apr_hash_this(hi, &key, &klen, NULL);
5577       new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
5578       apr_hash_set(*str_entries_p, key, klen,
5579                    svn_string_create(new_val, pool));
5580     }
5581
5582   return SVN_NO_ERROR;
5583 }
5584
5585
5586 /* Given a hash STR_ENTRIES with values as svn_string_t as specified
5587    in an FSFS directory contents listing, return a hash of dirents in
5588    *ENTRIES_P.  Perform allocations in POOL. */
5589 static svn_error_t *
5590 parse_dir_entries(apr_hash_t **entries_p,
5591                   apr_hash_t *str_entries,
5592                   const char *unparsed_id,
5593                   apr_pool_t *pool)
5594 {
5595   apr_hash_index_t *hi;
5596
5597   *entries_p = apr_hash_make(pool);
5598
5599   /* Translate the string dir entries into real entries. */
5600   for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
5601     {
5602       const char *name = svn__apr_hash_index_key(hi);
5603       svn_string_t *str_val = svn__apr_hash_index_val(hi);
5604       char *str, *last_str;
5605       svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
5606
5607       last_str = apr_pstrdup(pool, str_val->data);
5608       dirent->name = apr_pstrdup(pool, name);
5609
5610       str = svn_cstring_tokenize(" ", &last_str);
5611       if (str == NULL)
5612         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5613                                  _("Directory entry corrupt in '%s'"),
5614                                  unparsed_id);
5615
5616       if (strcmp(str, KIND_FILE) == 0)
5617         {
5618           dirent->kind = svn_node_file;
5619         }
5620       else if (strcmp(str, KIND_DIR) == 0)
5621         {
5622           dirent->kind = svn_node_dir;
5623         }
5624       else
5625         {
5626           return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5627                                    _("Directory entry corrupt in '%s'"),
5628                                    unparsed_id);
5629         }
5630
5631       str = svn_cstring_tokenize(" ", &last_str);
5632       if (str == NULL)
5633           return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5634                                    _("Directory entry corrupt in '%s'"),
5635                                    unparsed_id);
5636
5637       dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
5638
5639       svn_hash_sets(*entries_p, dirent->name, dirent);
5640     }
5641
5642   return SVN_NO_ERROR;
5643 }
5644
5645 /* Return the cache object in FS responsible to storing the directory
5646  * the NODEREV. If none exists, return NULL. */
5647 static svn_cache__t *
5648 locate_dir_cache(svn_fs_t *fs,
5649                  node_revision_t *noderev)
5650 {
5651   fs_fs_data_t *ffd = fs->fsap_data;
5652   return svn_fs_fs__id_txn_id(noderev->id)
5653       ? ffd->txn_dir_cache
5654       : ffd->dir_cache;
5655 }
5656
5657 svn_error_t *
5658 svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
5659                             svn_fs_t *fs,
5660                             node_revision_t *noderev,
5661                             apr_pool_t *pool)
5662 {
5663   const char *unparsed_id = NULL;
5664   apr_hash_t *unparsed_entries, *parsed_entries;
5665
5666   /* find the cache we may use */
5667   svn_cache__t *cache = locate_dir_cache(fs, noderev);
5668   if (cache)
5669     {
5670       svn_boolean_t found;
5671
5672       unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
5673       SVN_ERR(svn_cache__get((void **) entries_p, &found, cache,
5674                              unparsed_id, pool));
5675       if (found)
5676         return SVN_NO_ERROR;
5677     }
5678
5679   /* Read in the directory hash. */
5680   unparsed_entries = apr_hash_make(pool);
5681   SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
5682   SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
5683                             unparsed_id, pool));
5684
5685   /* Update the cache, if we are to use one. */
5686   if (cache)
5687     SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool));
5688
5689   *entries_p = parsed_entries;
5690   return SVN_NO_ERROR;
5691 }
5692
5693 svn_error_t *
5694 svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
5695                                   svn_fs_t *fs,
5696                                   node_revision_t *noderev,
5697                                   const char *name,
5698                                   apr_pool_t *result_pool,
5699                                   apr_pool_t *scratch_pool)
5700 {
5701   svn_boolean_t found = FALSE;
5702
5703   /* find the cache we may use */
5704   svn_cache__t *cache = locate_dir_cache(fs, noderev);
5705   if (cache)
5706     {
5707       const char *unparsed_id =
5708         svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data;
5709
5710       /* Cache lookup. */
5711       SVN_ERR(svn_cache__get_partial((void **)dirent,
5712                                      &found,
5713                                      cache,
5714                                      unparsed_id,
5715                                      svn_fs_fs__extract_dir_entry,
5716                                      (void*)name,
5717                                      result_pool));
5718     }
5719
5720   /* fetch data from disk if we did not find it in the cache */
5721   if (! found)
5722     {
5723       apr_hash_t *entries;
5724       svn_fs_dirent_t *entry;
5725       svn_fs_dirent_t *entry_copy = NULL;
5726
5727       /* read the dir from the file system. It will probably be put it
5728          into the cache for faster lookup in future calls. */
5729       SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
5730                                           scratch_pool));
5731
5732       /* find desired entry and return a copy in POOL, if found */
5733       entry = svn_hash_gets(entries, name);
5734       if (entry != NULL)
5735         {
5736           entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
5737           entry_copy->name = apr_pstrdup(result_pool, entry->name);
5738           entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool);
5739           entry_copy->kind = entry->kind;
5740         }
5741
5742       *dirent = entry_copy;
5743     }
5744
5745   return SVN_NO_ERROR;
5746 }
5747
5748 svn_error_t *
5749 svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
5750                         svn_fs_t *fs,
5751                         node_revision_t *noderev,
5752                         apr_pool_t *pool)
5753 {
5754   apr_hash_t *proplist;
5755   svn_stream_t *stream;
5756
5757   if (noderev->prop_rep && noderev->prop_rep->txn_id)
5758     {
5759       const char *filename = path_txn_node_props(fs, noderev->id, pool);
5760       proplist = apr_hash_make(pool);
5761
5762       SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
5763       SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5764       SVN_ERR(svn_stream_close(stream));
5765     }
5766   else if (noderev->prop_rep)
5767     {
5768       fs_fs_data_t *ffd = fs->fsap_data;
5769       representation_t *rep = noderev->prop_rep;
5770       pair_cache_key_t key = { 0 };
5771
5772       key.revision = rep->revision;
5773       key.second = rep->offset;
5774       if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5775         {
5776           svn_boolean_t is_cached;
5777           SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
5778                                  ffd->properties_cache, &key, pool));
5779           if (is_cached)
5780             return SVN_NO_ERROR;
5781         }
5782
5783       proplist = apr_hash_make(pool);
5784       SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
5785       SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5786       SVN_ERR(svn_stream_close(stream));
5787
5788       if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5789         SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
5790     }
5791   else
5792     {
5793       /* return an empty prop list if the node doesn't have any props */
5794       proplist = apr_hash_make(pool);
5795     }
5796
5797   *proplist_p = proplist;
5798
5799   return SVN_NO_ERROR;
5800 }
5801
5802 svn_error_t *
5803 svn_fs_fs__file_length(svn_filesize_t *length,
5804                        node_revision_t *noderev,
5805                        apr_pool_t *pool)
5806 {
5807   if (noderev->data_rep)
5808     *length = noderev->data_rep->expanded_size;
5809   else
5810     *length = 0;
5811
5812   return SVN_NO_ERROR;
5813 }
5814
5815 svn_boolean_t
5816 svn_fs_fs__noderev_same_rep_key(representation_t *a,
5817                                 representation_t *b)
5818 {
5819   if (a == b)
5820     return TRUE;
5821
5822   if (a == NULL || b == NULL)
5823     return FALSE;
5824
5825   if (a->offset != b->offset)
5826     return FALSE;
5827
5828   if (a->revision != b->revision)
5829     return FALSE;
5830
5831   if (a->uniquifier == b->uniquifier)
5832     return TRUE;
5833
5834   if (a->uniquifier == NULL || b->uniquifier == NULL)
5835     return FALSE;
5836
5837   return strcmp(a->uniquifier, b->uniquifier) == 0;
5838 }
5839
5840 svn_error_t *
5841 svn_fs_fs__file_checksum(svn_checksum_t **checksum,
5842                          node_revision_t *noderev,
5843                          svn_checksum_kind_t kind,
5844                          apr_pool_t *pool)
5845 {
5846   if (noderev->data_rep)
5847     {
5848       switch(kind)
5849         {
5850           case svn_checksum_md5:
5851             *checksum = svn_checksum_dup(noderev->data_rep->md5_checksum,
5852                                          pool);
5853             break;
5854           case svn_checksum_sha1:
5855             *checksum = svn_checksum_dup(noderev->data_rep->sha1_checksum,
5856                                          pool);
5857             break;
5858           default:
5859             *checksum = NULL;
5860         }
5861     }
5862   else
5863     *checksum = NULL;
5864
5865   return SVN_NO_ERROR;
5866 }
5867
5868 representation_t *
5869 svn_fs_fs__rep_copy(representation_t *rep,
5870                     apr_pool_t *pool)
5871 {
5872   representation_t *rep_new;
5873
5874   if (rep == NULL)
5875     return NULL;
5876
5877   rep_new = apr_pcalloc(pool, sizeof(*rep_new));
5878
5879   memcpy(rep_new, rep, sizeof(*rep_new));
5880   rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
5881   rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
5882   rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier);
5883
5884   return rep_new;
5885 }
5886
5887 /* Merge the internal-use-only CHANGE into a hash of public-FS
5888    svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
5889    single summarical (is that real word?) change per path.  Also keep
5890    the COPYFROM_CACHE up to date with new adds and replaces.  */
5891 static svn_error_t *
5892 fold_change(apr_hash_t *changes,
5893             const change_t *change,
5894             apr_hash_t *copyfrom_cache)
5895 {
5896   apr_pool_t *pool = apr_hash_pool_get(changes);
5897   svn_fs_path_change2_t *old_change, *new_change;
5898   const char *path;
5899   apr_size_t path_len = strlen(change->path);
5900
5901   if ((old_change = apr_hash_get(changes, change->path, path_len)))
5902     {
5903       /* This path already exists in the hash, so we have to merge
5904          this change into the already existing one. */
5905
5906       /* Sanity check:  only allow NULL node revision ID in the
5907          `reset' case. */
5908       if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
5909         return svn_error_create
5910           (SVN_ERR_FS_CORRUPT, NULL,
5911            _("Missing required node revision ID"));
5912
5913       /* Sanity check: we should be talking about the same node
5914          revision ID as our last change except where the last change
5915          was a deletion. */
5916       if (change->noderev_id
5917           && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
5918           && (old_change->change_kind != svn_fs_path_change_delete))
5919         return svn_error_create
5920           (SVN_ERR_FS_CORRUPT, NULL,
5921            _("Invalid change ordering: new node revision ID "
5922              "without delete"));
5923
5924       /* Sanity check: an add, replacement, or reset must be the first
5925          thing to follow a deletion. */
5926       if ((old_change->change_kind == svn_fs_path_change_delete)
5927           && (! ((change->kind == svn_fs_path_change_replace)
5928                  || (change->kind == svn_fs_path_change_reset)
5929                  || (change->kind == svn_fs_path_change_add))))
5930         return svn_error_create
5931           (SVN_ERR_FS_CORRUPT, NULL,
5932            _("Invalid change ordering: non-add change on deleted path"));
5933
5934       /* Sanity check: an add can't follow anything except
5935          a delete or reset.  */
5936       if ((change->kind == svn_fs_path_change_add)
5937           && (old_change->change_kind != svn_fs_path_change_delete)
5938           && (old_change->change_kind != svn_fs_path_change_reset))
5939         return svn_error_create
5940           (SVN_ERR_FS_CORRUPT, NULL,
5941            _("Invalid change ordering: add change on preexisting path"));
5942
5943       /* Now, merge that change in. */
5944       switch (change->kind)
5945         {
5946         case svn_fs_path_change_reset:
5947           /* A reset here will simply remove the path change from the
5948              hash. */
5949           old_change = NULL;
5950           break;
5951
5952         case svn_fs_path_change_delete:
5953           if (old_change->change_kind == svn_fs_path_change_add)
5954             {
5955               /* If the path was introduced in this transaction via an
5956                  add, and we are deleting it, just remove the path
5957                  altogether. */
5958               old_change = NULL;
5959             }
5960           else
5961             {
5962               /* A deletion overrules all previous changes. */
5963               old_change->change_kind = svn_fs_path_change_delete;
5964               old_change->text_mod = change->text_mod;
5965               old_change->prop_mod = change->prop_mod;
5966               old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5967               old_change->copyfrom_path = NULL;
5968             }
5969           break;
5970
5971         case svn_fs_path_change_add:
5972         case svn_fs_path_change_replace:
5973           /* An add at this point must be following a previous delete,
5974              so treat it just like a replace. */
5975           old_change->change_kind = svn_fs_path_change_replace;
5976           old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
5977                                                        pool);
5978           old_change->text_mod = change->text_mod;
5979           old_change->prop_mod = change->prop_mod;
5980           if (change->copyfrom_rev == SVN_INVALID_REVNUM)
5981             {
5982               old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5983               old_change->copyfrom_path = NULL;
5984             }
5985           else
5986             {
5987               old_change->copyfrom_rev = change->copyfrom_rev;
5988               old_change->copyfrom_path = apr_pstrdup(pool,
5989                                                       change->copyfrom_path);
5990             }
5991           break;
5992
5993         case svn_fs_path_change_modify:
5994         default:
5995           if (change->text_mod)
5996             old_change->text_mod = TRUE;
5997           if (change->prop_mod)
5998             old_change->prop_mod = TRUE;
5999           break;
6000         }
6001
6002       /* Point our new_change to our (possibly modified) old_change. */
6003       new_change = old_change;
6004     }
6005   else
6006     {
6007       /* This change is new to the hash, so make a new public change
6008          structure from the internal one (in the hash's pool), and dup
6009          the path into the hash's pool, too. */
6010       new_change = apr_pcalloc(pool, sizeof(*new_change));
6011       new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
6012       new_change->change_kind = change->kind;
6013       new_change->text_mod = change->text_mod;
6014       new_change->prop_mod = change->prop_mod;
6015       /* In FSFS, copyfrom_known is *always* true, since we've always
6016        * stored copyfroms in changed paths lists. */
6017       new_change->copyfrom_known = TRUE;
6018       if (change->copyfrom_rev != SVN_INVALID_REVNUM)
6019         {
6020           new_change->copyfrom_rev = change->copyfrom_rev;
6021           new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path);
6022         }
6023       else
6024         {
6025           new_change->copyfrom_rev = SVN_INVALID_REVNUM;
6026           new_change->copyfrom_path = NULL;
6027         }
6028     }
6029
6030   if (new_change)
6031     new_change->node_kind = change->node_kind;
6032
6033   /* Add (or update) this path.
6034
6035      Note: this key might already be present, and it would be nice to
6036      re-use its value, but there is no way to fetch it. The API makes no
6037      guarantees that this (new) key will not be retained. Thus, we (again)
6038      copy the key into the target pool to ensure a proper lifetime.  */
6039   path = apr_pstrmemdup(pool, change->path, path_len);
6040   apr_hash_set(changes, path, path_len, new_change);
6041
6042   /* Update the copyfrom cache, if any. */
6043   if (copyfrom_cache)
6044     {
6045       apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache);
6046       const char *copyfrom_string = NULL, *copyfrom_key = path;
6047       if (new_change)
6048         {
6049           if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev))
6050             copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
6051                                            new_change->copyfrom_rev,
6052                                            new_change->copyfrom_path);
6053           else
6054             copyfrom_string = "";
6055         }
6056       /* We need to allocate a copy of the key in the copyfrom_pool if
6057        * we're not doing a deletion and if it isn't already there. */
6058       if (   copyfrom_string
6059           && (   ! apr_hash_count(copyfrom_cache)
6060               || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len)))
6061         copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len);
6062
6063       apr_hash_set(copyfrom_cache, copyfrom_key, path_len,
6064                    copyfrom_string);
6065     }
6066
6067   return SVN_NO_ERROR;
6068 }
6069
6070 /* The 256 is an arbitrary size large enough to hold the node id and the
6071  * various flags. */
6072 #define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
6073
6074 /* Read the next entry in the changes record from file FILE and store
6075    the resulting change in *CHANGE_P.  If there is no next record,
6076    store NULL there.  Perform all allocations from POOL. */
6077 static svn_error_t *
6078 read_change(change_t **change_p,
6079             apr_file_t *file,
6080             apr_pool_t *pool)
6081 {
6082   char buf[MAX_CHANGE_LINE_LEN];
6083   apr_size_t len = sizeof(buf);
6084   change_t *change;
6085   char *str, *last_str = buf, *kind_str;
6086   svn_error_t *err;
6087
6088   /* Default return value. */
6089   *change_p = NULL;
6090
6091   err = svn_io_read_length_line(file, buf, &len, pool);
6092
6093   /* Check for a blank line. */
6094   if (err || (len == 0))
6095     {
6096       if (err && APR_STATUS_IS_EOF(err->apr_err))
6097         {
6098           svn_error_clear(err);
6099           return SVN_NO_ERROR;
6100         }
6101       if ((len == 0) && (! err))
6102         return SVN_NO_ERROR;
6103       return svn_error_trace(err);
6104     }
6105
6106   change = apr_pcalloc(pool, sizeof(*change));
6107
6108   /* Get the node-id of the change. */
6109   str = svn_cstring_tokenize(" ", &last_str);
6110   if (str == NULL)
6111     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6112                             _("Invalid changes line in rev-file"));
6113
6114   change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
6115   if (change->noderev_id == NULL)
6116     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6117                             _("Invalid changes line in rev-file"));
6118
6119   /* Get the change type. */
6120   str = svn_cstring_tokenize(" ", &last_str);
6121   if (str == NULL)
6122     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6123                             _("Invalid changes line in rev-file"));
6124
6125   /* Don't bother to check the format number before looking for
6126    * node-kinds: just read them if you find them. */
6127   change->node_kind = svn_node_unknown;
6128   kind_str = strchr(str, '-');
6129   if (kind_str)
6130     {
6131       /* Cap off the end of "str" (the action). */
6132       *kind_str = '\0';
6133       kind_str++;
6134       if (strcmp(kind_str, KIND_FILE) == 0)
6135         change->node_kind = svn_node_file;
6136       else if (strcmp(kind_str, KIND_DIR) == 0)
6137         change->node_kind = svn_node_dir;
6138       else
6139         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6140                                 _("Invalid changes line in rev-file"));
6141     }
6142
6143   if (strcmp(str, ACTION_MODIFY) == 0)
6144     {
6145       change->kind = svn_fs_path_change_modify;
6146     }
6147   else if (strcmp(str, ACTION_ADD) == 0)
6148     {
6149       change->kind = svn_fs_path_change_add;
6150     }
6151   else if (strcmp(str, ACTION_DELETE) == 0)
6152     {
6153       change->kind = svn_fs_path_change_delete;
6154     }
6155   else if (strcmp(str, ACTION_REPLACE) == 0)
6156     {
6157       change->kind = svn_fs_path_change_replace;
6158     }
6159   else if (strcmp(str, ACTION_RESET) == 0)
6160     {
6161       change->kind = svn_fs_path_change_reset;
6162     }
6163   else
6164     {
6165       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6166                               _("Invalid change kind in rev file"));
6167     }
6168
6169   /* Get the text-mod flag. */
6170   str = svn_cstring_tokenize(" ", &last_str);
6171   if (str == NULL)
6172     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6173                             _("Invalid changes line in rev-file"));
6174
6175   if (strcmp(str, FLAG_TRUE) == 0)
6176     {
6177       change->text_mod = TRUE;
6178     }
6179   else if (strcmp(str, FLAG_FALSE) == 0)
6180     {
6181       change->text_mod = FALSE;
6182     }
6183   else
6184     {
6185       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6186                               _("Invalid text-mod flag in rev-file"));
6187     }
6188
6189   /* Get the prop-mod flag. */
6190   str = svn_cstring_tokenize(" ", &last_str);
6191   if (str == NULL)
6192     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6193                             _("Invalid changes line in rev-file"));
6194
6195   if (strcmp(str, FLAG_TRUE) == 0)
6196     {
6197       change->prop_mod = TRUE;
6198     }
6199   else if (strcmp(str, FLAG_FALSE) == 0)
6200     {
6201       change->prop_mod = FALSE;
6202     }
6203   else
6204     {
6205       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6206                               _("Invalid prop-mod flag in rev-file"));
6207     }
6208
6209   /* Get the changed path. */
6210   change->path = apr_pstrdup(pool, last_str);
6211
6212
6213   /* Read the next line, the copyfrom line. */
6214   len = sizeof(buf);
6215   SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
6216
6217   if (len == 0)
6218     {
6219       change->copyfrom_rev = SVN_INVALID_REVNUM;
6220       change->copyfrom_path = NULL;
6221     }
6222   else
6223     {
6224       last_str = buf;
6225       str = svn_cstring_tokenize(" ", &last_str);
6226       if (! str)
6227         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6228                                 _("Invalid changes line in rev-file"));
6229       change->copyfrom_rev = SVN_STR_TO_REV(str);
6230
6231       if (! last_str)
6232         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6233                                 _("Invalid changes line in rev-file"));
6234
6235       change->copyfrom_path = apr_pstrdup(pool, last_str);
6236     }
6237
6238   *change_p = change;
6239
6240   return SVN_NO_ERROR;
6241 }
6242
6243 /* Examine all the changed path entries in CHANGES and store them in
6244    *CHANGED_PATHS.  Folding is done to remove redundant or unnecessary
6245    *data.  Store a hash of paths to copyfrom "REV PATH" strings in
6246    COPYFROM_HASH if it is non-NULL.  If PREFOLDED is true, assume that
6247    the changed-path entries have already been folded (by
6248    write_final_changed_path_info) and may be out of order, so we shouldn't
6249    remove children of replaced or deleted directories.  Do all
6250    allocations in POOL. */
6251 static svn_error_t *
6252 process_changes(apr_hash_t *changed_paths,
6253                 apr_hash_t *copyfrom_cache,
6254                 apr_array_header_t *changes,
6255                 svn_boolean_t prefolded,
6256                 apr_pool_t *pool)
6257 {
6258   apr_pool_t *iterpool = svn_pool_create(pool);
6259   int i;
6260
6261   /* Read in the changes one by one, folding them into our local hash
6262      as necessary. */
6263
6264   for (i = 0; i < changes->nelts; ++i)
6265     {
6266       change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
6267
6268       SVN_ERR(fold_change(changed_paths, change, copyfrom_cache));
6269
6270       /* Now, if our change was a deletion or replacement, we have to
6271          blow away any changes thus far on paths that are (or, were)
6272          children of this path.
6273          ### i won't bother with another iteration pool here -- at
6274          most we talking about a few extra dups of paths into what
6275          is already a temporary subpool.
6276       */
6277
6278       if (((change->kind == svn_fs_path_change_delete)
6279            || (change->kind == svn_fs_path_change_replace))
6280           && ! prefolded)
6281         {
6282           apr_hash_index_t *hi;
6283
6284           /* a potential child path must contain at least 2 more chars
6285              (the path separator plus at least one char for the name).
6286              Also, we should not assume that all paths have been normalized
6287              i.e. some might have trailing path separators.
6288           */
6289           apr_ssize_t change_path_len = strlen(change->path);
6290           apr_ssize_t min_child_len = change_path_len == 0
6291                                     ? 1
6292                                     : change->path[change_path_len-1] == '/'
6293                                         ? change_path_len + 1
6294                                         : change_path_len + 2;
6295
6296           /* CAUTION: This is the inner loop of an O(n^2) algorithm.
6297              The number of changes to process may be >> 1000.
6298              Therefore, keep the inner loop as tight as possible.
6299           */
6300           for (hi = apr_hash_first(iterpool, changed_paths);
6301                hi;
6302                hi = apr_hash_next(hi))
6303             {
6304               /* KEY is the path. */
6305               const void *path;
6306               apr_ssize_t klen;
6307               apr_hash_this(hi, &path, &klen, NULL);
6308
6309               /* If we come across a child of our path, remove it.
6310                  Call svn_dirent_is_child only if there is a chance that
6311                  this is actually a sub-path.
6312                */
6313               if (   klen >= min_child_len
6314                   && svn_dirent_is_child(change->path, path, iterpool))
6315                 apr_hash_set(changed_paths, path, klen, NULL);
6316             }
6317         }
6318
6319       /* Clear the per-iteration subpool. */
6320       svn_pool_clear(iterpool);
6321     }
6322
6323   /* Destroy the per-iteration subpool. */
6324   svn_pool_destroy(iterpool);
6325
6326   return SVN_NO_ERROR;
6327 }
6328
6329 /* Fetch all the changes from FILE and store them in *CHANGES.  Do all
6330    allocations in POOL. */
6331 static svn_error_t *
6332 read_all_changes(apr_array_header_t **changes,
6333                  apr_file_t *file,
6334                  apr_pool_t *pool)
6335 {
6336   change_t *change;
6337
6338   /* pre-allocate enough room for most change lists
6339      (will be auto-expanded as necessary) */
6340   *changes = apr_array_make(pool, 30, sizeof(change_t *));
6341
6342   SVN_ERR(read_change(&change, file, pool));
6343   while (change)
6344     {
6345       APR_ARRAY_PUSH(*changes, change_t*) = change;
6346       SVN_ERR(read_change(&change, file, pool));
6347     }
6348
6349   return SVN_NO_ERROR;
6350 }
6351
6352 svn_error_t *
6353 svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
6354                              svn_fs_t *fs,
6355                              const char *txn_id,
6356                              apr_pool_t *pool)
6357 {
6358   apr_file_t *file;
6359   apr_hash_t *changed_paths = apr_hash_make(pool);
6360   apr_array_header_t *changes;
6361   apr_pool_t *scratch_pool = svn_pool_create(pool);
6362
6363   SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
6364                            APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6365
6366   SVN_ERR(read_all_changes(&changes, file, scratch_pool));
6367   SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool));
6368   svn_pool_destroy(scratch_pool);
6369
6370   SVN_ERR(svn_io_file_close(file, pool));
6371
6372   *changed_paths_p = changed_paths;
6373
6374   return SVN_NO_ERROR;
6375 }
6376
6377 /* Fetch the list of change in revision REV in FS and return it in *CHANGES.
6378  * Allocate the result in POOL.
6379  */
6380 static svn_error_t *
6381 get_changes(apr_array_header_t **changes,
6382             svn_fs_t *fs,
6383             svn_revnum_t rev,
6384             apr_pool_t *pool)
6385 {
6386   apr_off_t changes_offset;
6387   apr_file_t *revision_file;
6388   svn_boolean_t found;
6389   fs_fs_data_t *ffd = fs->fsap_data;
6390
6391   /* try cache lookup first */
6392
6393   if (ffd->changes_cache)
6394     {
6395       SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
6396                              &rev, pool));
6397       if (found)
6398         return SVN_NO_ERROR;
6399     }
6400
6401   /* read changes from revision file */
6402
6403   SVN_ERR(ensure_revision_exists(fs, rev, pool));
6404
6405   SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
6406
6407   SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs,
6408                                   rev, pool));
6409
6410   SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
6411   SVN_ERR(read_all_changes(changes, revision_file, pool));
6412
6413   SVN_ERR(svn_io_file_close(revision_file, pool));
6414
6415   /* cache for future reference */
6416
6417   if (ffd->changes_cache)
6418     SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool));
6419
6420   return SVN_NO_ERROR;
6421 }
6422
6423
6424 svn_error_t *
6425 svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
6426                          svn_fs_t *fs,
6427                          svn_revnum_t rev,
6428                          apr_hash_t *copyfrom_cache,
6429                          apr_pool_t *pool)
6430 {
6431   apr_hash_t *changed_paths;
6432   apr_array_header_t *changes;
6433   apr_pool_t *scratch_pool = svn_pool_create(pool);
6434
6435   SVN_ERR(get_changes(&changes, fs, rev, scratch_pool));
6436
6437   changed_paths = svn_hash__make(pool);
6438
6439   SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes,
6440                           TRUE, pool));
6441   svn_pool_destroy(scratch_pool);
6442
6443   *changed_paths_p = changed_paths;
6444
6445   return SVN_NO_ERROR;
6446 }
6447
6448 /* Copy a revision node-rev SRC into the current transaction TXN_ID in
6449    the filesystem FS.  This is only used to create the root of a transaction.
6450    Allocations are from POOL.  */
6451 static svn_error_t *
6452 create_new_txn_noderev_from_rev(svn_fs_t *fs,
6453                                 const char *txn_id,
6454                                 svn_fs_id_t *src,
6455                                 apr_pool_t *pool)
6456 {
6457   node_revision_t *noderev;
6458   const char *node_id, *copy_id;
6459
6460   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
6461
6462   if (svn_fs_fs__id_txn_id(noderev->id))
6463     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6464                             _("Copying from transactions not allowed"));
6465
6466   noderev->predecessor_id = noderev->id;
6467   noderev->predecessor_count++;
6468   noderev->copyfrom_path = NULL;
6469   noderev->copyfrom_rev = SVN_INVALID_REVNUM;
6470
6471   /* For the transaction root, the copyroot never changes. */
6472
6473   node_id = svn_fs_fs__id_node_id(noderev->id);
6474   copy_id = svn_fs_fs__id_copy_id(noderev->id);
6475   noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6476
6477   return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
6478 }
6479
6480 /* A structure used by get_and_increment_txn_key_body(). */
6481 struct get_and_increment_txn_key_baton {
6482   svn_fs_t *fs;
6483   char *txn_id;
6484   apr_pool_t *pool;
6485 };
6486
6487 /* Callback used in the implementation of create_txn_dir().  This gets
6488    the current base 36 value in PATH_TXN_CURRENT and increments it.
6489    It returns the original value by the baton. */
6490 static svn_error_t *
6491 get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
6492 {
6493   struct get_and_increment_txn_key_baton *cb = baton;
6494   const char *txn_current_filename = path_txn_current(cb->fs, pool);
6495   const char *tmp_filename;
6496   char next_txn_id[MAX_KEY_SIZE+3];
6497   apr_size_t len;
6498
6499   svn_stringbuf_t *buf;
6500   SVN_ERR(read_content(&buf, txn_current_filename, cb->pool));
6501
6502   /* remove trailing newlines */
6503   svn_stringbuf_strip_whitespace(buf);
6504   cb->txn_id = buf->data;
6505   len = buf->len;
6506
6507   /* Increment the key and add a trailing \n to the string so the
6508      txn-current file has a newline in it. */
6509   svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
6510   next_txn_id[len] = '\n';
6511   ++len;
6512   next_txn_id[len] = '\0';
6513
6514   SVN_ERR(svn_io_write_unique(&tmp_filename,
6515                               svn_dirent_dirname(txn_current_filename, pool),
6516                               next_txn_id, len, svn_io_file_del_none, pool));
6517   SVN_ERR(move_into_place(tmp_filename, txn_current_filename,
6518                           txn_current_filename, pool));
6519
6520   return SVN_NO_ERROR;
6521 }
6522
6523 /* Create a unique directory for a transaction in FS based on revision
6524    REV.  Return the ID for this transaction in *ID_P.  Use a sequence
6525    value in the transaction ID to prevent reuse of transaction IDs. */
6526 static svn_error_t *
6527 create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6528                apr_pool_t *pool)
6529 {
6530   struct get_and_increment_txn_key_baton cb;
6531   const char *txn_dir;
6532
6533   /* Get the current transaction sequence value, which is a base-36
6534      number, from the txn-current file, and write an
6535      incremented value back out to the file.  Place the revision
6536      number the transaction is based off into the transaction id. */
6537   cb.pool = pool;
6538   cb.fs = fs;
6539   SVN_ERR(with_txn_current_lock(fs,
6540                                 get_and_increment_txn_key_body,
6541                                 &cb,
6542                                 pool));
6543   *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
6544
6545   txn_dir = svn_dirent_join_many(pool,
6546                                  fs->path,
6547                                  PATH_TXNS_DIR,
6548                                  apr_pstrcat(pool, *id_p, PATH_EXT_TXN,
6549                                              (char *)NULL),
6550                                  NULL);
6551
6552   return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
6553 }
6554
6555 /* Create a unique directory for a transaction in FS based on revision
6556    REV.  Return the ID for this transaction in *ID_P.  This
6557    implementation is used in svn 1.4 and earlier repositories and is
6558    kept in 1.5 and greater to support the --pre-1.4-compatible and
6559    --pre-1.5-compatible repository creation options.  Reused
6560    transaction IDs are possible with this implementation. */
6561 static svn_error_t *
6562 create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6563                        apr_pool_t *pool)
6564 {
6565   unsigned int i;
6566   apr_pool_t *subpool;
6567   const char *unique_path, *prefix;
6568
6569   /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
6570   prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
6571                                 apr_psprintf(pool, "%ld", rev), NULL);
6572
6573   subpool = svn_pool_create(pool);
6574   for (i = 1; i <= 99999; i++)
6575     {
6576       svn_error_t *err;
6577
6578       svn_pool_clear(subpool);
6579       unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
6580       err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
6581       if (! err)
6582         {
6583           /* We succeeded.  Return the basename minus the ".txn" extension. */
6584           const char *name = svn_dirent_basename(unique_path, subpool);
6585           *id_p = apr_pstrndup(pool, name,
6586                                strlen(name) - strlen(PATH_EXT_TXN));
6587           svn_pool_destroy(subpool);
6588           return SVN_NO_ERROR;
6589         }
6590       if (! APR_STATUS_IS_EEXIST(err->apr_err))
6591         return svn_error_trace(err);
6592       svn_error_clear(err);
6593     }
6594
6595   return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
6596                            NULL,
6597                            _("Unable to create transaction directory "
6598                              "in '%s' for revision %ld"),
6599                            svn_dirent_local_style(fs->path, pool),
6600                            rev);
6601 }
6602
6603 svn_error_t *
6604 svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
6605                       svn_fs_t *fs,
6606                       svn_revnum_t rev,
6607                       apr_pool_t *pool)
6608 {
6609   fs_fs_data_t *ffd = fs->fsap_data;
6610   svn_fs_txn_t *txn;
6611   svn_fs_id_t *root_id;
6612
6613   txn = apr_pcalloc(pool, sizeof(*txn));
6614
6615   /* Get the txn_id. */
6616   if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
6617     SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
6618   else
6619     SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
6620
6621   txn->fs = fs;
6622   txn->base_rev = rev;
6623
6624   txn->vtable = &txn_vtable;
6625   *txn_p = txn;
6626
6627   /* Create a new root node for this transaction. */
6628   SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
6629   SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
6630
6631   /* Create an empty rev file. */
6632   SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
6633                              pool));
6634
6635   /* Create an empty rev-lock file. */
6636   SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
6637                              pool));
6638
6639   /* Create an empty changes file. */
6640   SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
6641                              pool));
6642
6643   /* Create the next-ids file. */
6644   return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
6645                             pool);
6646 }
6647
6648 /* Store the property list for transaction TXN_ID in PROPLIST.
6649    Perform temporary allocations in POOL. */
6650 static svn_error_t *
6651 get_txn_proplist(apr_hash_t *proplist,
6652                  svn_fs_t *fs,
6653                  const char *txn_id,
6654                  apr_pool_t *pool)
6655 {
6656   svn_stream_t *stream;
6657
6658   /* Check for issue #3696. (When we find and fix the cause, we can change
6659    * this to an assertion.) */
6660   if (txn_id == NULL)
6661     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
6662                             _("Internal error: a null transaction id was "
6663                               "passed to get_txn_proplist()"));
6664
6665   /* Open the transaction properties file. */
6666   SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
6667                                    pool, pool));
6668
6669   /* Read in the property list. */
6670   SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
6671
6672   return svn_stream_close(stream);
6673 }
6674
6675 svn_error_t *
6676 svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
6677                            const char *name,
6678                            const svn_string_t *value,
6679                            apr_pool_t *pool)
6680 {
6681   apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
6682   svn_prop_t prop;
6683
6684   prop.name = name;
6685   prop.value = value;
6686   APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6687
6688   return svn_fs_fs__change_txn_props(txn, props, pool);
6689 }
6690
6691 svn_error_t *
6692 svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
6693                             const apr_array_header_t *props,
6694                             apr_pool_t *pool)
6695 {
6696   const char *txn_prop_filename;
6697   svn_stringbuf_t *buf;
6698   svn_stream_t *stream;
6699   apr_hash_t *txn_prop = apr_hash_make(pool);
6700   int i;
6701   svn_error_t *err;
6702
6703   err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
6704   /* Here - and here only - we need to deal with the possibility that the
6705      transaction property file doesn't yet exist.  The rest of the
6706      implementation assumes that the file exists, but we're called to set the
6707      initial transaction properties as the transaction is being created. */
6708   if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
6709     svn_error_clear(err);
6710   else if (err)
6711     return svn_error_trace(err);
6712
6713   for (i = 0; i < props->nelts; i++)
6714     {
6715       svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
6716
6717       svn_hash_sets(txn_prop, prop->name, prop->value);
6718     }
6719
6720   /* Create a new version of the file and write out the new props. */
6721   /* Open the transaction properties file. */
6722   buf = svn_stringbuf_create_ensure(1024, pool);
6723   stream = svn_stream_from_stringbuf(buf, pool);
6724   SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool));
6725   SVN_ERR(svn_stream_close(stream));
6726   SVN_ERR(svn_io_write_unique(&txn_prop_filename,
6727                               path_txn_dir(txn->fs, txn->id, pool),
6728                               buf->data,
6729                               buf->len,
6730                               svn_io_file_del_none,
6731                               pool));
6732   return svn_io_file_rename(txn_prop_filename,
6733                             path_txn_props(txn->fs, txn->id, pool),
6734                             pool);
6735 }
6736
6737 svn_error_t *
6738 svn_fs_fs__get_txn(transaction_t **txn_p,
6739                    svn_fs_t *fs,
6740                    const char *txn_id,
6741                    apr_pool_t *pool)
6742 {
6743   transaction_t *txn;
6744   node_revision_t *noderev;
6745   svn_fs_id_t *root_id;
6746
6747   txn = apr_pcalloc(pool, sizeof(*txn));
6748   txn->proplist = apr_hash_make(pool);
6749
6750   SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
6751   root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
6752
6753   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
6754
6755   txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
6756   txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
6757   txn->copies = NULL;
6758
6759   *txn_p = txn;
6760
6761   return SVN_NO_ERROR;
6762 }
6763
6764 /* Write out the currently available next node_id NODE_ID and copy_id
6765    COPY_ID for transaction TXN_ID in filesystem FS.  The next node-id is
6766    used both for creating new unique nodes for the given transaction, as
6767    well as uniquifying representations.  Perform temporary allocations in
6768    POOL. */
6769 static svn_error_t *
6770 write_next_ids(svn_fs_t *fs,
6771                const char *txn_id,
6772                const char *node_id,
6773                const char *copy_id,
6774                apr_pool_t *pool)
6775 {
6776   apr_file_t *file;
6777   svn_stream_t *out_stream;
6778
6779   SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6780                            APR_WRITE | APR_TRUNCATE,
6781                            APR_OS_DEFAULT, pool));
6782
6783   out_stream = svn_stream_from_aprfile2(file, TRUE, pool);
6784
6785   SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
6786
6787   SVN_ERR(svn_stream_close(out_stream));
6788   return svn_io_file_close(file, pool);
6789 }
6790
6791 /* Find out what the next unique node-id and copy-id are for
6792    transaction TXN_ID in filesystem FS.  Store the results in *NODE_ID
6793    and *COPY_ID.  The next node-id is used both for creating new unique
6794    nodes for the given transaction, as well as uniquifying representations.
6795    Perform all allocations in POOL. */
6796 static svn_error_t *
6797 read_next_ids(const char **node_id,
6798               const char **copy_id,
6799               svn_fs_t *fs,
6800               const char *txn_id,
6801               apr_pool_t *pool)
6802 {
6803   apr_file_t *file;
6804   char buf[MAX_KEY_SIZE*2+3];
6805   apr_size_t limit;
6806   char *str, *last_str = buf;
6807
6808   SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6809                            APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6810
6811   limit = sizeof(buf);
6812   SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
6813
6814   SVN_ERR(svn_io_file_close(file, pool));
6815
6816   /* Parse this into two separate strings. */
6817
6818   str = svn_cstring_tokenize(" ", &last_str);
6819   if (! str)
6820     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6821                             _("next-id file corrupt"));
6822
6823   *node_id = apr_pstrdup(pool, str);
6824
6825   str = svn_cstring_tokenize(" ", &last_str);
6826   if (! str)
6827     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6828                             _("next-id file corrupt"));
6829
6830   *copy_id = apr_pstrdup(pool, str);
6831
6832   return SVN_NO_ERROR;
6833 }
6834
6835 /* Get a new and unique to this transaction node-id for transaction
6836    TXN_ID in filesystem FS.  Store the new node-id in *NODE_ID_P.
6837    Node-ids are guaranteed to be unique to this transction, but may
6838    not necessarily be sequential.  Perform all allocations in POOL. */
6839 static svn_error_t *
6840 get_new_txn_node_id(const char **node_id_p,
6841                     svn_fs_t *fs,
6842                     const char *txn_id,
6843                     apr_pool_t *pool)
6844 {
6845   const char *cur_node_id, *cur_copy_id;
6846   char *node_id;
6847   apr_size_t len;
6848
6849   /* First read in the current next-ids file. */
6850   SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
6851
6852   node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
6853
6854   len = strlen(cur_node_id);
6855   svn_fs_fs__next_key(cur_node_id, &len, node_id);
6856
6857   SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
6858
6859   *node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL);
6860
6861   return SVN_NO_ERROR;
6862 }
6863
6864 svn_error_t *
6865 svn_fs_fs__create_node(const svn_fs_id_t **id_p,
6866                        svn_fs_t *fs,
6867                        node_revision_t *noderev,
6868                        const char *copy_id,
6869                        const char *txn_id,
6870                        apr_pool_t *pool)
6871 {
6872   const char *node_id;
6873   const svn_fs_id_t *id;
6874
6875   /* Get a new node-id for this node. */
6876   SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
6877
6878   id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6879
6880   noderev->id = id;
6881
6882   SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
6883
6884   *id_p = id;
6885
6886   return SVN_NO_ERROR;
6887 }
6888
6889 svn_error_t *
6890 svn_fs_fs__purge_txn(svn_fs_t *fs,
6891                      const char *txn_id,
6892                      apr_pool_t *pool)
6893 {
6894   fs_fs_data_t *ffd = fs->fsap_data;
6895
6896   /* Remove the shared transaction object associated with this transaction. */
6897   SVN_ERR(purge_shared_txn(fs, txn_id, pool));
6898   /* Remove the directory associated with this transaction. */
6899   SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
6900                              NULL, NULL, pool));
6901   if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
6902     {
6903       /* Delete protorev and its lock, which aren't in the txn
6904          directory.  It's OK if they don't exist (for example, if this
6905          is post-commit and the proto-rev has been moved into
6906          place). */
6907       SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool),
6908                                   TRUE, pool));
6909       SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool),
6910                                   TRUE, pool));
6911     }
6912   return SVN_NO_ERROR;
6913 }
6914
6915
6916 svn_error_t *
6917 svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
6918                      apr_pool_t *pool)
6919 {
6920   SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
6921
6922   /* Now, purge the transaction. */
6923   SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
6924             apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
6925                          txn->id));
6926
6927   return SVN_NO_ERROR;
6928 }
6929
6930
6931 svn_error_t *
6932 svn_fs_fs__set_entry(svn_fs_t *fs,
6933                      const char *txn_id,
6934                      node_revision_t *parent_noderev,
6935                      const char *name,
6936                      const svn_fs_id_t *id,
6937                      svn_node_kind_t kind,
6938                      apr_pool_t *pool)
6939 {
6940   representation_t *rep = parent_noderev->data_rep;
6941   const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
6942   apr_file_t *file;
6943   svn_stream_t *out;
6944   fs_fs_data_t *ffd = fs->fsap_data;
6945   apr_pool_t *subpool = svn_pool_create(pool);
6946
6947   if (!rep || !rep->txn_id)
6948     {
6949       const char *unique_suffix;
6950       apr_hash_t *entries;
6951
6952       /* Before we can modify the directory, we need to dump its old
6953          contents into a mutable representation file. */
6954       SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
6955                                           subpool));
6956       SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
6957       SVN_ERR(svn_io_file_open(&file, filename,
6958                                APR_WRITE | APR_CREATE | APR_BUFFERED,
6959                                APR_OS_DEFAULT, pool));
6960       out = svn_stream_from_aprfile2(file, TRUE, pool);
6961       SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
6962
6963       svn_pool_clear(subpool);
6964
6965       /* Mark the node-rev's data rep as mutable. */
6966       rep = apr_pcalloc(pool, sizeof(*rep));
6967       rep->revision = SVN_INVALID_REVNUM;
6968       rep->txn_id = txn_id;
6969       SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool));
6970       rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix);
6971       parent_noderev->data_rep = rep;
6972       SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
6973                                            parent_noderev, FALSE, pool));
6974     }
6975   else
6976     {
6977       /* The directory rep is already mutable, so just open it for append. */
6978       SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
6979                                APR_OS_DEFAULT, pool));
6980       out = svn_stream_from_aprfile2(file, TRUE, pool);
6981     }
6982
6983   /* if we have a directory cache for this transaction, update it */
6984   if (ffd->txn_dir_cache)
6985     {
6986       /* build parameters: (name, new entry) pair */
6987       const char *key =
6988           svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
6989       replace_baton_t baton;
6990
6991       baton.name = name;
6992       baton.new_entry = NULL;
6993
6994       if (id)
6995         {
6996           baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
6997           baton.new_entry->name = name;
6998           baton.new_entry->kind = kind;
6999           baton.new_entry->id = id;
7000         }
7001
7002       /* actually update the cached directory (if cached) */
7003       SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
7004                                      svn_fs_fs__replace_dir_entry, &baton,
7005                                      subpool));
7006     }
7007   svn_pool_clear(subpool);
7008
7009   /* Append an incremental hash entry for the entry change. */
7010   if (id)
7011     {
7012       const char *val = unparse_dir_entry(kind, id, subpool);
7013
7014       SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n"
7015                                 "V %" APR_SIZE_T_FMT "\n%s\n",
7016                                 strlen(name), name,
7017                                 strlen(val), val));
7018     }
7019   else
7020     {
7021       SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
7022                                 strlen(name), name));
7023     }
7024
7025   SVN_ERR(svn_io_file_close(file, subpool));
7026   svn_pool_destroy(subpool);
7027   return SVN_NO_ERROR;
7028 }
7029
7030 /* Write a single change entry, path PATH, change CHANGE, and copyfrom
7031    string COPYFROM, into the file specified by FILE.  Only include the
7032    node kind field if INCLUDE_NODE_KIND is true.  All temporary
7033    allocations are in POOL. */
7034 static svn_error_t *
7035 write_change_entry(apr_file_t *file,
7036                    const char *path,
7037                    svn_fs_path_change2_t *change,
7038                    svn_boolean_t include_node_kind,
7039                    apr_pool_t *pool)
7040 {
7041   const char *idstr, *buf;
7042   const char *change_string = NULL;
7043   const char *kind_string = "";
7044
7045   switch (change->change_kind)
7046     {
7047     case svn_fs_path_change_modify:
7048       change_string = ACTION_MODIFY;
7049       break;
7050     case svn_fs_path_change_add:
7051       change_string = ACTION_ADD;
7052       break;
7053     case svn_fs_path_change_delete:
7054       change_string = ACTION_DELETE;
7055       break;
7056     case svn_fs_path_change_replace:
7057       change_string = ACTION_REPLACE;
7058       break;
7059     case svn_fs_path_change_reset:
7060       change_string = ACTION_RESET;
7061       break;
7062     default:
7063       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7064                                _("Invalid change type %d"),
7065                                change->change_kind);
7066     }
7067
7068   if (change->node_rev_id)
7069     idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
7070   else
7071     idstr = ACTION_RESET;
7072
7073   if (include_node_kind)
7074     {
7075       SVN_ERR_ASSERT(change->node_kind == svn_node_dir
7076                      || change->node_kind == svn_node_file);
7077       kind_string = apr_psprintf(pool, "-%s",
7078                                  change->node_kind == svn_node_dir
7079                                  ? KIND_DIR : KIND_FILE);
7080     }
7081   buf = apr_psprintf(pool, "%s %s%s %s %s %s\n",
7082                      idstr, change_string, kind_string,
7083                      change->text_mod ? FLAG_TRUE : FLAG_FALSE,
7084                      change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
7085                      path);
7086
7087   SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7088
7089   if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
7090     {
7091       buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev,
7092                          change->copyfrom_path);
7093       SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7094     }
7095
7096   return svn_io_file_write_full(file, "\n", 1, NULL, pool);
7097 }
7098
7099 svn_error_t *
7100 svn_fs_fs__add_change(svn_fs_t *fs,
7101                       const char *txn_id,
7102                       const char *path,
7103                       const svn_fs_id_t *id,
7104                       svn_fs_path_change_kind_t change_kind,
7105                       svn_boolean_t text_mod,
7106                       svn_boolean_t prop_mod,
7107                       svn_node_kind_t node_kind,
7108                       svn_revnum_t copyfrom_rev,
7109                       const char *copyfrom_path,
7110                       apr_pool_t *pool)
7111 {
7112   apr_file_t *file;
7113   svn_fs_path_change2_t *change;
7114
7115   SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
7116                            APR_APPEND | APR_WRITE | APR_CREATE
7117                            | APR_BUFFERED, APR_OS_DEFAULT, pool));
7118
7119   change = svn_fs__path_change_create_internal(id, change_kind, pool);
7120   change->text_mod = text_mod;
7121   change->prop_mod = prop_mod;
7122   change->node_kind = node_kind;
7123   change->copyfrom_rev = copyfrom_rev;
7124   change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
7125
7126   SVN_ERR(write_change_entry(file, path, change, TRUE, pool));
7127
7128   return svn_io_file_close(file, pool);
7129 }
7130
7131 /* This baton is used by the representation writing streams.  It keeps
7132    track of the checksum information as well as the total size of the
7133    representation so far. */
7134 struct rep_write_baton
7135 {
7136   /* The FS we are writing to. */
7137   svn_fs_t *fs;
7138
7139   /* Actual file to which we are writing. */
7140   svn_stream_t *rep_stream;
7141
7142   /* A stream from the delta combiner.  Data written here gets
7143      deltified, then eventually written to rep_stream. */
7144   svn_stream_t *delta_stream;
7145
7146   /* Where is this representation header stored. */
7147   apr_off_t rep_offset;
7148
7149   /* Start of the actual data. */
7150   apr_off_t delta_start;
7151
7152   /* How many bytes have been written to this rep already. */
7153   svn_filesize_t rep_size;
7154
7155   /* The node revision for which we're writing out info. */
7156   node_revision_t *noderev;
7157
7158   /* Actual output file. */
7159   apr_file_t *file;
7160   /* Lock 'cookie' used to unlock the output file once we've finished
7161      writing to it. */
7162   void *lockcookie;
7163
7164   svn_checksum_ctx_t *md5_checksum_ctx;
7165   svn_checksum_ctx_t *sha1_checksum_ctx;
7166
7167   apr_pool_t *pool;
7168
7169   apr_pool_t *parent_pool;
7170 };
7171
7172 /* Handler for the write method of the representation writable stream.
7173    BATON is a rep_write_baton, DATA is the data to write, and *LEN is
7174    the length of this data. */
7175 static svn_error_t *
7176 rep_write_contents(void *baton,
7177                    const char *data,
7178                    apr_size_t *len)
7179 {
7180   struct rep_write_baton *b = baton;
7181
7182   SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
7183   SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
7184   b->rep_size += *len;
7185
7186   /* If we are writing a delta, use that stream. */
7187   if (b->delta_stream)
7188     return svn_stream_write(b->delta_stream, data, len);
7189   else
7190     return svn_stream_write(b->rep_stream, data, len);
7191 }
7192
7193 /* Given a node-revision NODEREV in filesystem FS, return the
7194    representation in *REP to use as the base for a text representation
7195    delta if PROPS is FALSE.  If PROPS has been set, a suitable props
7196    base representation will be returned.  Perform temporary allocations
7197    in *POOL. */
7198 static svn_error_t *
7199 choose_delta_base(representation_t **rep,
7200                   svn_fs_t *fs,
7201                   node_revision_t *noderev,
7202                   svn_boolean_t props,
7203                   apr_pool_t *pool)
7204 {
7205   int count;
7206   int walk;
7207   node_revision_t *base;
7208   fs_fs_data_t *ffd = fs->fsap_data;
7209   svn_boolean_t maybe_shared_rep = FALSE;
7210
7211   /* If we have no predecessors, then use the empty stream as a
7212      base. */
7213   if (! noderev->predecessor_count)
7214     {
7215       *rep = NULL;
7216       return SVN_NO_ERROR;
7217     }
7218
7219   /* Flip the rightmost '1' bit of the predecessor count to determine
7220      which file rev (counting from 0) we want to use.  (To see why
7221      count & (count - 1) unsets the rightmost set bit, think about how
7222      you decrement a binary number.) */
7223   count = noderev->predecessor_count;
7224   count = count & (count - 1);
7225
7226   /* We use skip delta for limiting the number of delta operations
7227      along very long node histories.  Close to HEAD however, we create
7228      a linear history to minimize delta size.  */
7229   walk = noderev->predecessor_count - count;
7230   if (walk < (int)ffd->max_linear_deltification)
7231     count = noderev->predecessor_count - 1;
7232
7233   /* Finding the delta base over a very long distance can become extremely
7234      expensive for very deep histories, possibly causing client timeouts etc.
7235      OTOH, this is a rare operation and its gains are minimal. Lets simply
7236      start deltification anew close every other 1000 changes or so.  */
7237   if (walk > (int)ffd->max_deltification_walk)
7238     {
7239       *rep = NULL;
7240       return SVN_NO_ERROR;
7241     }
7242
7243   /* Walk back a number of predecessors equal to the difference
7244      between count and the original predecessor count.  (For example,
7245      if noderev has ten predecessors and we want the eighth file rev,
7246      walk back two predecessors.) */
7247   base = noderev;
7248   while ((count++) < noderev->predecessor_count)
7249     {
7250       SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
7251                                            base->predecessor_id, pool));
7252
7253       /* If there is a shared rep along the way, we need to limit the
7254        * length of the deltification chain.
7255        *
7256        * Please note that copied nodes - such as branch directories - will
7257        * look the same (false positive) while reps shared within the same
7258        * revision will not be caught (false negative).
7259        */
7260       if (props)
7261         {
7262           if (   base->prop_rep
7263               && svn_fs_fs__id_rev(base->id) > base->prop_rep->revision)
7264             maybe_shared_rep = TRUE;
7265         }
7266       else
7267         {
7268           if (   base->data_rep
7269               && svn_fs_fs__id_rev(base->id) > base->data_rep->revision)
7270             maybe_shared_rep = TRUE;
7271         }
7272     }
7273
7274   /* return a suitable base representation */
7275   *rep = props ? base->prop_rep : base->data_rep;
7276
7277   /* if we encountered a shared rep, it's parent chain may be different
7278    * from the node-rev parent chain. */
7279   if (*rep && maybe_shared_rep)
7280     {
7281       /* Check whether the length of the deltification chain is acceptable.
7282        * Otherwise, shared reps may form a non-skipping delta chain in
7283        * extreme cases. */
7284       apr_pool_t *sub_pool = svn_pool_create(pool);
7285       representation_t base_rep = **rep;
7286
7287       /* Some reasonable limit, depending on how acceptable longer linear
7288        * chains are in this repo.  Also, allow for some minimal chain. */
7289       int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2;
7290
7291       /* re-use open files between iterations */
7292       svn_revnum_t rev_hint = SVN_INVALID_REVNUM;
7293       apr_file_t *file_hint = NULL;
7294
7295       /* follow the delta chain towards the end but for at most
7296        * MAX_CHAIN_LENGTH steps. */
7297       for (; max_chain_length; --max_chain_length)
7298         {
7299           struct rep_state *rep_state;
7300           struct rep_args *rep_args;
7301
7302           SVN_ERR(create_rep_state_body(&rep_state,
7303                                         &rep_args,
7304                                         &file_hint,
7305                                         &rev_hint,
7306                                         &base_rep,
7307                                         fs,
7308                                         sub_pool));
7309           if (!rep_args->is_delta  || !rep_args->base_revision)
7310             break;
7311
7312           base_rep.revision = rep_args->base_revision;
7313           base_rep.offset = rep_args->base_offset;
7314           base_rep.size = rep_args->base_length;
7315           base_rep.txn_id = NULL;
7316         }
7317
7318       /* start new delta chain if the current one has grown too long */
7319       if (max_chain_length == 0)
7320         *rep = NULL;
7321
7322       svn_pool_destroy(sub_pool);
7323     }
7324
7325   /* verify that the reps don't form a degenerated '*/
7326   return SVN_NO_ERROR;
7327 }
7328
7329 /* Something went wrong and the pool for the rep write is being
7330    cleared before we've finished writing the rep.  So we need
7331    to remove the rep from the protorevfile and we need to unlock
7332    the protorevfile. */
7333 static apr_status_t
7334 rep_write_cleanup(void *data)
7335 {
7336   struct rep_write_baton *b = data;
7337   const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7338   svn_error_t *err;
7339
7340   /* Truncate and close the protorevfile. */
7341   err = svn_io_file_trunc(b->file, b->rep_offset, b->pool);
7342   err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool));
7343
7344   /* Remove our lock regardless of any preceeding errors so that the
7345      being_written flag is always removed and stays consistent with the
7346      file lock which will be removed no matter what since the pool is
7347      going away. */
7348   err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id,
7349                                                        b->lockcookie, b->pool));
7350   if (err)
7351     {
7352       apr_status_t rc = err->apr_err;
7353       svn_error_clear(err);
7354       return rc;
7355     }
7356
7357   return APR_SUCCESS;
7358 }
7359
7360
7361 /* Get a rep_write_baton and store it in *WB_P for the representation
7362    indicated by NODEREV in filesystem FS.  Perform allocations in
7363    POOL.  Only appropriate for file contents, not for props or
7364    directory contents. */
7365 static svn_error_t *
7366 rep_write_get_baton(struct rep_write_baton **wb_p,
7367                     svn_fs_t *fs,
7368                     node_revision_t *noderev,
7369                     apr_pool_t *pool)
7370 {
7371   struct rep_write_baton *b;
7372   apr_file_t *file;
7373   representation_t *base_rep;
7374   svn_stream_t *source;
7375   const char *header;
7376   svn_txdelta_window_handler_t wh;
7377   void *whb;
7378   fs_fs_data_t *ffd = fs->fsap_data;
7379   int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7380
7381   b = apr_pcalloc(pool, sizeof(*b));
7382
7383   b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7384   b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7385
7386   b->fs = fs;
7387   b->parent_pool = pool;
7388   b->pool = svn_pool_create(pool);
7389   b->rep_size = 0;
7390   b->noderev = noderev;
7391
7392   /* Open the prototype rev file and seek to its end. */
7393   SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
7394                                  fs, svn_fs_fs__id_txn_id(noderev->id),
7395                                  b->pool));
7396
7397   b->file = file;
7398   b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool);
7399
7400   SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
7401
7402   /* Get the base for this delta. */
7403   SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool));
7404   SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
7405
7406   /* Write out the rep header. */
7407   if (base_rep)
7408     {
7409       header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7410                             SVN_FILESIZE_T_FMT "\n",
7411                             base_rep->revision, base_rep->offset,
7412                             base_rep->size);
7413     }
7414   else
7415     {
7416       header = REP_DELTA "\n";
7417     }
7418   SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7419                                  b->pool));
7420
7421   /* Now determine the offset of the actual svndiff data. */
7422   SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
7423
7424   /* Cleanup in case something goes wrong. */
7425   apr_pool_cleanup_register(b->pool, b, rep_write_cleanup,
7426                             apr_pool_cleanup_null);
7427
7428   /* Prepare to write the svndiff data. */
7429   svn_txdelta_to_svndiff3(&wh,
7430                           &whb,
7431                           b->rep_stream,
7432                           diff_version,
7433                           SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7434                           pool);
7435
7436   b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
7437
7438   *wb_p = b;
7439
7440   return SVN_NO_ERROR;
7441 }
7442
7443 /* For the hash REP->SHA1, try to find an already existing representation
7444    in FS and return it in *OUT_REP.  If no such representation exists or
7445    if rep sharing has been disabled for FS, NULL will be returned.  Since
7446    there may be new duplicate representations within the same uncommitted
7447    revision, those can be passed in REPS_HASH (maps a sha1 digest onto
7448    representation_t*), otherwise pass in NULL for REPS_HASH.
7449    POOL will be used for allocations. The lifetime of the returned rep is
7450    limited by both, POOL and REP lifetime.
7451  */
7452 static svn_error_t *
7453 get_shared_rep(representation_t **old_rep,
7454                svn_fs_t *fs,
7455                representation_t *rep,
7456                apr_hash_t *reps_hash,
7457                apr_pool_t *pool)
7458 {
7459   svn_error_t *err;
7460   fs_fs_data_t *ffd = fs->fsap_data;
7461
7462   /* Return NULL, if rep sharing has been disabled. */
7463   *old_rep = NULL;
7464   if (!ffd->rep_sharing_allowed)
7465     return SVN_NO_ERROR;
7466
7467   /* Check and see if we already have a representation somewhere that's
7468      identical to the one we just wrote out.  Start with the hash lookup
7469      because it is cheepest. */
7470   if (reps_hash)
7471     *old_rep = apr_hash_get(reps_hash,
7472                             rep->sha1_checksum->digest,
7473                             APR_SHA1_DIGESTSIZE);
7474
7475   /* If we haven't found anything yet, try harder and consult our DB. */
7476   if (*old_rep == NULL)
7477     {
7478       err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum,
7479                                          pool);
7480       /* ### Other error codes that we shouldn't mask out? */
7481       if (err == SVN_NO_ERROR)
7482         {
7483           if (*old_rep)
7484             SVN_ERR(verify_walker(*old_rep, NULL, fs, pool));
7485         }
7486       else if (err->apr_err == SVN_ERR_FS_CORRUPT
7487                || SVN_ERROR_IN_CATEGORY(err->apr_err,
7488                                         SVN_ERR_MALFUNC_CATEGORY_START))
7489         {
7490           /* Fatal error; don't mask it.
7491
7492              In particular, this block is triggered when the rep-cache refers
7493              to revisions in the future.  We signal that as a corruption situation
7494              since, once those revisions are less than youngest (because of more
7495              commits), the rep-cache would be invalid.
7496            */
7497           SVN_ERR(err);
7498         }
7499       else
7500         {
7501           /* Something's wrong with the rep-sharing index.  We can continue
7502              without rep-sharing, but warn.
7503            */
7504           (fs->warning)(fs->warning_baton, err);
7505           svn_error_clear(err);
7506           *old_rep = NULL;
7507         }
7508     }
7509
7510   /* look for intra-revision matches (usually data reps but not limited
7511      to them in case props happen to look like some data rep)
7512    */
7513   if (*old_rep == NULL && rep->txn_id)
7514     {
7515       svn_node_kind_t kind;
7516       const char *file_name
7517         = path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool);
7518
7519       /* in our txn, is there a rep file named with the wanted SHA1?
7520          If so, read it and use that rep.
7521        */
7522       SVN_ERR(svn_io_check_path(file_name, &kind, pool));
7523       if (kind == svn_node_file)
7524         {
7525           svn_stringbuf_t *rep_string;
7526           SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool));
7527           SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data,
7528                                         rep->txn_id, FALSE, pool));
7529         }
7530     }
7531
7532   /* Add information that is missing in the cached data. */
7533   if (*old_rep)
7534     {
7535       /* Use the old rep for this content. */
7536       (*old_rep)->md5_checksum = rep->md5_checksum;
7537       (*old_rep)->uniquifier = rep->uniquifier;
7538     }
7539
7540   return SVN_NO_ERROR;
7541 }
7542
7543 /* Close handler for the representation write stream.  BATON is a
7544    rep_write_baton.  Writes out a new node-rev that correctly
7545    references the representation we just finished writing. */
7546 static svn_error_t *
7547 rep_write_contents_close(void *baton)
7548 {
7549   struct rep_write_baton *b = baton;
7550   const char *unique_suffix;
7551   representation_t *rep;
7552   representation_t *old_rep;
7553   apr_off_t offset;
7554
7555   rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
7556   rep->offset = b->rep_offset;
7557
7558   /* Close our delta stream so the last bits of svndiff are written
7559      out. */
7560   if (b->delta_stream)
7561     SVN_ERR(svn_stream_close(b->delta_stream));
7562
7563   /* Determine the length of the svndiff data. */
7564   SVN_ERR(get_file_offset(&offset, b->file, b->pool));
7565   rep->size = offset - b->delta_start;
7566
7567   /* Fill in the rest of the representation field. */
7568   rep->expanded_size = b->rep_size;
7569   rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7570   SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool));
7571   rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id,
7572                                  unique_suffix);
7573   rep->revision = SVN_INVALID_REVNUM;
7574
7575   /* Finalize the checksum. */
7576   SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx,
7577                               b->parent_pool));
7578   SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx,
7579                               b->parent_pool));
7580
7581   /* Check and see if we already have a representation somewhere that's
7582      identical to the one we just wrote out. */
7583   SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool));
7584
7585   if (old_rep)
7586     {
7587       /* We need to erase from the protorev the data we just wrote. */
7588       SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool));
7589
7590       /* Use the old rep for this content. */
7591       b->noderev->data_rep = old_rep;
7592     }
7593   else
7594     {
7595       /* Write out our cosmetic end marker. */
7596       SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
7597
7598       b->noderev->data_rep = rep;
7599     }
7600
7601   /* Remove cleanup callback. */
7602   apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup);
7603
7604   /* Write out the new node-rev information. */
7605   SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
7606                                        b->pool));
7607   if (!old_rep)
7608     SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool));
7609
7610   SVN_ERR(svn_io_file_close(b->file, b->pool));
7611   SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
7612   svn_pool_destroy(b->pool);
7613
7614   return SVN_NO_ERROR;
7615 }
7616
7617 /* Store a writable stream in *CONTENTS_P that will receive all data
7618    written and store it as the file data representation referenced by
7619    NODEREV in filesystem FS.  Perform temporary allocations in
7620    POOL.  Only appropriate for file data, not props or directory
7621    contents. */
7622 static svn_error_t *
7623 set_representation(svn_stream_t **contents_p,
7624                    svn_fs_t *fs,
7625                    node_revision_t *noderev,
7626                    apr_pool_t *pool)
7627 {
7628   struct rep_write_baton *wb;
7629
7630   if (! svn_fs_fs__id_txn_id(noderev->id))
7631     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7632                              _("Attempted to write to non-transaction '%s'"),
7633                              svn_fs_fs__id_unparse(noderev->id, pool)->data);
7634
7635   SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
7636
7637   *contents_p = svn_stream_create(wb, pool);
7638   svn_stream_set_write(*contents_p, rep_write_contents);
7639   svn_stream_set_close(*contents_p, rep_write_contents_close);
7640
7641   return SVN_NO_ERROR;
7642 }
7643
7644 svn_error_t *
7645 svn_fs_fs__set_contents(svn_stream_t **stream,
7646                         svn_fs_t *fs,
7647                         node_revision_t *noderev,
7648                         apr_pool_t *pool)
7649 {
7650   if (noderev->kind != svn_node_file)
7651     return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
7652                             _("Can't set text contents of a directory"));
7653
7654   return set_representation(stream, fs, noderev, pool);
7655 }
7656
7657 svn_error_t *
7658 svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
7659                             svn_fs_t *fs,
7660                             const svn_fs_id_t *old_idp,
7661                             node_revision_t *new_noderev,
7662                             const char *copy_id,
7663                             const char *txn_id,
7664                             apr_pool_t *pool)
7665 {
7666   const svn_fs_id_t *id;
7667
7668   if (! copy_id)
7669     copy_id = svn_fs_fs__id_copy_id(old_idp);
7670   id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
7671                                 txn_id, pool);
7672
7673   new_noderev->id = id;
7674
7675   if (! new_noderev->copyroot_path)
7676     {
7677       new_noderev->copyroot_path = apr_pstrdup(pool,
7678                                                new_noderev->created_path);
7679       new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
7680     }
7681
7682   SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
7683                                        pool));
7684
7685   *new_id_p = id;
7686
7687   return SVN_NO_ERROR;
7688 }
7689
7690 svn_error_t *
7691 svn_fs_fs__set_proplist(svn_fs_t *fs,
7692                         node_revision_t *noderev,
7693                         apr_hash_t *proplist,
7694                         apr_pool_t *pool)
7695 {
7696   const char *filename = path_txn_node_props(fs, noderev->id, pool);
7697   apr_file_t *file;
7698   svn_stream_t *out;
7699
7700   /* Dump the property list to the mutable property file. */
7701   SVN_ERR(svn_io_file_open(&file, filename,
7702                            APR_WRITE | APR_CREATE | APR_TRUNCATE
7703                            | APR_BUFFERED, APR_OS_DEFAULT, pool));
7704   out = svn_stream_from_aprfile2(file, TRUE, pool);
7705   SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
7706   SVN_ERR(svn_io_file_close(file, pool));
7707
7708   /* Mark the node-rev's prop rep as mutable, if not already done. */
7709   if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
7710     {
7711       noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
7712       noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
7713       SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
7714     }
7715
7716   return SVN_NO_ERROR;
7717 }
7718
7719 /* Read the 'current' file for filesystem FS and store the next
7720    available node id in *NODE_ID, and the next available copy id in
7721    *COPY_ID.  Allocations are performed from POOL. */
7722 static svn_error_t *
7723 get_next_revision_ids(const char **node_id,
7724                       const char **copy_id,
7725                       svn_fs_t *fs,
7726                       apr_pool_t *pool)
7727 {
7728   char *buf;
7729   char *str;
7730   svn_stringbuf_t *content;
7731
7732   SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool));
7733   buf = content->data;
7734
7735   str = svn_cstring_tokenize(" ", &buf);
7736   if (! str)
7737     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7738                             _("Corrupt 'current' file"));
7739
7740   str = svn_cstring_tokenize(" ", &buf);
7741   if (! str)
7742     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7743                             _("Corrupt 'current' file"));
7744
7745   *node_id = apr_pstrdup(pool, str);
7746
7747   str = svn_cstring_tokenize(" \n", &buf);
7748   if (! str)
7749     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7750                             _("Corrupt 'current' file"));
7751
7752   *copy_id = apr_pstrdup(pool, str);
7753
7754   return SVN_NO_ERROR;
7755 }
7756
7757 /* This baton is used by the stream created for write_hash_rep. */
7758 struct write_hash_baton
7759 {
7760   svn_stream_t *stream;
7761
7762   apr_size_t size;
7763
7764   svn_checksum_ctx_t *md5_ctx;
7765   svn_checksum_ctx_t *sha1_ctx;
7766 };
7767
7768 /* The handler for the write_hash_rep stream.  BATON is a
7769    write_hash_baton, DATA has the data to write and *LEN is the number
7770    of bytes to write. */
7771 static svn_error_t *
7772 write_hash_handler(void *baton,
7773                    const char *data,
7774                    apr_size_t *len)
7775 {
7776   struct write_hash_baton *whb = baton;
7777
7778   SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
7779   SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
7780
7781   SVN_ERR(svn_stream_write(whb->stream, data, len));
7782   whb->size += *len;
7783
7784   return SVN_NO_ERROR;
7785 }
7786
7787 /* Write out the hash HASH as a text representation to file FILE.  In
7788    the process, record position, the total size of the dump and MD5 as
7789    well as SHA1 in REP.   If rep sharing has been enabled and REPS_HASH
7790    is not NULL, it will be used in addition to the on-disk cache to find
7791    earlier reps with the same content.  When such existing reps can be
7792    found, we will truncate the one just written from the file and return
7793    the existing rep.  Perform temporary allocations in POOL. */
7794 static svn_error_t *
7795 write_hash_rep(representation_t *rep,
7796                apr_file_t *file,
7797                apr_hash_t *hash,
7798                svn_fs_t *fs,
7799                apr_hash_t *reps_hash,
7800                apr_pool_t *pool)
7801 {
7802   svn_stream_t *stream;
7803   struct write_hash_baton *whb;
7804   representation_t *old_rep;
7805
7806   SVN_ERR(get_file_offset(&rep->offset, file, pool));
7807
7808   whb = apr_pcalloc(pool, sizeof(*whb));
7809
7810   whb->stream = svn_stream_from_aprfile2(file, TRUE, pool);
7811   whb->size = 0;
7812   whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7813   whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7814
7815   stream = svn_stream_create(whb, pool);
7816   svn_stream_set_write(stream, write_hash_handler);
7817
7818   SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
7819
7820   SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7821
7822   /* Store the results. */
7823   SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7824   SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7825
7826   /* Check and see if we already have a representation somewhere that's
7827      identical to the one we just wrote out. */
7828   SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7829
7830   if (old_rep)
7831     {
7832       /* We need to erase from the protorev the data we just wrote. */
7833       SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7834
7835       /* Use the old rep for this content. */
7836       memcpy(rep, old_rep, sizeof (*rep));
7837     }
7838   else
7839     {
7840       /* Write out our cosmetic end marker. */
7841       SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
7842
7843       /* update the representation */
7844       rep->size = whb->size;
7845       rep->expanded_size = 0;
7846     }
7847
7848   return SVN_NO_ERROR;
7849 }
7850
7851 /* Write out the hash HASH pertaining to the NODEREV in FS as a deltified
7852    text representation to file FILE.  In the process, record the total size
7853    and the md5 digest in REP.  If rep sharing has been enabled and REPS_HASH
7854    is not NULL, it will be used in addition to the on-disk cache to find
7855    earlier reps with the same content.  When such existing reps can be found,
7856    we will truncate the one just written from the file and return the existing
7857    rep.  If PROPS is set, assume that we want to a props representation as
7858    the base for our delta.  Perform temporary allocations in POOL. */
7859 static svn_error_t *
7860 write_hash_delta_rep(representation_t *rep,
7861                      apr_file_t *file,
7862                      apr_hash_t *hash,
7863                      svn_fs_t *fs,
7864                      node_revision_t *noderev,
7865                      apr_hash_t *reps_hash,
7866                      svn_boolean_t props,
7867                      apr_pool_t *pool)
7868 {
7869   svn_txdelta_window_handler_t diff_wh;
7870   void *diff_whb;
7871
7872   svn_stream_t *file_stream;
7873   svn_stream_t *stream;
7874   representation_t *base_rep;
7875   representation_t *old_rep;
7876   svn_stream_t *source;
7877   const char *header;
7878
7879   apr_off_t rep_end = 0;
7880   apr_off_t delta_start = 0;
7881
7882   struct write_hash_baton *whb;
7883   fs_fs_data_t *ffd = fs->fsap_data;
7884   int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7885
7886   /* Get the base for this delta. */
7887   SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool));
7888   SVN_ERR(read_representation(&source, fs, base_rep, pool));
7889
7890   SVN_ERR(get_file_offset(&rep->offset, file, pool));
7891
7892   /* Write out the rep header. */
7893   if (base_rep)
7894     {
7895       header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7896                             SVN_FILESIZE_T_FMT "\n",
7897                             base_rep->revision, base_rep->offset,
7898                             base_rep->size);
7899     }
7900   else
7901     {
7902       header = REP_DELTA "\n";
7903     }
7904   SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7905                                  pool));
7906
7907   SVN_ERR(get_file_offset(&delta_start, file, pool));
7908   file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
7909
7910   /* Prepare to write the svndiff data. */
7911   svn_txdelta_to_svndiff3(&diff_wh,
7912                           &diff_whb,
7913                           file_stream,
7914                           diff_version,
7915                           SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7916                           pool);
7917
7918   whb = apr_pcalloc(pool, sizeof(*whb));
7919   whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
7920   whb->size = 0;
7921   whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7922   whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7923
7924   /* serialize the hash */
7925   stream = svn_stream_create(whb, pool);
7926   svn_stream_set_write(stream, write_hash_handler);
7927
7928   SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7929   SVN_ERR(svn_stream_close(whb->stream));
7930
7931   /* Store the results. */
7932   SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7933   SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7934
7935   /* Check and see if we already have a representation somewhere that's
7936      identical to the one we just wrote out. */
7937   SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7938
7939   if (old_rep)
7940     {
7941       /* We need to erase from the protorev the data we just wrote. */
7942       SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7943
7944       /* Use the old rep for this content. */
7945       memcpy(rep, old_rep, sizeof (*rep));
7946     }
7947   else
7948     {
7949       /* Write out our cosmetic end marker. */
7950       SVN_ERR(get_file_offset(&rep_end, file, pool));
7951       SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
7952
7953       /* update the representation */
7954       rep->expanded_size = whb->size;
7955       rep->size = rep_end - delta_start;
7956     }
7957
7958   return SVN_NO_ERROR;
7959 }
7960
7961 /* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
7962    of (not yet committed) revision REV in FS.  Use POOL for temporary
7963    allocations.
7964
7965    If you change this function, consider updating svn_fs_fs__verify() too.
7966  */
7967 static svn_error_t *
7968 validate_root_noderev(svn_fs_t *fs,
7969                       node_revision_t *root_noderev,
7970                       svn_revnum_t rev,
7971                       apr_pool_t *pool)
7972 {
7973   svn_revnum_t head_revnum = rev-1;
7974   int head_predecessor_count;
7975
7976   SVN_ERR_ASSERT(rev > 0);
7977
7978   /* Compute HEAD_PREDECESSOR_COUNT. */
7979   {
7980     svn_fs_root_t *head_revision;
7981     const svn_fs_id_t *head_root_id;
7982     node_revision_t *head_root_noderev;
7983
7984     /* Get /@HEAD's noderev. */
7985     SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
7986     SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
7987     SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
7988                                          pool));
7989
7990     head_predecessor_count = head_root_noderev->predecessor_count;
7991   }
7992
7993   /* Check that the root noderev's predecessor count equals REV.
7994
7995      This kind of corruption was seen on svn.apache.org (both on
7996      the root noderev and on other fspaths' noderevs); see
7997      issue #4129.
7998
7999      Normally (rev == root_noderev->predecessor_count), but here we
8000      use a more roundabout check that should only trigger on new instances
8001      of the corruption, rather then trigger on each and every new commit
8002      to a repository that has triggered the bug somewhere in its root
8003      noderev's history.
8004    */
8005   if (root_noderev->predecessor_count != -1
8006       && (root_noderev->predecessor_count - head_predecessor_count)
8007          != (rev - head_revnum))
8008     {
8009       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
8010                                _("predecessor count for "
8011                                  "the root node-revision is wrong: "
8012                                  "found (%d+%ld != %d), committing r%ld"),
8013                                  head_predecessor_count,
8014                                  rev - head_revnum, /* This is equal to 1. */
8015                                  root_noderev->predecessor_count,
8016                                  rev);
8017     }
8018
8019   return SVN_NO_ERROR;
8020 }
8021
8022 /* Copy a node-revision specified by id ID in fileystem FS from a
8023    transaction into the proto-rev-file FILE.  Set *NEW_ID_P to a
8024    pointer to the new node-id which will be allocated in POOL.
8025    If this is a directory, copy all children as well.
8026
8027    START_NODE_ID and START_COPY_ID are
8028    the first available node and copy ids for this filesystem, for older
8029    FS formats.
8030
8031    REV is the revision number that this proto-rev-file will represent.
8032
8033    INITIAL_OFFSET is the offset of the proto-rev-file on entry to
8034    commit_body.
8035
8036    If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
8037    REPS_POOL) of each data rep that is new in this revision.
8038
8039    If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
8040    of the representations of each property rep that is new in this
8041    revision.
8042
8043    AT_ROOT is true if the node revision being written is the root
8044    node-revision.  It is only controls additional sanity checking
8045    logic.
8046
8047    Temporary allocations are also from POOL. */
8048 static svn_error_t *
8049 write_final_rev(const svn_fs_id_t **new_id_p,
8050                 apr_file_t *file,
8051                 svn_revnum_t rev,
8052                 svn_fs_t *fs,
8053                 const svn_fs_id_t *id,
8054                 const char *start_node_id,
8055                 const char *start_copy_id,
8056                 apr_off_t initial_offset,
8057                 apr_array_header_t *reps_to_cache,
8058                 apr_hash_t *reps_hash,
8059                 apr_pool_t *reps_pool,
8060                 svn_boolean_t at_root,
8061                 apr_pool_t *pool)
8062 {
8063   node_revision_t *noderev;
8064   apr_off_t my_offset;
8065   char my_node_id_buf[MAX_KEY_SIZE + 2];
8066   char my_copy_id_buf[MAX_KEY_SIZE + 2];
8067   const svn_fs_id_t *new_id;
8068   const char *node_id, *copy_id, *my_node_id, *my_copy_id;
8069   fs_fs_data_t *ffd = fs->fsap_data;
8070
8071   *new_id_p = NULL;
8072
8073   /* Check to see if this is a transaction node. */
8074   if (! svn_fs_fs__id_txn_id(id))
8075     return SVN_NO_ERROR;
8076
8077   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
8078
8079   if (noderev->kind == svn_node_dir)
8080     {
8081       apr_pool_t *subpool;
8082       apr_hash_t *entries, *str_entries;
8083       apr_array_header_t *sorted_entries;
8084       int i;
8085
8086       /* This is a directory.  Write out all the children first. */
8087       subpool = svn_pool_create(pool);
8088
8089       SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
8090       /* For the sake of the repository administrator sort the entries
8091          so that the final file is deterministic and repeatable,
8092          however the rest of the FSFS code doesn't require any
8093          particular order here. */
8094       sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically,
8095                                       pool);
8096       for (i = 0; i < sorted_entries->nelts; ++i)
8097         {
8098           svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i,
8099                                                   svn_sort__item_t).value;
8100
8101           svn_pool_clear(subpool);
8102           SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
8103                                   start_node_id, start_copy_id, initial_offset,
8104                                   reps_to_cache, reps_hash, reps_pool, FALSE,
8105                                   subpool));
8106           if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
8107             dirent->id = svn_fs_fs__id_copy(new_id, pool);
8108         }
8109       svn_pool_destroy(subpool);
8110
8111       if (noderev->data_rep && noderev->data_rep->txn_id)
8112         {
8113           /* Write out the contents of this directory as a text rep. */
8114           SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
8115
8116           noderev->data_rep->txn_id = NULL;
8117           noderev->data_rep->revision = rev;
8118
8119           if (ffd->deltify_directories)
8120             SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
8121                                          str_entries, fs, noderev, NULL,
8122                                          FALSE, pool));
8123           else
8124             SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
8125                                    fs, NULL, pool));
8126         }
8127     }
8128   else
8129     {
8130       /* This is a file.  We should make sure the data rep, if it
8131          exists in a "this" state, gets rewritten to our new revision
8132          num. */
8133
8134       if (noderev->data_rep && noderev->data_rep->txn_id)
8135         {
8136           noderev->data_rep->txn_id = NULL;
8137           noderev->data_rep->revision = rev;
8138
8139           /* See issue 3845.  Some unknown mechanism caused the
8140              protorev file to get truncated, so check for that
8141              here.  */
8142           if (noderev->data_rep->offset + noderev->data_rep->size
8143               > initial_offset)
8144             return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
8145                                     _("Truncated protorev file detected"));
8146         }
8147     }
8148
8149   /* Fix up the property reps. */
8150   if (noderev->prop_rep && noderev->prop_rep->txn_id)
8151     {
8152       apr_hash_t *proplist;
8153       SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
8154
8155       noderev->prop_rep->txn_id = NULL;
8156       noderev->prop_rep->revision = rev;
8157
8158       if (ffd->deltify_properties)
8159         SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
8160                                      proplist, fs, noderev, reps_hash,
8161                                      TRUE, pool));
8162       else
8163         SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
8164                                fs, reps_hash, pool));
8165     }
8166
8167
8168   /* Convert our temporary ID into a permanent revision one. */
8169   SVN_ERR(get_file_offset(&my_offset, file, pool));
8170
8171   node_id = svn_fs_fs__id_node_id(noderev->id);
8172   if (*node_id == '_')
8173     {
8174       if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8175         my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev);
8176       else
8177         {
8178           svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf);
8179           my_node_id = my_node_id_buf;
8180         }
8181     }
8182   else
8183     my_node_id = node_id;
8184
8185   copy_id = svn_fs_fs__id_copy_id(noderev->id);
8186   if (*copy_id == '_')
8187     {
8188       if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8189         my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev);
8190       else
8191         {
8192           svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf);
8193           my_copy_id = my_copy_id_buf;
8194         }
8195     }
8196   else
8197     my_copy_id = copy_id;
8198
8199   if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
8200     noderev->copyroot_rev = rev;
8201
8202   new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
8203                                     pool);
8204
8205   noderev->id = new_id;
8206
8207   if (ffd->rep_sharing_allowed)
8208     {
8209       /* Save the data representation's hash in the rep cache. */
8210       if (   noderev->data_rep && noderev->kind == svn_node_file
8211           && noderev->data_rep->revision == rev)
8212         {
8213           SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8214           APR_ARRAY_PUSH(reps_to_cache, representation_t *)
8215             = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
8216         }
8217
8218       if (noderev->prop_rep && noderev->prop_rep->revision == rev)
8219         {
8220           /* Add new property reps to hash and on-disk cache. */
8221           representation_t *copy
8222             = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
8223
8224           SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8225           APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
8226
8227           apr_hash_set(reps_hash,
8228                         copy->sha1_checksum->digest,
8229                         APR_SHA1_DIGESTSIZE,
8230                         copy);
8231         }
8232     }
8233
8234   /* don't serialize SHA1 for dirs to disk (waste of space) */
8235   if (noderev->data_rep && noderev->kind == svn_node_dir)
8236     noderev->data_rep->sha1_checksum = NULL;
8237
8238   /* don't serialize SHA1 for props to disk (waste of space) */
8239   if (noderev->prop_rep)
8240     noderev->prop_rep->sha1_checksum = NULL;
8241
8242   /* Workaround issue #4031: is-fresh-txn-root in revision files. */
8243   noderev->is_fresh_txn_root = FALSE;
8244
8245   /* Write out our new node-revision. */
8246   if (at_root)
8247     SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
8248
8249   SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool),
8250                                    noderev, ffd->format,
8251                                    svn_fs_fs__fs_supports_mergeinfo(fs),
8252                                    pool));
8253
8254   /* Return our ID that references the revision file. */
8255   *new_id_p = noderev->id;
8256
8257   return SVN_NO_ERROR;
8258 }
8259
8260 /* Write the changed path info from transaction TXN_ID in filesystem
8261    FS to the permanent rev-file FILE.  *OFFSET_P is set the to offset
8262    in the file of the beginning of this information.  Perform
8263    temporary allocations in POOL. */
8264 static svn_error_t *
8265 write_final_changed_path_info(apr_off_t *offset_p,
8266                               apr_file_t *file,
8267                               svn_fs_t *fs,
8268                               const char *txn_id,
8269                               apr_pool_t *pool)
8270 {
8271   apr_hash_t *changed_paths;
8272   apr_off_t offset;
8273   apr_pool_t *iterpool = svn_pool_create(pool);
8274   fs_fs_data_t *ffd = fs->fsap_data;
8275   svn_boolean_t include_node_kinds =
8276       ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
8277   apr_array_header_t *sorted_changed_paths;
8278   int i;
8279
8280   SVN_ERR(get_file_offset(&offset, file, pool));
8281
8282   SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool));
8283   /* For the sake of the repository administrator sort the changes so
8284      that the final file is deterministic and repeatable, however the
8285      rest of the FSFS code doesn't require any particular order here. */
8286   sorted_changed_paths = svn_sort__hash(changed_paths,
8287                                         svn_sort_compare_items_lexically, pool);
8288
8289   /* Iterate through the changed paths one at a time, and convert the
8290      temporary node-id into a permanent one for each change entry. */
8291   for (i = 0; i < sorted_changed_paths->nelts; ++i)
8292     {
8293       node_revision_t *noderev;
8294       const svn_fs_id_t *id;
8295       svn_fs_path_change2_t *change;
8296       const char *path;
8297
8298       svn_pool_clear(iterpool);
8299
8300       change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
8301       path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
8302
8303       id = change->node_rev_id;
8304
8305       /* If this was a delete of a mutable node, then it is OK to
8306          leave the change entry pointing to the non-existent temporary
8307          node, since it will never be used. */
8308       if ((change->change_kind != svn_fs_path_change_delete) &&
8309           (! svn_fs_fs__id_txn_id(id)))
8310         {
8311           SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
8312
8313           /* noderev has the permanent node-id at this point, so we just
8314              substitute it for the temporary one. */
8315           change->node_rev_id = noderev->id;
8316         }
8317
8318       /* Write out the new entry into the final rev-file. */
8319       SVN_ERR(write_change_entry(file, path, change, include_node_kinds,
8320                                  iterpool));
8321     }
8322
8323   svn_pool_destroy(iterpool);
8324
8325   *offset_p = offset;
8326
8327   return SVN_NO_ERROR;
8328 }
8329
8330 /* Atomically update the 'current' file to hold the specifed REV,
8331    NEXT_NODE_ID, and NEXT_COPY_ID.  (The two next-ID parameters are
8332    ignored and may be NULL if the FS format does not use them.)
8333    Perform temporary allocations in POOL. */
8334 static svn_error_t *
8335 write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
8336               const char *next_copy_id, apr_pool_t *pool)
8337 {
8338   char *buf;
8339   const char *tmp_name, *name;
8340   fs_fs_data_t *ffd = fs->fsap_data;
8341
8342   /* Now we can just write out this line. */
8343   if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8344     buf = apr_psprintf(pool, "%ld\n", rev);
8345   else
8346     buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
8347
8348   name = svn_fs_fs__path_current(fs, pool);
8349   SVN_ERR(svn_io_write_unique(&tmp_name,
8350                               svn_dirent_dirname(name, pool),
8351                               buf, strlen(buf),
8352                               svn_io_file_del_none, pool));
8353
8354   return move_into_place(tmp_name, name, name, pool);
8355 }
8356
8357 /* Open a new svn_fs_t handle to FS, set that handle's concept of "current
8358    youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
8359    NEW_REV's revision root.
8360
8361    Intended to be called as the very last step in a commit before 'current'
8362    is bumped.  This implies that we are holding the write lock. */
8363 static svn_error_t *
8364 verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
8365                                             svn_revnum_t new_rev,
8366                                             apr_pool_t *pool)
8367 {
8368 #ifdef SVN_DEBUG
8369   fs_fs_data_t *ffd = fs->fsap_data;
8370   svn_fs_t *ft; /* fs++ == ft */
8371   svn_fs_root_t *root;
8372   fs_fs_data_t *ft_ffd;
8373   apr_hash_t *fs_config;
8374
8375   SVN_ERR_ASSERT(ffd->svn_fs_open_);
8376
8377   /* make sure FT does not simply return data cached by other instances
8378    * but actually retrieves it from disk at least once.
8379    */
8380   fs_config = apr_hash_make(pool);
8381   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
8382                            svn_uuid_generate(pool));
8383   SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
8384                             fs_config,
8385                             pool));
8386   ft_ffd = ft->fsap_data;
8387   /* Don't let FT consult rep-cache.db, either. */
8388   ft_ffd->rep_sharing_allowed = FALSE;
8389
8390   /* Time travel! */
8391   ft_ffd->youngest_rev_cache = new_rev;
8392
8393   SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
8394   SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
8395   SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
8396   SVN_ERR(svn_fs_fs__verify_root(root, pool));
8397 #endif /* SVN_DEBUG */
8398
8399   return SVN_NO_ERROR;
8400 }
8401
8402 /* Update the 'current' file to hold the correct next node and copy_ids
8403    from transaction TXN_ID in filesystem FS.  The current revision is
8404    set to REV.  Perform temporary allocations in POOL. */
8405 static svn_error_t *
8406 write_final_current(svn_fs_t *fs,
8407                     const char *txn_id,
8408                     svn_revnum_t rev,
8409                     const char *start_node_id,
8410                     const char *start_copy_id,
8411                     apr_pool_t *pool)
8412 {
8413   const char *txn_node_id, *txn_copy_id;
8414   char new_node_id[MAX_KEY_SIZE + 2];
8415   char new_copy_id[MAX_KEY_SIZE + 2];
8416   fs_fs_data_t *ffd = fs->fsap_data;
8417
8418   if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8419     return write_current(fs, rev, NULL, NULL, pool);
8420
8421   /* To find the next available ids, we add the id that used to be in
8422      the 'current' file, to the next ids from the transaction file. */
8423   SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
8424
8425   svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
8426   svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
8427
8428   return write_current(fs, rev, new_node_id, new_copy_id, pool);
8429 }
8430
8431 /* Verify that the user registed with FS has all the locks necessary to
8432    permit all the changes associate with TXN_NAME.
8433    The FS write lock is assumed to be held by the caller. */
8434 static svn_error_t *
8435 verify_locks(svn_fs_t *fs,
8436              const char *txn_name,
8437              apr_pool_t *pool)
8438 {
8439   apr_pool_t *subpool = svn_pool_create(pool);
8440   apr_hash_t *changes;
8441   apr_hash_index_t *hi;
8442   apr_array_header_t *changed_paths;
8443   svn_stringbuf_t *last_recursed = NULL;
8444   int i;
8445
8446   /* Fetch the changes for this transaction. */
8447   SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool));
8448
8449   /* Make an array of the changed paths, and sort them depth-first-ily.  */
8450   changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
8451                                  sizeof(const char *));
8452   for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
8453     APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi);
8454   qsort(changed_paths->elts, changed_paths->nelts,
8455         changed_paths->elt_size, svn_sort_compare_paths);
8456
8457   /* Now, traverse the array of changed paths, verify locks.  Note
8458      that if we need to do a recursive verification a path, we'll skip
8459      over children of that path when we get to them. */
8460   for (i = 0; i < changed_paths->nelts; i++)
8461     {
8462       const char *path;
8463       svn_fs_path_change2_t *change;
8464       svn_boolean_t recurse = TRUE;
8465
8466       svn_pool_clear(subpool);
8467       path = APR_ARRAY_IDX(changed_paths, i, const char *);
8468
8469       /* If this path has already been verified as part of a recursive
8470          check of one of its parents, no need to do it again.  */
8471       if (last_recursed
8472           && svn_dirent_is_child(last_recursed->data, path, subpool))
8473         continue;
8474
8475       /* Fetch the change associated with our path.  */
8476       change = svn_hash_gets(changes, path);
8477
8478       /* What does it mean to succeed at lock verification for a given
8479          path?  For an existing file or directory getting modified
8480          (text, props), it means we hold the lock on the file or
8481          directory.  For paths being added or removed, we need to hold
8482          the locks for that path and any children of that path.
8483
8484          WHEW!  We have no reliable way to determine the node kind
8485          of deleted items, but fortunately we are going to do a
8486          recursive check on deleted paths regardless of their kind.  */
8487       if (change->change_kind == svn_fs_path_change_modify)
8488         recurse = FALSE;
8489       SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
8490                                                 subpool));
8491
8492       /* If we just did a recursive check, remember the path we
8493          checked (so children can be skipped).  */
8494       if (recurse)
8495         {
8496           if (! last_recursed)
8497             last_recursed = svn_stringbuf_create(path, pool);
8498           else
8499             svn_stringbuf_set(last_recursed, path);
8500         }
8501     }
8502   svn_pool_destroy(subpool);
8503   return SVN_NO_ERROR;
8504 }
8505
8506 /* Baton used for commit_body below. */
8507 struct commit_baton {
8508   svn_revnum_t *new_rev_p;
8509   svn_fs_t *fs;
8510   svn_fs_txn_t *txn;
8511   apr_array_header_t *reps_to_cache;
8512   apr_hash_t *reps_hash;
8513   apr_pool_t *reps_pool;
8514 };
8515
8516 /* The work-horse for svn_fs_fs__commit, called with the FS write lock.
8517    This implements the svn_fs_fs__with_write_lock() 'body' callback
8518    type.  BATON is a 'struct commit_baton *'. */
8519 static svn_error_t *
8520 commit_body(void *baton, apr_pool_t *pool)
8521 {
8522   struct commit_baton *cb = baton;
8523   fs_fs_data_t *ffd = cb->fs->fsap_data;
8524   const char *old_rev_filename, *rev_filename, *proto_filename;
8525   const char *revprop_filename, *final_revprop;
8526   const svn_fs_id_t *root_id, *new_root_id;
8527   const char *start_node_id = NULL, *start_copy_id = NULL;
8528   svn_revnum_t old_rev, new_rev;
8529   apr_file_t *proto_file;
8530   void *proto_file_lockcookie;
8531   apr_off_t initial_offset, changed_path_offset;
8532   char *buf;
8533   apr_hash_t *txnprops;
8534   apr_array_header_t *txnprop_list;
8535   svn_prop_t prop;
8536   svn_string_t date;
8537
8538   /* Get the current youngest revision. */
8539   SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
8540
8541   /* Check to make sure this transaction is based off the most recent
8542      revision. */
8543   if (cb->txn->base_rev != old_rev)
8544     return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
8545                             _("Transaction out of date"));
8546
8547   /* Locks may have been added (or stolen) between the calling of
8548      previous svn_fs.h functions and svn_fs_commit_txn(), so we need
8549      to re-examine every changed-path in the txn and re-verify all
8550      discovered locks. */
8551   SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
8552
8553   /* Get the next node_id and copy_id to use. */
8554   if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8555     SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
8556                                   pool));
8557
8558   /* We are going to be one better than this puny old revision. */
8559   new_rev = old_rev + 1;
8560
8561   /* Get a write handle on the proto revision file. */
8562   SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
8563                                  cb->fs, cb->txn->id, pool));
8564   SVN_ERR(get_file_offset(&initial_offset, proto_file, pool));
8565
8566   /* Write out all the node-revisions and directory contents. */
8567   root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
8568   SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
8569                           start_node_id, start_copy_id, initial_offset,
8570                           cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
8571                           TRUE, pool));
8572
8573   /* Write the changed-path information. */
8574   SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
8575                                         cb->fs, cb->txn->id, pool));
8576
8577   /* Write the final line. */
8578   buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
8579                      svn_fs_fs__id_offset(new_root_id),
8580                      changed_path_offset);
8581   SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
8582                                  pool));
8583   SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
8584   SVN_ERR(svn_io_file_close(proto_file, pool));
8585
8586   /* We don't unlock the prototype revision file immediately to avoid a
8587      race with another caller writing to the prototype revision file
8588      before we commit it. */
8589
8590   /* Remove any temporary txn props representing 'flags'. */
8591   SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
8592   txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t));
8593   prop.value = NULL;
8594
8595   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
8596     {
8597       prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
8598       APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8599     }
8600
8601   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
8602     {
8603       prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
8604       APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8605     }
8606
8607   if (! apr_is_empty_array(txnprop_list))
8608     SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool));
8609
8610   /* Create the shard for the rev and revprop file, if we're sharding and
8611      this is the first revision of a new shard.  We don't care if this
8612      fails because the shard already existed for some reason. */
8613   if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
8614     {
8615       /* Create the revs shard. */
8616         {
8617           const char *new_dir = path_rev_shard(cb->fs, new_rev, pool);
8618           svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8619           if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8620             return svn_error_trace(err);
8621           svn_error_clear(err);
8622           SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8623                                                     PATH_REVS_DIR,
8624                                                     pool),
8625                                     new_dir, pool));
8626         }
8627
8628       /* Create the revprops shard. */
8629       SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8630         {
8631           const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool);
8632           svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8633           if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8634             return svn_error_trace(err);
8635           svn_error_clear(err);
8636           SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8637                                                     PATH_REVPROPS_DIR,
8638                                                     pool),
8639                                     new_dir, pool));
8640         }
8641     }
8642
8643   /* Move the finished rev file into place. */
8644   SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename,
8645                                        cb->fs, old_rev, pool));
8646   rev_filename = path_rev(cb->fs, new_rev, pool);
8647   proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
8648   SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename,
8649                           pool));
8650
8651   /* Now that we've moved the prototype revision file out of the way,
8652      we can unlock it (since further attempts to write to the file
8653      will fail as it no longer exists).  We must do this so that we can
8654      remove the transaction directory later. */
8655   SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
8656
8657   /* Update commit time to ensure that svn:date revprops remain ordered. */
8658   date.data = svn_time_to_cstring(apr_time_now(), pool);
8659   date.len = strlen(date.data);
8660
8661   SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
8662                                      &date, pool));
8663
8664   /* Move the revprops file into place. */
8665   SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8666   revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
8667   final_revprop = path_revprops(cb->fs, new_rev, pool);
8668   SVN_ERR(move_into_place(revprop_filename, final_revprop,
8669                           old_rev_filename, pool));
8670
8671   /* Update the 'current' file. */
8672   SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
8673   SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
8674                               start_copy_id, pool));
8675
8676   /* At this point the new revision is committed and globally visible
8677      so let the caller know it succeeded by giving it the new revision
8678      number, which fulfills svn_fs_commit_txn() contract.  Any errors
8679      after this point do not change the fact that a new revision was
8680      created. */
8681   *cb->new_rev_p = new_rev;
8682
8683   ffd->youngest_rev_cache = new_rev;
8684
8685   /* Remove this transaction directory. */
8686   SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
8687
8688   return SVN_NO_ERROR;
8689 }
8690
8691 /* Add the representations in REPS_TO_CACHE (an array of representation_t *)
8692  * to the rep-cache database of FS. */
8693 static svn_error_t *
8694 write_reps_to_cache(svn_fs_t *fs,
8695                     const apr_array_header_t *reps_to_cache,
8696                     apr_pool_t *scratch_pool)
8697 {
8698   int i;
8699
8700   for (i = 0; i < reps_to_cache->nelts; i++)
8701     {
8702       representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
8703
8704       /* FALSE because we don't care if another parallel commit happened to
8705        * collide with us.  (Non-parallel collisions will not be detected.) */
8706       SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool));
8707     }
8708
8709   return SVN_NO_ERROR;
8710 }
8711
8712 svn_error_t *
8713 svn_fs_fs__commit(svn_revnum_t *new_rev_p,
8714                   svn_fs_t *fs,
8715                   svn_fs_txn_t *txn,
8716                   apr_pool_t *pool)
8717 {
8718   struct commit_baton cb;
8719   fs_fs_data_t *ffd = fs->fsap_data;
8720
8721   cb.new_rev_p = new_rev_p;
8722   cb.fs = fs;
8723   cb.txn = txn;
8724
8725   if (ffd->rep_sharing_allowed)
8726     {
8727       cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
8728       cb.reps_hash = apr_hash_make(pool);
8729       cb.reps_pool = pool;
8730     }
8731   else
8732     {
8733       cb.reps_to_cache = NULL;
8734       cb.reps_hash = NULL;
8735       cb.reps_pool = NULL;
8736     }
8737
8738   SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
8739
8740   /* At this point, *NEW_REV_P has been set, so errors below won't affect
8741      the success of the commit.  (See svn_fs_commit_txn().)  */
8742
8743   if (ffd->rep_sharing_allowed)
8744     {
8745       SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
8746
8747       /* Write new entries to the rep-sharing database.
8748        *
8749        * We use an sqlite transaction to speed things up;
8750        * see <http://www.sqlite.org/faq.html#q19>.
8751        */
8752       SVN_SQLITE__WITH_TXN(
8753         write_reps_to_cache(fs, cb.reps_to_cache, pool),
8754         ffd->rep_cache_db);
8755     }
8756
8757   return SVN_NO_ERROR;
8758 }
8759
8760
8761 svn_error_t *
8762 svn_fs_fs__reserve_copy_id(const char **copy_id_p,
8763                            svn_fs_t *fs,
8764                            const char *txn_id,
8765                            apr_pool_t *pool)
8766 {
8767   const char *cur_node_id, *cur_copy_id;
8768   char *copy_id;
8769   apr_size_t len;
8770
8771   /* First read in the current next-ids file. */
8772   SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
8773
8774   copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
8775
8776   len = strlen(cur_copy_id);
8777   svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
8778
8779   SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
8780
8781   *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL);
8782
8783   return SVN_NO_ERROR;
8784 }
8785
8786 /* Write out the zeroth revision for filesystem FS. */
8787 static svn_error_t *
8788 write_revision_zero(svn_fs_t *fs)
8789 {
8790   const char *path_revision_zero = path_rev(fs, 0, fs->pool);
8791   apr_hash_t *proplist;
8792   svn_string_t date;
8793
8794   /* Write out a rev file for revision 0. */
8795   SVN_ERR(svn_io_file_create(path_revision_zero,
8796                              "PLAIN\nEND\nENDREP\n"
8797                              "id: 0.0.r0/17\n"
8798                              "type: dir\n"
8799                              "count: 0\n"
8800                              "text: 0 0 4 4 "
8801                              "2d2977d1c96f487abe4a1e202dd03b4e\n"
8802                              "cpath: /\n"
8803                              "\n\n17 107\n", fs->pool));
8804   SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
8805
8806   /* Set a date on revision 0. */
8807   date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
8808   date.len = strlen(date.data);
8809   proplist = apr_hash_make(fs->pool);
8810   svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
8811   return set_revision_proplist(fs, 0, proplist, fs->pool);
8812 }
8813
8814 svn_error_t *
8815 svn_fs_fs__create(svn_fs_t *fs,
8816                   const char *path,
8817                   apr_pool_t *pool)
8818 {
8819   int format = SVN_FS_FS__FORMAT_NUMBER;
8820   fs_fs_data_t *ffd = fs->fsap_data;
8821
8822   fs->path = apr_pstrdup(pool, path);
8823   /* See if compatibility with older versions was explicitly requested. */
8824   if (fs->config)
8825     {
8826       if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
8827         format = 1;
8828       else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
8829         format = 2;
8830       else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
8831         format = 3;
8832       else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE))
8833         format = 4;
8834     }
8835   ffd->format = format;
8836
8837   /* Override the default linear layout if this is a new-enough format. */
8838   if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
8839     ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
8840
8841   /* Create the revision data directories. */
8842   if (ffd->max_files_per_dir)
8843     SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool));
8844   else
8845     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
8846                                                         pool),
8847                                         pool));
8848
8849   /* Create the revprops directory. */
8850   if (ffd->max_files_per_dir)
8851     SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool),
8852                                         pool));
8853   else
8854     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
8855                                                         PATH_REVPROPS_DIR,
8856                                                         pool),
8857                                         pool));
8858
8859   /* Create the transaction directory. */
8860   SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR,
8861                                                       pool),
8862                                       pool));
8863
8864   /* Create the protorevs directory. */
8865   if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
8866     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR,
8867                                                       pool),
8868                                         pool));
8869
8870   /* Create the 'current' file. */
8871   SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
8872                              (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
8873                               ? "0\n" : "0 1 1\n"),
8874                              pool));
8875   SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool));
8876   SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool));
8877
8878   SVN_ERR(write_revision_zero(fs));
8879
8880   /* Create the fsfs.conf file if supported.  Older server versions would
8881      simply ignore the file but that might result in a different behavior
8882      than with the later releases.  Also, hotcopy would ignore, i.e. not
8883      copy, a fsfs.conf with old formats. */
8884   if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
8885     SVN_ERR(write_config(fs, pool));
8886
8887   SVN_ERR(read_config(ffd, fs->path, pool));
8888
8889   /* Create the min unpacked rev file. */
8890   if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
8891     SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
8892
8893   /* Create the txn-current file if the repository supports
8894      the transaction sequence file. */
8895   if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
8896     {
8897       SVN_ERR(svn_io_file_create(path_txn_current(fs, pool),
8898                                  "0\n", pool));
8899       SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool),
8900                                  "", pool));
8901     }
8902
8903   /* This filesystem is ready.  Stamp it with a format number. */
8904   SVN_ERR(write_format(path_format(fs, pool),
8905                        ffd->format, ffd->max_files_per_dir, FALSE, pool));
8906
8907   ffd->youngest_rev_cache = 0;
8908   return SVN_NO_ERROR;
8909 }
8910
8911 /* Part of the recovery procedure.  Return the largest revision *REV in
8912    filesystem FS.  Use POOL for temporary allocation. */
8913 static svn_error_t *
8914 recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
8915 {
8916   /* Discovering the largest revision in the filesystem would be an
8917      expensive operation if we did a readdir() or searched linearly,
8918      so we'll do a form of binary search.  left is a revision that we
8919      know exists, right a revision that we know does not exist. */
8920   apr_pool_t *iterpool;
8921   svn_revnum_t left, right = 1;
8922
8923   iterpool = svn_pool_create(pool);
8924   /* Keep doubling right, until we find a revision that doesn't exist. */
8925   while (1)
8926     {
8927       svn_error_t *err;
8928       apr_file_t *file;
8929
8930       err = open_pack_or_rev_file(&file, fs, right, iterpool);
8931       svn_pool_clear(iterpool);
8932
8933       if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8934         {
8935           svn_error_clear(err);
8936           break;
8937         }
8938       else
8939         SVN_ERR(err);
8940
8941       right <<= 1;
8942     }
8943
8944   left = right >> 1;
8945
8946   /* We know that left exists and right doesn't.  Do a normal bsearch to find
8947      the last revision. */
8948   while (left + 1 < right)
8949     {
8950       svn_revnum_t probe = left + ((right - left) / 2);
8951       svn_error_t *err;
8952       apr_file_t *file;
8953
8954       err = open_pack_or_rev_file(&file, fs, probe, iterpool);
8955       svn_pool_clear(iterpool);
8956
8957       if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8958         {
8959           svn_error_clear(err);
8960           right = probe;
8961         }
8962       else
8963         {
8964           SVN_ERR(err);
8965           left = probe;
8966         }
8967     }
8968
8969   svn_pool_destroy(iterpool);
8970
8971   /* left is now the largest revision that exists. */
8972   *rev = left;
8973   return SVN_NO_ERROR;
8974 }
8975
8976 /* A baton for reading a fixed amount from an open file.  For
8977    recover_find_max_ids() below. */
8978 struct recover_read_from_file_baton
8979 {
8980   apr_file_t *file;
8981   apr_pool_t *pool;
8982   apr_off_t remaining;
8983 };
8984
8985 /* A stream read handler used by recover_find_max_ids() below.
8986    Read and return at most BATON->REMAINING bytes from the stream,
8987    returning nothing after that to indicate EOF. */
8988 static svn_error_t *
8989 read_handler_recover(void *baton, char *buffer, apr_size_t *len)
8990 {
8991   struct recover_read_from_file_baton *b = baton;
8992   svn_filesize_t bytes_to_read = *len;
8993
8994   if (b->remaining == 0)
8995     {
8996       /* Return a successful read of zero bytes to signal EOF. */
8997       *len = 0;
8998       return SVN_NO_ERROR;
8999     }
9000
9001   if (bytes_to_read > b->remaining)
9002     bytes_to_read = b->remaining;
9003   b->remaining -= bytes_to_read;
9004
9005   return svn_io_file_read_full2(b->file, buffer, (apr_size_t) bytes_to_read,
9006                                 len, NULL, b->pool);
9007 }
9008
9009 /* Part of the recovery procedure.  Read the directory noderev at offset
9010    OFFSET of file REV_FILE (the revision file of revision REV of
9011    filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
9012    and copy-id of that node, if greater than the current value stored
9013    in either.  Recurse into any child directories that were modified in
9014    this revision.
9015
9016    MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
9017
9018    Perform temporary allocation in POOL. */
9019 static svn_error_t *
9020 recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev,
9021                      apr_file_t *rev_file, apr_off_t offset,
9022                      char *max_node_id, char *max_copy_id,
9023                      apr_pool_t *pool)
9024 {
9025   apr_hash_t *headers;
9026   char *value;
9027   representation_t *data_rep;
9028   struct rep_args *ra;
9029   struct recover_read_from_file_baton baton;
9030   svn_stream_t *stream;
9031   apr_hash_t *entries;
9032   apr_hash_index_t *hi;
9033   apr_pool_t *iterpool;
9034
9035   SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9036   SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile2(rev_file, TRUE,
9037                                                                pool),
9038                             pool));
9039
9040   /* Check that this is a directory.  It should be. */
9041   value = svn_hash_gets(headers, HEADER_TYPE);
9042   if (value == NULL || strcmp(value, KIND_DIR) != 0)
9043     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9044                             _("Recovery encountered a non-directory node"));
9045
9046   /* Get the data location.  No data location indicates an empty directory. */
9047   value = svn_hash_gets(headers, HEADER_TEXT);
9048   if (!value)
9049     return SVN_NO_ERROR;
9050   SVN_ERR(read_rep_offsets(&data_rep, value, NULL, FALSE, pool));
9051
9052   /* If the directory's data representation wasn't changed in this revision,
9053      we've already scanned the directory's contents for noderevs, so we don't
9054      need to again.  This will occur if a property is changed on a directory
9055      without changing the directory's contents. */
9056   if (data_rep->revision != rev)
9057     return SVN_NO_ERROR;
9058
9059   /* We could use get_dir_contents(), but this is much cheaper.  It does
9060      rely on directory entries being stored as PLAIN reps, though. */
9061   offset = data_rep->offset;
9062   SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9063   SVN_ERR(read_rep_line(&ra, rev_file, pool));
9064   if (ra->is_delta)
9065     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9066                             _("Recovery encountered a deltified directory "
9067                               "representation"));
9068
9069   /* Now create a stream that's allowed to read only as much data as is
9070      stored in the representation. */
9071   baton.file = rev_file;
9072   baton.pool = pool;
9073   baton.remaining = data_rep->expanded_size;
9074   stream = svn_stream_create(&baton, pool);
9075   svn_stream_set_read(stream, read_handler_recover);
9076
9077   /* Now read the entries from that stream. */
9078   entries = apr_hash_make(pool);
9079   SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
9080   SVN_ERR(svn_stream_close(stream));
9081
9082   /* Now check each of the entries in our directory to find new node and
9083      copy ids, and recurse into new subdirectories. */
9084   iterpool = svn_pool_create(pool);
9085   for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
9086     {
9087       char *str_val;
9088       char *str;
9089       svn_node_kind_t kind;
9090       svn_fs_id_t *id;
9091       const char *node_id, *copy_id;
9092       apr_off_t child_dir_offset;
9093       const svn_string_t *path = svn__apr_hash_index_val(hi);
9094
9095       svn_pool_clear(iterpool);
9096
9097       str_val = apr_pstrdup(iterpool, path->data);
9098
9099       str = svn_cstring_tokenize(" ", &str_val);
9100       if (str == NULL)
9101         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9102                                 _("Directory entry corrupt"));
9103
9104       if (strcmp(str, KIND_FILE) == 0)
9105         kind = svn_node_file;
9106       else if (strcmp(str, KIND_DIR) == 0)
9107         kind = svn_node_dir;
9108       else
9109         {
9110           return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9111                                   _("Directory entry corrupt"));
9112         }
9113
9114       str = svn_cstring_tokenize(" ", &str_val);
9115       if (str == NULL)
9116         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9117                                 _("Directory entry corrupt"));
9118
9119       id = svn_fs_fs__id_parse(str, strlen(str), iterpool);
9120
9121       if (svn_fs_fs__id_rev(id) != rev)
9122         {
9123           /* If the node wasn't modified in this revision, we've already
9124              checked the node and copy id. */
9125           continue;
9126         }
9127
9128       node_id = svn_fs_fs__id_node_id(id);
9129       copy_id = svn_fs_fs__id_copy_id(id);
9130
9131       if (svn_fs_fs__key_compare(node_id, max_node_id) > 0)
9132         {
9133           SVN_ERR_ASSERT(strlen(node_id) < MAX_KEY_SIZE);
9134           apr_cpystrn(max_node_id, node_id, MAX_KEY_SIZE);
9135         }
9136       if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0)
9137         {
9138           SVN_ERR_ASSERT(strlen(copy_id) < MAX_KEY_SIZE);
9139           apr_cpystrn(max_copy_id, copy_id, MAX_KEY_SIZE);
9140         }
9141
9142       if (kind == svn_node_file)
9143         continue;
9144
9145       child_dir_offset = svn_fs_fs__id_offset(id);
9146       SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
9147                                    max_node_id, max_copy_id, iterpool));
9148     }
9149   svn_pool_destroy(iterpool);
9150
9151   return SVN_NO_ERROR;
9152 }
9153
9154 /* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
9155  * Use POOL for temporary allocations.
9156  * Set *MISSING, if the reason is a missing manifest or pack file.
9157  */
9158 static svn_boolean_t
9159 packed_revprop_available(svn_boolean_t *missing,
9160                          svn_fs_t *fs,
9161                          svn_revnum_t revision,
9162                          apr_pool_t *pool)
9163 {
9164   fs_fs_data_t *ffd = fs->fsap_data;
9165   svn_stringbuf_t *content = NULL;
9166
9167   /* try to read the manifest file */
9168   const char *folder = path_revprops_pack_shard(fs, revision, pool);
9169   const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool);
9170
9171   svn_error_t *err = try_stringbuf_from_file(&content,
9172                                              missing,
9173                                              manifest_path,
9174                                              FALSE,
9175                                              pool);
9176
9177   /* if the manifest cannot be read, consider the pack files inaccessible
9178    * even if the file itself exists. */
9179   if (err)
9180     {
9181       svn_error_clear(err);
9182       return FALSE;
9183     }
9184
9185   if (*missing)
9186     return FALSE;
9187
9188   /* parse manifest content until we find the entry for REVISION.
9189    * Revision 0 is never packed. */
9190   revision = revision < ffd->max_files_per_dir
9191            ? revision - 1
9192            : revision % ffd->max_files_per_dir;
9193   while (content->data)
9194     {
9195       char *next = strchr(content->data, '\n');
9196       if (next)
9197         {
9198           *next = 0;
9199           ++next;
9200         }
9201
9202       if (revision-- == 0)
9203         {
9204           /* the respective pack file must exist (and be a file) */
9205           svn_node_kind_t kind;
9206           err = svn_io_check_path(svn_dirent_join(folder, content->data,
9207                                                   pool),
9208                                   &kind, pool);
9209           if (err)
9210             {
9211               svn_error_clear(err);
9212               return FALSE;
9213             }
9214
9215           *missing = kind == svn_node_none;
9216           return kind == svn_node_file;
9217         }
9218
9219       content->data = next;
9220     }
9221
9222   return FALSE;
9223 }
9224
9225 /* Baton used for recover_body below. */
9226 struct recover_baton {
9227   svn_fs_t *fs;
9228   svn_cancel_func_t cancel_func;
9229   void *cancel_baton;
9230 };
9231
9232 /* The work-horse for svn_fs_fs__recover, called with the FS
9233    write lock.  This implements the svn_fs_fs__with_write_lock()
9234    'body' callback type.  BATON is a 'struct recover_baton *'. */
9235 static svn_error_t *
9236 recover_body(void *baton, apr_pool_t *pool)
9237 {
9238   struct recover_baton *b = baton;
9239   svn_fs_t *fs = b->fs;
9240   fs_fs_data_t *ffd = fs->fsap_data;
9241   svn_revnum_t max_rev;
9242   char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE];
9243   char *next_node_id = NULL, *next_copy_id = NULL;
9244   svn_revnum_t youngest_rev;
9245   svn_node_kind_t youngest_revprops_kind;
9246
9247   /* Lose potentially corrupted data in temp files */
9248   SVN_ERR(cleanup_revprop_namespace(fs));
9249
9250   /* We need to know the largest revision in the filesystem. */
9251   SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
9252
9253   /* Get the expected youngest revision */
9254   SVN_ERR(get_youngest(&youngest_rev, fs->path, pool));
9255
9256   /* Policy note:
9257
9258      Since the revprops file is written after the revs file, the true
9259      maximum available revision is the youngest one for which both are
9260      present.  That's probably the same as the max_rev we just found,
9261      but if it's not, we could, in theory, repeatedly decrement
9262      max_rev until we find a revision that has both a revs and
9263      revprops file, then write db/current with that.
9264
9265      But we choose not to.  If a repository is so corrupt that it's
9266      missing at least one revprops file, we shouldn't assume that the
9267      youngest revision for which both the revs and revprops files are
9268      present is healthy.  In other words, we're willing to recover
9269      from a missing or out-of-date db/current file, because db/current
9270      is truly redundant -- it's basically a cache so we don't have to
9271      find max_rev each time, albeit a cache with unusual semantics,
9272      since it also officially defines when a revision goes live.  But
9273      if we're missing more than the cache, it's time to back out and
9274      let the admin reconstruct things by hand: correctness at that
9275      point may depend on external things like checking a commit email
9276      list, looking in particular working copies, etc.
9277
9278      This policy matches well with a typical naive backup scenario.
9279      Say you're rsyncing your FSFS repository nightly to the same
9280      location.  Once revs and revprops are written, you've got the
9281      maximum rev; if the backup should bomb before db/current is
9282      written, then db/current could stay arbitrarily out-of-date, but
9283      we can still recover.  It's a small window, but we might as well
9284      do what we can. */
9285
9286   /* Even if db/current were missing, it would be created with 0 by
9287      get_youngest(), so this conditional remains valid. */
9288   if (youngest_rev > max_rev)
9289     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9290                              _("Expected current rev to be <= %ld "
9291                                "but found %ld"), max_rev, youngest_rev);
9292
9293   /* We only need to search for maximum IDs for old FS formats which
9294      se global ID counters. */
9295   if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
9296     {
9297       /* Next we need to find the maximum node id and copy id in use across the
9298          filesystem.  Unfortunately, the only way we can get this information
9299          is to scan all the noderevs of all the revisions and keep track as
9300          we go along. */
9301       svn_revnum_t rev;
9302       apr_pool_t *iterpool = svn_pool_create(pool);
9303       char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0";
9304       apr_size_t len;
9305
9306       for (rev = 0; rev <= max_rev; rev++)
9307         {
9308           apr_file_t *rev_file;
9309           apr_off_t root_offset;
9310
9311           svn_pool_clear(iterpool);
9312
9313           if (b->cancel_func)
9314             SVN_ERR(b->cancel_func(b->cancel_baton));
9315
9316           SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, iterpool));
9317           SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, fs, rev,
9318                                           iterpool));
9319           SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
9320                                        max_node_id, max_copy_id, iterpool));
9321           SVN_ERR(svn_io_file_close(rev_file, iterpool));
9322         }
9323       svn_pool_destroy(iterpool);
9324
9325       /* Now that we finally have the maximum revision, node-id and copy-id, we
9326          can bump the two ids to get the next of each. */
9327       len = strlen(max_node_id);
9328       svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf);
9329       next_node_id = next_node_id_buf;
9330       len = strlen(max_copy_id);
9331       svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf);
9332       next_copy_id = next_copy_id_buf;
9333     }
9334
9335   /* Before setting current, verify that there is a revprops file
9336      for the youngest revision.  (Issue #2992) */
9337   SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool),
9338                             &youngest_revprops_kind, pool));
9339   if (youngest_revprops_kind == svn_node_none)
9340     {
9341       svn_boolean_t missing = TRUE;
9342       if (!packed_revprop_available(&missing, fs, max_rev, pool))
9343         {
9344           if (missing)
9345             {
9346               return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9347                                       _("Revision %ld has a revs file but no "
9348                                         "revprops file"),
9349                                       max_rev);
9350             }
9351           else
9352             {
9353               return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9354                                       _("Revision %ld has a revs file but the "
9355                                         "revprops file is inaccessible"),
9356                                       max_rev);
9357             }
9358           }
9359     }
9360   else if (youngest_revprops_kind != svn_node_file)
9361     {
9362       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9363                                _("Revision %ld has a non-file where its "
9364                                  "revprops file should be"),
9365                                max_rev);
9366     }
9367
9368   /* Prune younger-than-(newfound-youngest) revisions from the rep
9369      cache if sharing is enabled taking care not to create the cache
9370      if it does not exist. */
9371   if (ffd->rep_sharing_allowed)
9372     {
9373       svn_boolean_t rep_cache_exists;
9374
9375       SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool));
9376       if (rep_cache_exists)
9377         SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool));
9378     }
9379
9380   /* Now store the discovered youngest revision, and the next IDs if
9381      relevant, in a new 'current' file. */
9382   return write_current(fs, max_rev, next_node_id, next_copy_id, pool);
9383 }
9384
9385 /* This implements the fs_library_vtable_t.recover() API. */
9386 svn_error_t *
9387 svn_fs_fs__recover(svn_fs_t *fs,
9388                    svn_cancel_func_t cancel_func, void *cancel_baton,
9389                    apr_pool_t *pool)
9390 {
9391   struct recover_baton b;
9392
9393   /* We have no way to take out an exclusive lock in FSFS, so we're
9394      restricted as to the types of recovery we can do.  Luckily,
9395      we just want to recreate the 'current' file, and we can do that just
9396      by blocking other writers. */
9397   b.fs = fs;
9398   b.cancel_func = cancel_func;
9399   b.cancel_baton = cancel_baton;
9400   return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool);
9401 }
9402
9403 svn_error_t *
9404 svn_fs_fs__set_uuid(svn_fs_t *fs,
9405                     const char *uuid,
9406                     apr_pool_t *pool)
9407 {
9408   char *my_uuid;
9409   apr_size_t my_uuid_len;
9410   const char *tmp_path;
9411   const char *uuid_path = path_uuid(fs, pool);
9412
9413   if (! uuid)
9414     uuid = svn_uuid_generate(pool);
9415
9416   /* Make sure we have a copy in FS->POOL, and append a newline. */
9417   my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL);
9418   my_uuid_len = strlen(my_uuid);
9419
9420   SVN_ERR(svn_io_write_unique(&tmp_path,
9421                               svn_dirent_dirname(uuid_path, pool),
9422                               my_uuid, my_uuid_len,
9423                               svn_io_file_del_none, pool));
9424
9425   /* We use the permissions of the 'current' file, because the 'uuid'
9426      file does not exist during repository creation. */
9427   SVN_ERR(move_into_place(tmp_path, uuid_path,
9428                           svn_fs_fs__path_current(fs, pool), pool));
9429
9430   /* Remove the newline we added, and stash the UUID. */
9431   my_uuid[my_uuid_len - 1] = '\0';
9432   fs->uuid = my_uuid;
9433
9434   return SVN_NO_ERROR;
9435 }
9436
9437 /** Node origin lazy cache. */
9438
9439 /* If directory PATH does not exist, create it and give it the same
9440    permissions as FS_path.*/
9441 svn_error_t *
9442 svn_fs_fs__ensure_dir_exists(const char *path,
9443                              const char *fs_path,
9444                              apr_pool_t *pool)
9445 {
9446   svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
9447   if (err && APR_STATUS_IS_EEXIST(err->apr_err))
9448     {
9449       svn_error_clear(err);
9450       return SVN_NO_ERROR;
9451     }
9452   SVN_ERR(err);
9453
9454   /* We successfully created a new directory.  Dup the permissions
9455      from FS->path. */
9456   return svn_io_copy_perms(fs_path, path, pool);
9457 }
9458
9459 /* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
9460    'svn_string_t *' node revision IDs.  Use POOL for allocations. */
9461 static svn_error_t *
9462 get_node_origins_from_file(svn_fs_t *fs,
9463                            apr_hash_t **node_origins,
9464                            const char *node_origins_file,
9465                            apr_pool_t *pool)
9466 {
9467   apr_file_t *fd;
9468   svn_error_t *err;
9469   svn_stream_t *stream;
9470
9471   *node_origins = NULL;
9472   err = svn_io_file_open(&fd, node_origins_file,
9473                          APR_READ, APR_OS_DEFAULT, pool);
9474   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
9475     {
9476       svn_error_clear(err);
9477       return SVN_NO_ERROR;
9478     }
9479   SVN_ERR(err);
9480
9481   stream = svn_stream_from_aprfile2(fd, FALSE, pool);
9482   *node_origins = apr_hash_make(pool);
9483   SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool));
9484   return svn_stream_close(stream);
9485 }
9486
9487 svn_error_t *
9488 svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
9489                            svn_fs_t *fs,
9490                            const char *node_id,
9491                            apr_pool_t *pool)
9492 {
9493   apr_hash_t *node_origins;
9494
9495   *origin_id = NULL;
9496   SVN_ERR(get_node_origins_from_file(fs, &node_origins,
9497                                      path_node_origin(fs, node_id, pool),
9498                                      pool));
9499   if (node_origins)
9500     {
9501       svn_string_t *origin_id_str =
9502         svn_hash_gets(node_origins, node_id);
9503       if (origin_id_str)
9504         *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
9505                                          origin_id_str->len, pool);
9506     }
9507   return SVN_NO_ERROR;
9508 }
9509
9510
9511 /* Helper for svn_fs_fs__set_node_origin.  Takes a NODE_ID/NODE_REV_ID
9512    pair and adds it to the NODE_ORIGINS_PATH file.  */
9513 static svn_error_t *
9514 set_node_origins_for_file(svn_fs_t *fs,
9515                           const char *node_origins_path,
9516                           const char *node_id,
9517                           svn_string_t *node_rev_id,
9518                           apr_pool_t *pool)
9519 {
9520   const char *path_tmp;
9521   svn_stream_t *stream;
9522   apr_hash_t *origins_hash;
9523   svn_string_t *old_node_rev_id;
9524
9525   SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
9526                                                        PATH_NODE_ORIGINS_DIR,
9527                                                        pool),
9528                                        fs->path, pool));
9529
9530   /* Read the previously existing origins (if any), and merge our
9531      update with it. */
9532   SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
9533                                      node_origins_path, pool));
9534   if (! origins_hash)
9535     origins_hash = apr_hash_make(pool);
9536
9537   old_node_rev_id = svn_hash_gets(origins_hash, node_id);
9538
9539   if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
9540     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9541                              _("Node origin for '%s' exists with a different "
9542                                "value (%s) than what we were about to store "
9543                                "(%s)"),
9544                              node_id, old_node_rev_id->data, node_rev_id->data);
9545
9546   svn_hash_sets(origins_hash, node_id, node_rev_id);
9547
9548   /* Sure, there's a race condition here.  Two processes could be
9549      trying to add different cache elements to the same file at the
9550      same time, and the entries added by the first one to write will
9551      be lost.  But this is just a cache of reconstructible data, so
9552      we'll accept this problem in return for not having to deal with
9553      locking overhead. */
9554
9555   /* Create a temporary file, write out our hash, and close the file. */
9556   SVN_ERR(svn_stream_open_unique(&stream, &path_tmp,
9557                                  svn_dirent_dirname(node_origins_path, pool),
9558                                  svn_io_file_del_none, pool, pool));
9559   SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
9560   SVN_ERR(svn_stream_close(stream));
9561
9562   /* Rename the temp file as the real destination */
9563   return svn_io_file_rename(path_tmp, node_origins_path, pool);
9564 }
9565
9566
9567 svn_error_t *
9568 svn_fs_fs__set_node_origin(svn_fs_t *fs,
9569                            const char *node_id,
9570                            const svn_fs_id_t *node_rev_id,
9571                            apr_pool_t *pool)
9572 {
9573   svn_error_t *err;
9574   const char *filename = path_node_origin(fs, node_id, pool);
9575
9576   err = set_node_origins_for_file(fs, filename,
9577                                   node_id,
9578                                   svn_fs_fs__id_unparse(node_rev_id, pool),
9579                                   pool);
9580   if (err && APR_STATUS_IS_EACCES(err->apr_err))
9581     {
9582       /* It's just a cache; stop trying if I can't write. */
9583       svn_error_clear(err);
9584       err = NULL;
9585     }
9586   return svn_error_trace(err);
9587 }
9588
9589
9590 svn_error_t *
9591 svn_fs_fs__list_transactions(apr_array_header_t **names_p,
9592                              svn_fs_t *fs,
9593                              apr_pool_t *pool)
9594 {
9595   const char *txn_dir;
9596   apr_hash_t *dirents;
9597   apr_hash_index_t *hi;
9598   apr_array_header_t *names;
9599   apr_size_t ext_len = strlen(PATH_EXT_TXN);
9600
9601   names = apr_array_make(pool, 1, sizeof(const char *));
9602
9603   /* Get the transactions directory. */
9604   txn_dir = svn_dirent_join(fs->path, PATH_TXNS_DIR, pool);
9605
9606   /* Now find a listing of this directory. */
9607   SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
9608
9609   /* Loop through all the entries and return anything that ends with '.txn'. */
9610   for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
9611     {
9612       const char *name = svn__apr_hash_index_key(hi);
9613       apr_ssize_t klen = svn__apr_hash_index_klen(hi);
9614       const char *id;
9615
9616       /* The name must end with ".txn" to be considered a transaction. */
9617       if ((apr_size_t) klen <= ext_len
9618           || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
9619         continue;
9620
9621       /* Truncate the ".txn" extension and store the ID. */
9622       id = apr_pstrndup(pool, name, strlen(name) - ext_len);
9623       APR_ARRAY_PUSH(names, const char *) = id;
9624     }
9625
9626   *names_p = names;
9627
9628   return SVN_NO_ERROR;
9629 }
9630
9631 svn_error_t *
9632 svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
9633                     svn_fs_t *fs,
9634                     const char *name,
9635                     apr_pool_t *pool)
9636 {
9637   svn_fs_txn_t *txn;
9638   svn_node_kind_t kind;
9639   transaction_t *local_txn;
9640
9641   /* First check to see if the directory exists. */
9642   SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool));
9643
9644   /* Did we find it? */
9645   if (kind != svn_node_dir)
9646     return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
9647                              _("No such transaction '%s'"),
9648                              name);
9649
9650   txn = apr_pcalloc(pool, sizeof(*txn));
9651
9652   /* Read in the root node of this transaction. */
9653   txn->id = apr_pstrdup(pool, name);
9654   txn->fs = fs;
9655
9656   SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool));
9657
9658   txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
9659
9660   txn->vtable = &txn_vtable;
9661   *txn_p = txn;
9662
9663   return SVN_NO_ERROR;
9664 }
9665
9666 svn_error_t *
9667 svn_fs_fs__txn_proplist(apr_hash_t **table_p,
9668                         svn_fs_txn_t *txn,
9669                         apr_pool_t *pool)
9670 {
9671   apr_hash_t *proplist = apr_hash_make(pool);
9672   SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool));
9673   *table_p = proplist;
9674
9675   return SVN_NO_ERROR;
9676 }
9677
9678 svn_error_t *
9679 svn_fs_fs__delete_node_revision(svn_fs_t *fs,
9680                                 const svn_fs_id_t *id,
9681                                 apr_pool_t *pool)
9682 {
9683   node_revision_t *noderev;
9684
9685   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
9686
9687   /* Delete any mutable property representation. */
9688   if (noderev->prop_rep && noderev->prop_rep->txn_id)
9689     SVN_ERR(svn_io_remove_file2(path_txn_node_props(fs, id, pool), FALSE,
9690                                 pool));
9691
9692   /* Delete any mutable data representation. */
9693   if (noderev->data_rep && noderev->data_rep->txn_id
9694       && noderev->kind == svn_node_dir)
9695     {
9696       fs_fs_data_t *ffd = fs->fsap_data;
9697       SVN_ERR(svn_io_remove_file2(path_txn_node_children(fs, id, pool), FALSE,
9698                                   pool));
9699
9700       /* remove the corresponding entry from the cache, if such exists */
9701       if (ffd->txn_dir_cache)
9702         {
9703           const char *key = svn_fs_fs__id_unparse(id, pool)->data;
9704           SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
9705         }
9706     }
9707
9708   return svn_io_remove_file2(path_txn_node_rev(fs, id, pool), FALSE, pool);
9709 }
9710
9711
9712 \f
9713 /*** Revisions ***/
9714
9715 svn_error_t *
9716 svn_fs_fs__revision_prop(svn_string_t **value_p,
9717                          svn_fs_t *fs,
9718                          svn_revnum_t rev,
9719                          const char *propname,
9720                          apr_pool_t *pool)
9721 {
9722   apr_hash_t *table;
9723
9724   SVN_ERR(svn_fs__check_fs(fs, TRUE));
9725   SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool));
9726
9727   *value_p = svn_hash_gets(table, propname);
9728
9729   return SVN_NO_ERROR;
9730 }
9731
9732
9733 /* Baton used for change_rev_prop_body below. */
9734 struct change_rev_prop_baton {
9735   svn_fs_t *fs;
9736   svn_revnum_t rev;
9737   const char *name;
9738   const svn_string_t *const *old_value_p;
9739   const svn_string_t *value;
9740 };
9741
9742 /* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
9743    write lock.  This implements the svn_fs_fs__with_write_lock()
9744    'body' callback type.  BATON is a 'struct change_rev_prop_baton *'. */
9745 static svn_error_t *
9746 change_rev_prop_body(void *baton, apr_pool_t *pool)
9747 {
9748   struct change_rev_prop_baton *cb = baton;
9749   apr_hash_t *table;
9750
9751   SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool));
9752
9753   if (cb->old_value_p)
9754     {
9755       const svn_string_t *wanted_value = *cb->old_value_p;
9756       const svn_string_t *present_value = svn_hash_gets(table, cb->name);
9757       if ((!wanted_value != !present_value)
9758           || (wanted_value && present_value
9759               && !svn_string_compare(wanted_value, present_value)))
9760         {
9761           /* What we expected isn't what we found. */
9762           return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
9763                                    _("revprop '%s' has unexpected value in "
9764                                      "filesystem"),
9765                                    cb->name);
9766         }
9767       /* Fall through. */
9768     }
9769   svn_hash_sets(table, cb->name, cb->value);
9770
9771   return set_revision_proplist(cb->fs, cb->rev, table, pool);
9772 }
9773
9774 svn_error_t *
9775 svn_fs_fs__change_rev_prop(svn_fs_t *fs,
9776                            svn_revnum_t rev,
9777                            const char *name,
9778                            const svn_string_t *const *old_value_p,
9779                            const svn_string_t *value,
9780                            apr_pool_t *pool)
9781 {
9782   struct change_rev_prop_baton cb;
9783
9784   SVN_ERR(svn_fs__check_fs(fs, TRUE));
9785
9786   cb.fs = fs;
9787   cb.rev = rev;
9788   cb.name = name;
9789   cb.old_value_p = old_value_p;
9790   cb.value = value;
9791
9792   return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
9793 }
9794
9795
9796 \f
9797 /*** Transactions ***/
9798
9799 svn_error_t *
9800 svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
9801                        const svn_fs_id_t **base_root_id_p,
9802                        svn_fs_t *fs,
9803                        const char *txn_name,
9804                        apr_pool_t *pool)
9805 {
9806   transaction_t *txn;
9807   SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
9808   *root_id_p = txn->root_id;
9809   *base_root_id_p = txn->base_id;
9810   return SVN_NO_ERROR;
9811 }
9812
9813 \f
9814 /* Generic transaction operations.  */
9815
9816 svn_error_t *
9817 svn_fs_fs__txn_prop(svn_string_t **value_p,
9818                     svn_fs_txn_t *txn,
9819                     const char *propname,
9820                     apr_pool_t *pool)
9821 {
9822   apr_hash_t *table;
9823   svn_fs_t *fs = txn->fs;
9824
9825   SVN_ERR(svn_fs__check_fs(fs, TRUE));
9826   SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
9827
9828   *value_p = svn_hash_gets(table, propname);
9829
9830   return SVN_NO_ERROR;
9831 }
9832
9833 svn_error_t *
9834 svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
9835                      svn_fs_t *fs,
9836                      svn_revnum_t rev,
9837                      apr_uint32_t flags,
9838                      apr_pool_t *pool)
9839 {
9840   svn_string_t date;
9841   svn_prop_t prop;
9842   apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
9843
9844   SVN_ERR(svn_fs__check_fs(fs, TRUE));
9845
9846   SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
9847
9848   /* Put a datestamp on the newly created txn, so we always know
9849      exactly how old it is.  (This will help sysadmins identify
9850      long-abandoned txns that may need to be manually removed.)  When
9851      a txn is promoted to a revision, this property will be
9852      automatically overwritten with a revision datestamp. */
9853   date.data = svn_time_to_cstring(apr_time_now(), pool);
9854   date.len = strlen(date.data);
9855
9856   prop.name = SVN_PROP_REVISION_DATE;
9857   prop.value = &date;
9858   APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9859
9860   /* Set temporary txn props that represent the requested 'flags'
9861      behaviors. */
9862   if (flags & SVN_FS_TXN_CHECK_OOD)
9863     {
9864       prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
9865       prop.value = svn_string_create("true", pool);
9866       APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9867     }
9868
9869   if (flags & SVN_FS_TXN_CHECK_LOCKS)
9870     {
9871       prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
9872       prop.value = svn_string_create("true", pool);
9873       APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9874     }
9875
9876   return svn_fs_fs__change_txn_props(*txn_p, props, pool);
9877 }
9878
9879 \f
9880 /****** Packing FSFS shards *********/
9881
9882 /* Write a file FILENAME in directory FS_PATH, containing a single line
9883  * with the number REVNUM in ASCII decimal.  Move the file into place
9884  * atomically, overwriting any existing file.
9885  *
9886  * Similar to write_current(). */
9887 static svn_error_t *
9888 write_revnum_file(const char *fs_path,
9889                   const char *filename,
9890                   svn_revnum_t revnum,
9891                   apr_pool_t *scratch_pool)
9892 {
9893   const char *final_path, *tmp_path;
9894   svn_stream_t *tmp_stream;
9895
9896   final_path = svn_dirent_join(fs_path, filename, scratch_pool);
9897   SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path,
9898                                    svn_io_file_del_none,
9899                                    scratch_pool, scratch_pool));
9900   SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum));
9901   SVN_ERR(svn_stream_close(tmp_stream));
9902   SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool));
9903   return SVN_NO_ERROR;
9904 }
9905
9906 /* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions
9907  * from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations.
9908  * CANCEL_FUNC and CANCEL_BATON are what you think they are.
9909  *
9910  * If for some reason we detect a partial packing already performed, we
9911  * remove the pack file and start again.
9912  */
9913 static svn_error_t *
9914 pack_rev_shard(const char *pack_file_dir,
9915                const char *shard_path,
9916                apr_int64_t shard,
9917                int max_files_per_dir,
9918                svn_cancel_func_t cancel_func,
9919                void *cancel_baton,
9920                apr_pool_t *pool)
9921 {
9922   const char *pack_file_path, *manifest_file_path;
9923   svn_stream_t *pack_stream, *manifest_stream;
9924   svn_revnum_t start_rev, end_rev, rev;
9925   apr_off_t next_offset;
9926   apr_pool_t *iterpool;
9927
9928   /* Some useful paths. */
9929   pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
9930   manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool);
9931
9932   /* Remove any existing pack file for this shard, since it is incomplete. */
9933   SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
9934                              pool));
9935
9936   /* Create the new directory and pack and manifest files. */
9937   SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool));
9938   SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool,
9939                                     pool));
9940   SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
9941                                    pool, pool));
9942
9943   start_rev = (svn_revnum_t) (shard * max_files_per_dir);
9944   end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
9945   next_offset = 0;
9946   iterpool = svn_pool_create(pool);
9947
9948   /* Iterate over the revisions in this shard, squashing them together. */
9949   for (rev = start_rev; rev <= end_rev; rev++)
9950     {
9951       svn_stream_t *rev_stream;
9952       apr_finfo_t finfo;
9953       const char *path;
9954
9955       svn_pool_clear(iterpool);
9956
9957       /* Get the size of the file. */
9958       path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
9959                              iterpool);
9960       SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
9961
9962       /* Update the manifest. */
9963       SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT
9964                                 "\n", next_offset));
9965       next_offset += finfo.size;
9966
9967       /* Copy all the bits from the rev file to the end of the pack file. */
9968       SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool));
9969       SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream,
9970                                                              iterpool),
9971                           cancel_func, cancel_baton, iterpool));
9972     }
9973
9974   SVN_ERR(svn_stream_close(manifest_stream));
9975   SVN_ERR(svn_stream_close(pack_stream));
9976   SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
9977   SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool));
9978   SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
9979
9980   svn_pool_destroy(iterpool);
9981
9982   return SVN_NO_ERROR;
9983 }
9984
9985 /* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH
9986  * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR.
9987  *
9988  * The file sizes have already been determined and written to SIZES.
9989  * Please note that this function will be executed while the filesystem
9990  * has been locked and that revprops files will therefore not be modified
9991  * while the pack is in progress.
9992  *
9993  * COMPRESSION_LEVEL defines how well the resulting pack file shall be
9994  * compressed or whether is shall be compressed at all.  TOTAL_SIZE is
9995  * a hint on which initial buffer size we should use to hold the pack file
9996  * content.
9997  *
9998  * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations
9999  * are done in SCRATCH_POOL.
10000  */
10001 static svn_error_t *
10002 copy_revprops(const char *pack_file_dir,
10003               const char *pack_filename,
10004               const char *shard_path,
10005               svn_revnum_t start_rev,
10006               svn_revnum_t end_rev,
10007               apr_array_header_t *sizes,
10008               apr_size_t total_size,
10009               int compression_level,
10010               svn_cancel_func_t cancel_func,
10011               void *cancel_baton,
10012               apr_pool_t *scratch_pool)
10013 {
10014   svn_stream_t *pack_stream;
10015   apr_file_t *pack_file;
10016   svn_revnum_t rev;
10017   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10018   svn_stream_t *stream;
10019
10020   /* create empty data buffer and a write stream on top of it */
10021   svn_stringbuf_t *uncompressed
10022     = svn_stringbuf_create_ensure(total_size, scratch_pool);
10023   svn_stringbuf_t *compressed
10024     = svn_stringbuf_create_empty(scratch_pool);
10025   pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
10026
10027   /* write the pack file header */
10028   SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
10029                                     sizes->nelts, iterpool));
10030
10031   /* Some useful paths. */
10032   SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
10033                                                        pack_filename,
10034                                                        scratch_pool),
10035                            APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
10036                            scratch_pool));
10037
10038   /* Iterate over the revisions in this shard, squashing them together. */
10039   for (rev = start_rev; rev <= end_rev; rev++)
10040     {
10041       const char *path;
10042
10043       svn_pool_clear(iterpool);
10044
10045       /* Construct the file name. */
10046       path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10047                              iterpool);
10048
10049       /* Copy all the bits from the non-packed revprop file to the end of
10050        * the pack file. */
10051       SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
10052       SVN_ERR(svn_stream_copy3(stream, pack_stream,
10053                                cancel_func, cancel_baton, iterpool));
10054     }
10055
10056   /* flush stream buffers to content buffer */
10057   SVN_ERR(svn_stream_close(pack_stream));
10058
10059   /* compress the content (or just store it for COMPRESSION_LEVEL 0) */
10060   SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
10061                         compressed, compression_level));
10062
10063   /* write the pack file content to disk */
10064   stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool);
10065   SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len));
10066   SVN_ERR(svn_stream_close(stream));
10067
10068   svn_pool_destroy(iterpool);
10069
10070   return SVN_NO_ERROR;
10071 }
10072
10073 /* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR
10074  * revprop files in it, create a packed shared at PACK_FILE_DIR.
10075  *
10076  * COMPRESSION_LEVEL defines how well the resulting pack file shall be
10077  * compressed or whether is shall be compressed at all.  Individual pack
10078  * file containing more than one revision will be limited to a size of
10079  * MAX_PACK_SIZE bytes before compression.
10080  *
10081  * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10082  * allocations are done in SCRATCH_POOL.
10083  */
10084 static svn_error_t *
10085 pack_revprops_shard(const char *pack_file_dir,
10086                     const char *shard_path,
10087                     apr_int64_t shard,
10088                     int max_files_per_dir,
10089                     apr_off_t max_pack_size,
10090                     int compression_level,
10091                     svn_cancel_func_t cancel_func,
10092                     void *cancel_baton,
10093                     apr_pool_t *scratch_pool)
10094 {
10095   const char *manifest_file_path, *pack_filename = NULL;
10096   svn_stream_t *manifest_stream;
10097   svn_revnum_t start_rev, end_rev, rev;
10098   apr_off_t total_size;
10099   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10100   apr_array_header_t *sizes;
10101
10102   /* Some useful paths. */
10103   manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
10104                                        scratch_pool);
10105
10106   /* Remove any existing pack file for this shard, since it is incomplete. */
10107   SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
10108                              scratch_pool));
10109
10110   /* Create the new directory and manifest file stream. */
10111   SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
10112   SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
10113                                    scratch_pool, scratch_pool));
10114
10115   /* revisions to handle. Special case: revision 0 */
10116   start_rev = (svn_revnum_t) (shard * max_files_per_dir);
10117   end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
10118   if (start_rev == 0)
10119     ++start_rev;
10120
10121   /* initialize the revprop size info */
10122   sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
10123   total_size = 2 * SVN_INT64_BUFFER_SIZE;
10124
10125   /* Iterate over the revisions in this shard, determine their size and
10126    * squashing them together into pack files. */
10127   for (rev = start_rev; rev <= end_rev; rev++)
10128     {
10129       apr_finfo_t finfo;
10130       const char *path;
10131
10132       svn_pool_clear(iterpool);
10133
10134       /* Get the size of the file. */
10135       path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10136                              iterpool);
10137       SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
10138
10139       /* if we already have started a pack file and this revprop cannot be
10140        * appended to it, write the previous pack file. */
10141       if (sizes->nelts != 0 &&
10142           total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
10143         {
10144           SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10145                                 start_rev, rev-1, sizes, (apr_size_t)total_size,
10146                                 compression_level, cancel_func, cancel_baton,
10147                                 iterpool));
10148
10149           /* next pack file starts empty again */
10150           apr_array_clear(sizes);
10151           total_size = 2 * SVN_INT64_BUFFER_SIZE;
10152           start_rev = rev;
10153         }
10154
10155       /* Update the manifest. Allocate a file name for the current pack
10156        * file if it is a new one */
10157       if (sizes->nelts == 0)
10158         pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
10159
10160       SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
10161                                 pack_filename));
10162
10163       /* add to list of files to put into the current pack file */
10164       APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
10165       total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
10166     }
10167
10168   /* write the last pack file */
10169   if (sizes->nelts != 0)
10170     SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10171                           start_rev, rev-1, sizes, (apr_size_t)total_size,
10172                           compression_level, cancel_func, cancel_baton,
10173                           iterpool));
10174
10175   /* flush the manifest file and update permissions */
10176   SVN_ERR(svn_stream_close(manifest_stream));
10177   SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
10178
10179   svn_pool_destroy(iterpool);
10180
10181   return SVN_NO_ERROR;
10182 }
10183
10184 /* Delete the non-packed revprop SHARD at SHARD_PATH with exactly
10185  * MAX_FILES_PER_DIR revprop files in it.  If this is shard 0, keep the
10186  * revprop file for revision 0.
10187  *
10188  * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10189  * allocations are done in SCRATCH_POOL.
10190  */
10191 static svn_error_t *
10192 delete_revprops_shard(const char *shard_path,
10193                       apr_int64_t shard,
10194                       int max_files_per_dir,
10195                       svn_cancel_func_t cancel_func,
10196                       void *cancel_baton,
10197                       apr_pool_t *scratch_pool)
10198 {
10199   if (shard == 0)
10200     {
10201       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10202       int i;
10203
10204       /* delete all files except the one for revision 0 */
10205       for (i = 1; i < max_files_per_dir; ++i)
10206         {
10207           const char *path = svn_dirent_join(shard_path,
10208                                        apr_psprintf(iterpool, "%d", i),
10209                                        iterpool);
10210           if (cancel_func)
10211             SVN_ERR((*cancel_func)(cancel_baton));
10212
10213           SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
10214           svn_pool_clear(iterpool);
10215         }
10216
10217       svn_pool_destroy(iterpool);
10218     }
10219   else
10220     SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
10221                                cancel_func, cancel_baton, scratch_pool));
10222
10223   return SVN_NO_ERROR;
10224 }
10225
10226 /* In the file system at FS_PATH, pack the SHARD in REVS_DIR and
10227  * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL
10228  * for allocations.  REVPROPS_DIR will be NULL if revprop packing is not
10229  * supported.  COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that
10230  * case.
10231  *
10232  * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly
10233  * NOTIFY_FUNC and NOTIFY_BATON.
10234  *
10235  * If for some reason we detect a partial packing already performed, we
10236  * remove the pack file and start again.
10237  */
10238 static svn_error_t *
10239 pack_shard(const char *revs_dir,
10240            const char *revsprops_dir,
10241            const char *fs_path,
10242            apr_int64_t shard,
10243            int max_files_per_dir,
10244            apr_off_t max_pack_size,
10245            int compression_level,
10246            svn_fs_pack_notify_t notify_func,
10247            void *notify_baton,
10248            svn_cancel_func_t cancel_func,
10249            void *cancel_baton,
10250            apr_pool_t *pool)
10251 {
10252   const char *rev_shard_path, *rev_pack_file_dir;
10253   const char *revprops_shard_path, *revprops_pack_file_dir;
10254
10255   /* Notify caller we're starting to pack this shard. */
10256   if (notify_func)
10257     SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start,
10258                         pool));
10259
10260   /* Some useful paths. */
10261   rev_pack_file_dir = svn_dirent_join(revs_dir,
10262                   apr_psprintf(pool,
10263                                "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10264                                shard),
10265                   pool);
10266   rev_shard_path = svn_dirent_join(revs_dir,
10267                            apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10268                            pool);
10269
10270   /* pack the revision content */
10271   SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path,
10272                          shard, max_files_per_dir,
10273                          cancel_func, cancel_baton, pool));
10274
10275   /* if enabled, pack the revprops in an equivalent way */
10276   if (revsprops_dir)
10277     {
10278       revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
10279                    apr_psprintf(pool,
10280                                 "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10281                                 shard),
10282                    pool);
10283       revprops_shard_path = svn_dirent_join(revsprops_dir,
10284                            apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10285                            pool);
10286
10287       SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
10288                                   shard, max_files_per_dir,
10289                                   (int)(0.9 * max_pack_size),
10290                                   compression_level,
10291                                   cancel_func, cancel_baton, pool));
10292     }
10293
10294   /* Update the min-unpacked-rev file to reflect our newly packed shard.
10295    * (This doesn't update ffd->min_unpacked_rev.  That will be updated by
10296    * update_min_unpacked_rev() when necessary.) */
10297   SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV,
10298                             (svn_revnum_t)((shard + 1) * max_files_per_dir),
10299                             pool));
10300
10301   /* Finally, remove the existing shard directories. */
10302   SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE,
10303                              cancel_func, cancel_baton, pool));
10304   if (revsprops_dir)
10305     SVN_ERR(delete_revprops_shard(revprops_shard_path,
10306                                   shard, max_files_per_dir,
10307                                   cancel_func, cancel_baton, pool));
10308
10309   /* Notify caller we're starting to pack this shard. */
10310   if (notify_func)
10311     SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end,
10312                         pool));
10313
10314   return SVN_NO_ERROR;
10315 }
10316
10317 struct pack_baton
10318 {
10319   svn_fs_t *fs;
10320   svn_fs_pack_notify_t notify_func;
10321   void *notify_baton;
10322   svn_cancel_func_t cancel_func;
10323   void *cancel_baton;
10324 };
10325
10326
10327 /* The work-horse for svn_fs_fs__pack, called with the FS write lock.
10328    This implements the svn_fs_fs__with_write_lock() 'body' callback
10329    type.  BATON is a 'struct pack_baton *'.
10330
10331    WARNING: if you add a call to this function, please note:
10332      The code currently assumes that any piece of code running with
10333      the write-lock set can rely on the ffd->min_unpacked_rev and
10334      ffd->min_unpacked_revprop caches to be up-to-date (and, by
10335      extension, on not having to use a retry when calling
10336      svn_fs_fs__path_rev_absolute() and friends).  If you add a call
10337      to this function, consider whether you have to call
10338      update_min_unpacked_rev().
10339      See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith
10340  */
10341 static svn_error_t *
10342 pack_body(void *baton,
10343           apr_pool_t *pool)
10344 {
10345   struct pack_baton *pb = baton;
10346   fs_fs_data_t ffd = {0};
10347   apr_int64_t completed_shards;
10348   apr_int64_t i;
10349   svn_revnum_t youngest;
10350   apr_pool_t *iterpool;
10351   const char *rev_data_path;
10352   const char *revprops_data_path = NULL;
10353
10354   /* read repository settings */
10355   SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir,
10356                       path_format(pb->fs, pool), pool));
10357   SVN_ERR(check_format(ffd.format));
10358   SVN_ERR(read_config(&ffd, pb->fs->path, pool));
10359
10360   /* If the repository isn't a new enough format, we don't support packing.
10361      Return a friendly error to that effect. */
10362   if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT)
10363     return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
10364       _("FSFS format (%d) too old to pack; please upgrade the filesystem."),
10365       ffd.format);
10366
10367   /* If we aren't using sharding, we can't do any packing, so quit. */
10368   if (!ffd.max_files_per_dir)
10369     return SVN_NO_ERROR;
10370
10371   SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev,
10372                                 path_min_unpacked_rev(pb->fs, pool),
10373                                 pool));
10374
10375   SVN_ERR(get_youngest(&youngest, pb->fs->path, pool));
10376   completed_shards = (youngest + 1) / ffd.max_files_per_dir;
10377
10378   /* See if we've already completed all possible shards thus far. */
10379   if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir))
10380     return SVN_NO_ERROR;
10381
10382   rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
10383   if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
10384     revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
10385                                          pool);
10386
10387   iterpool = svn_pool_create(pool);
10388   for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir;
10389        i < completed_shards;
10390        i++)
10391     {
10392       svn_pool_clear(iterpool);
10393
10394       if (pb->cancel_func)
10395         SVN_ERR(pb->cancel_func(pb->cancel_baton));
10396
10397       SVN_ERR(pack_shard(rev_data_path, revprops_data_path,
10398                          pb->fs->path, i, ffd.max_files_per_dir,
10399                          ffd.revprop_pack_size,
10400                          ffd.compress_packed_revprops
10401                            ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
10402                            : SVN_DELTA_COMPRESSION_LEVEL_NONE,
10403                          pb->notify_func, pb->notify_baton,
10404                          pb->cancel_func, pb->cancel_baton, iterpool));
10405     }
10406
10407   svn_pool_destroy(iterpool);
10408   return SVN_NO_ERROR;
10409 }
10410
10411 svn_error_t *
10412 svn_fs_fs__pack(svn_fs_t *fs,
10413                 svn_fs_pack_notify_t notify_func,
10414                 void *notify_baton,
10415                 svn_cancel_func_t cancel_func,
10416                 void *cancel_baton,
10417                 apr_pool_t *pool)
10418 {
10419   struct pack_baton pb = { 0 };
10420   pb.fs = fs;
10421   pb.notify_func = notify_func;
10422   pb.notify_baton = notify_baton;
10423   pb.cancel_func = cancel_func;
10424   pb.cancel_baton = cancel_baton;
10425   return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool);
10426 }
10427
10428 \f
10429 /** Verifying. **/
10430
10431 /* Baton type expected by verify_walker().  The purpose is to reuse open
10432  * rev / pack file handles between calls.  Its contents need to be cleaned
10433  * periodically to limit resource usage.
10434  */
10435 typedef struct verify_walker_baton_t
10436 {
10437   /* number of calls to verify_walker() since the last clean */
10438   int iteration_count;
10439
10440   /* number of files opened since the last clean */
10441   int file_count;
10442
10443   /* progress notification callback to invoke periodically (may be NULL) */
10444   svn_fs_progress_notify_func_t notify_func;
10445
10446   /* baton to use with NOTIFY_FUNC */
10447   void *notify_baton;
10448
10449   /* remember the last revision for which we called notify_func */
10450   svn_revnum_t last_notified_revision;
10451
10452   /* current file handle (or NULL) */
10453   apr_file_t *file_hint;
10454
10455   /* corresponding revision (or SVN_INVALID_REVNUM) */
10456   svn_revnum_t rev_hint;
10457
10458   /* pool to use for the file handles etc. */
10459   apr_pool_t *pool;
10460 } verify_walker_baton_t;
10461
10462 /* Used by svn_fs_fs__verify().
10463    Implements svn_fs_fs__walk_rep_reference().walker.  */
10464 static svn_error_t *
10465 verify_walker(representation_t *rep,
10466               void *baton,
10467               svn_fs_t *fs,
10468               apr_pool_t *scratch_pool)
10469 {
10470   struct rep_state *rs;
10471   struct rep_args *rep_args;
10472
10473   if (baton)
10474     {
10475       verify_walker_baton_t *walker_baton = baton;
10476       apr_file_t * previous_file;
10477
10478       /* notify and free resources periodically */
10479       if (   walker_baton->iteration_count > 1000
10480           || walker_baton->file_count > 16)
10481         {
10482           if (   walker_baton->notify_func
10483               && rep->revision != walker_baton->last_notified_revision)
10484             {
10485               walker_baton->notify_func(rep->revision,
10486                                         walker_baton->notify_baton,
10487                                         scratch_pool);
10488               walker_baton->last_notified_revision = rep->revision;
10489             }
10490
10491           svn_pool_clear(walker_baton->pool);
10492
10493           walker_baton->iteration_count = 0;
10494           walker_baton->file_count = 0;
10495           walker_baton->file_hint = NULL;
10496           walker_baton->rev_hint = SVN_INVALID_REVNUM;
10497         }
10498
10499       /* access the repo data */
10500       previous_file = walker_baton->file_hint;
10501       SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint,
10502                                &walker_baton->rev_hint, rep, fs,
10503                                walker_baton->pool));
10504
10505       /* update resource usage counters */
10506       walker_baton->iteration_count++;
10507       if (previous_file != walker_baton->file_hint)
10508         walker_baton->file_count++;
10509     }
10510   else
10511     {
10512       /* ### Should this be using read_rep_line() directly? */
10513       SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs,
10514                                scratch_pool));
10515     }
10516
10517   return SVN_NO_ERROR;
10518 }
10519
10520 svn_error_t *
10521 svn_fs_fs__verify(svn_fs_t *fs,
10522                   svn_revnum_t start,
10523                   svn_revnum_t end,
10524                   svn_fs_progress_notify_func_t notify_func,
10525                   void *notify_baton,
10526                   svn_cancel_func_t cancel_func,
10527                   void *cancel_baton,
10528                   apr_pool_t *pool)
10529 {
10530   fs_fs_data_t *ffd = fs->fsap_data;
10531   svn_boolean_t exists;
10532   svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
10533
10534   if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
10535     return SVN_NO_ERROR;
10536
10537   /* Input validation. */
10538   if (! SVN_IS_VALID_REVNUM(start))
10539     start = 0;
10540   if (! SVN_IS_VALID_REVNUM(end))
10541     end = youngest;
10542   SVN_ERR(ensure_revision_exists(fs, start, pool));
10543   SVN_ERR(ensure_revision_exists(fs, end, pool));
10544
10545   /* rep-cache verification. */
10546   SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
10547   if (exists)
10548     {
10549       /* provide a baton to allow the reuse of open file handles between
10550          iterations (saves 2/3 of OS level file operations). */
10551       verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
10552       baton->rev_hint = SVN_INVALID_REVNUM;
10553       baton->pool = svn_pool_create(pool);
10554       baton->last_notified_revision = SVN_INVALID_REVNUM;
10555       baton->notify_func = notify_func;
10556       baton->notify_baton = notify_baton;
10557
10558       /* tell the user that we are now ready to do *something* */
10559       if (notify_func)
10560         notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool);
10561
10562       /* Do not attempt to walk the rep-cache database if its file does
10563          not exist,  since doing so would create it --- which may confuse
10564          the administrator.   Don't take any lock. */
10565       SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end,
10566                                             verify_walker, baton,
10567                                             cancel_func, cancel_baton,
10568                                             pool));
10569
10570       /* walker resource cleanup */
10571       svn_pool_destroy(baton->pool);
10572     }
10573
10574   return SVN_NO_ERROR;
10575 }
10576
10577 \f
10578 /** Hotcopy. **/
10579
10580 /* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
10581  * the destination and do not differ in terms of kind, size, and mtime. */
10582 static svn_error_t *
10583 hotcopy_io_dir_file_copy(const char *src_path,
10584                          const char *dst_path,
10585                          const char *file,
10586                          apr_pool_t *scratch_pool)
10587 {
10588   const svn_io_dirent2_t *src_dirent;
10589   const svn_io_dirent2_t *dst_dirent;
10590   const char *src_target;
10591   const char *dst_target;
10592
10593   /* Does the destination already exist? If not, we must copy it. */
10594   dst_target = svn_dirent_join(dst_path, file, scratch_pool);
10595   SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
10596                               scratch_pool, scratch_pool));
10597   if (dst_dirent->kind != svn_node_none)
10598     {
10599       /* If the destination's stat information indicates that the file
10600        * is equal to the source, don't bother copying the file again. */
10601       src_target = svn_dirent_join(src_path, file, scratch_pool);
10602       SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
10603                                   scratch_pool, scratch_pool));
10604       if (src_dirent->kind == dst_dirent->kind &&
10605           src_dirent->special == dst_dirent->special &&
10606           src_dirent->filesize == dst_dirent->filesize &&
10607           src_dirent->mtime <= dst_dirent->mtime)
10608         return SVN_NO_ERROR;
10609     }
10610
10611   return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
10612                                               scratch_pool));
10613 }
10614
10615 /* Set *NAME_P to the UTF-8 representation of directory entry NAME.
10616  * NAME is in the internal encoding used by APR; PARENT is in
10617  * UTF-8 and in internal (not local) style.
10618  *
10619  * Use PARENT only for generating an error string if the conversion
10620  * fails because NAME could not be represented in UTF-8.  In that
10621  * case, return a two-level error in which the outer error's message
10622  * mentions PARENT, but the inner error's message does not mention
10623  * NAME (except possibly in hex) since NAME may not be printable.
10624  * Such a compound error at least allows the user to go looking in the
10625  * right directory for the problem.
10626  *
10627  * If there is any other error, just return that error directly.
10628  *
10629  * If there is any error, the effect on *NAME_P is undefined.
10630  *
10631  * *NAME_P and NAME may refer to the same storage.
10632  */
10633 static svn_error_t *
10634 entry_name_to_utf8(const char **name_p,
10635                    const char *name,
10636                    const char *parent,
10637                    apr_pool_t *pool)
10638 {
10639   svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
10640   if (err && err->apr_err == APR_EINVAL)
10641     {
10642       return svn_error_createf(err->apr_err, err,
10643                                _("Error converting entry "
10644                                  "in directory '%s' to UTF-8"),
10645                                svn_dirent_local_style(parent, pool));
10646     }
10647   return err;
10648 }
10649
10650 /* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
10651  * exist in the destination and do not differ from the source in terms of
10652  * kind, size, and mtime. */
10653 static svn_error_t *
10654 hotcopy_io_copy_dir_recursively(const char *src,
10655                                 const char *dst_parent,
10656                                 const char *dst_basename,
10657                                 svn_boolean_t copy_perms,
10658                                 svn_cancel_func_t cancel_func,
10659                                 void *cancel_baton,
10660                                 apr_pool_t *pool)
10661 {
10662   svn_node_kind_t kind;
10663   apr_status_t status;
10664   const char *dst_path;
10665   apr_dir_t *this_dir;
10666   apr_finfo_t this_entry;
10667   apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
10668
10669   /* Make a subpool for recursion */
10670   apr_pool_t *subpool = svn_pool_create(pool);
10671
10672   /* The 'dst_path' is simply dst_parent/dst_basename */
10673   dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
10674
10675   /* Sanity checks:  SRC and DST_PARENT are directories, and
10676      DST_BASENAME doesn't already exist in DST_PARENT. */
10677   SVN_ERR(svn_io_check_path(src, &kind, subpool));
10678   if (kind != svn_node_dir)
10679     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10680                              _("Source '%s' is not a directory"),
10681                              svn_dirent_local_style(src, pool));
10682
10683   SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
10684   if (kind != svn_node_dir)
10685     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10686                              _("Destination '%s' is not a directory"),
10687                              svn_dirent_local_style(dst_parent, pool));
10688
10689   SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
10690
10691   /* Create the new directory. */
10692   /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
10693   SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
10694
10695   /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
10696   SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
10697
10698   for (status = apr_dir_read(&this_entry, flags, this_dir);
10699        status == APR_SUCCESS;
10700        status = apr_dir_read(&this_entry, flags, this_dir))
10701     {
10702       if ((this_entry.name[0] == '.')
10703           && ((this_entry.name[1] == '\0')
10704               || ((this_entry.name[1] == '.')
10705                   && (this_entry.name[2] == '\0'))))
10706         {
10707           continue;
10708         }
10709       else
10710         {
10711           const char *entryname_utf8;
10712
10713           if (cancel_func)
10714             SVN_ERR(cancel_func(cancel_baton));
10715
10716           SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
10717                                      src, subpool));
10718           if (this_entry.filetype == APR_REG) /* regular file */
10719             {
10720               SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8,
10721                                                subpool));
10722             }
10723           else if (this_entry.filetype == APR_LNK) /* symlink */
10724             {
10725               const char *src_target = svn_dirent_join(src, entryname_utf8,
10726                                                        subpool);
10727               const char *dst_target = svn_dirent_join(dst_path,
10728                                                        entryname_utf8,
10729                                                        subpool);
10730               SVN_ERR(svn_io_copy_link(src_target, dst_target,
10731                                        subpool));
10732             }
10733           else if (this_entry.filetype == APR_DIR) /* recurse */
10734             {
10735               const char *src_target;
10736
10737               /* Prevent infinite recursion by filtering off our
10738                  newly created destination path. */
10739               if (strcmp(src, dst_parent) == 0
10740                   && strcmp(entryname_utf8, dst_basename) == 0)
10741                 continue;
10742
10743               src_target = svn_dirent_join(src, entryname_utf8, subpool);
10744               SVN_ERR(hotcopy_io_copy_dir_recursively(src_target,
10745                                                       dst_path,
10746                                                       entryname_utf8,
10747                                                       copy_perms,
10748                                                       cancel_func,
10749                                                       cancel_baton,
10750                                                       subpool));
10751             }
10752           /* ### support other APR node types someday?? */
10753
10754         }
10755     }
10756
10757   if (! (APR_STATUS_IS_ENOENT(status)))
10758     return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
10759                               svn_dirent_local_style(src, pool));
10760
10761   status = apr_dir_close(this_dir);
10762   if (status)
10763     return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
10764                               svn_dirent_local_style(src, pool));
10765
10766   /* Free any memory used by recursion */
10767   svn_pool_destroy(subpool);
10768
10769   return SVN_NO_ERROR;
10770 }
10771
10772 /* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
10773  * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
10774  * Use SCRATCH_POOL for temporary allocations. */
10775 static svn_error_t *
10776 hotcopy_copy_shard_file(const char *src_subdir,
10777                         const char *dst_subdir,
10778                         svn_revnum_t rev,
10779                         int max_files_per_dir,
10780                         apr_pool_t *scratch_pool)
10781 {
10782   const char *src_subdir_shard = src_subdir,
10783              *dst_subdir_shard = dst_subdir;
10784
10785   if (max_files_per_dir)
10786     {
10787       const char *shard = apr_psprintf(scratch_pool, "%ld",
10788                                        rev / max_files_per_dir);
10789       src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
10790       dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10791
10792       if (rev % max_files_per_dir == 0)
10793         {
10794           SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
10795           SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
10796                                     scratch_pool));
10797         }
10798     }
10799
10800   SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
10801                                    apr_psprintf(scratch_pool, "%ld", rev),
10802                                    scratch_pool));
10803   return SVN_NO_ERROR;
10804 }
10805
10806
10807 /* Copy a packed shard containing revision REV, and which contains
10808  * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
10809  * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
10810  * Do not re-copy data which already exists in DST_FS.
10811  * Use SCRATCH_POOL for temporary allocations. */
10812 static svn_error_t *
10813 hotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev,
10814                           svn_fs_t *src_fs,
10815                           svn_fs_t *dst_fs,
10816                           svn_revnum_t rev,
10817                           int max_files_per_dir,
10818                           apr_pool_t *scratch_pool)
10819 {
10820   const char *src_subdir;
10821   const char *dst_subdir;
10822   const char *packed_shard;
10823   const char *src_subdir_packed_shard;
10824   svn_revnum_t revprop_rev;
10825   apr_pool_t *iterpool;
10826   fs_fs_data_t *src_ffd = src_fs->fsap_data;
10827
10828   /* Copy the packed shard. */
10829   src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
10830   dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
10831   packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10832                               rev / max_files_per_dir);
10833   src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10834                                             scratch_pool);
10835   SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10836                                           dst_subdir, packed_shard,
10837                                           TRUE /* copy_perms */,
10838                                           NULL /* cancel_func */, NULL,
10839                                           scratch_pool));
10840
10841   /* Copy revprops belonging to revisions in this pack. */
10842   src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10843   dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10844
10845   if (   src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
10846       || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
10847     {
10848       /* copy unpacked revprops rev by rev */
10849       iterpool = svn_pool_create(scratch_pool);
10850       for (revprop_rev = rev;
10851            revprop_rev < rev + max_files_per_dir;
10852            revprop_rev++)
10853         {
10854           svn_pool_clear(iterpool);
10855
10856           SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10857                                           revprop_rev, max_files_per_dir,
10858                                           iterpool));
10859         }
10860       svn_pool_destroy(iterpool);
10861     }
10862   else
10863     {
10864       /* revprop for revision 0 will never be packed */
10865       if (rev == 0)
10866         SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10867                                         0, max_files_per_dir,
10868                                         scratch_pool));
10869
10870       /* packed revprops folder */
10871       packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10872                                   rev / max_files_per_dir);
10873       src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10874                                                 scratch_pool);
10875       SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10876                                               dst_subdir, packed_shard,
10877                                               TRUE /* copy_perms */,
10878                                               NULL /* cancel_func */, NULL,
10879                                               scratch_pool));
10880     }
10881
10882   /* If necessary, update the min-unpacked rev file in the hotcopy. */
10883   if (*dst_min_unpacked_rev < rev + max_files_per_dir)
10884     {
10885       *dst_min_unpacked_rev = rev + max_files_per_dir;
10886       SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV,
10887                                 *dst_min_unpacked_rev,
10888                                 scratch_pool));
10889     }
10890
10891   return SVN_NO_ERROR;
10892 }
10893
10894 /* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current'
10895  * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST.
10896  * Use SCRATCH_POOL for temporary allocations. */
10897 static svn_error_t *
10898 hotcopy_update_current(svn_revnum_t *dst_youngest,
10899                        svn_fs_t *dst_fs,
10900                        svn_revnum_t new_youngest,
10901                        apr_pool_t *scratch_pool)
10902 {
10903   char next_node_id[MAX_KEY_SIZE] = "0";
10904   char next_copy_id[MAX_KEY_SIZE] = "0";
10905   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
10906
10907   if (*dst_youngest >= new_youngest)
10908     return SVN_NO_ERROR;
10909
10910   /* If necessary, get new current next_node and next_copy IDs. */
10911   if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
10912     {
10913       apr_off_t root_offset;
10914       apr_file_t *rev_file;
10915
10916       if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
10917         SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool));
10918
10919       SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest,
10920                                     scratch_pool));
10921       SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file,
10922                                       dst_fs, new_youngest, scratch_pool));
10923       SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file,
10924                                    root_offset, next_node_id, next_copy_id,
10925                                    scratch_pool));
10926       SVN_ERR(svn_io_file_close(rev_file, scratch_pool));
10927     }
10928
10929   /* Update 'current'. */
10930   SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id,
10931                         scratch_pool));
10932
10933   *dst_youngest = new_youngest;
10934
10935   return SVN_NO_ERROR;
10936 }
10937
10938
10939 /* Remove revision or revprop files between START_REV (inclusive) and
10940  * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS.  Assume
10941  * sharding as per MAX_FILES_PER_DIR.
10942  * Use SCRATCH_POOL for temporary allocations. */
10943 static svn_error_t *
10944 hotcopy_remove_files(svn_fs_t *dst_fs,
10945                      const char *dst_subdir,
10946                      svn_revnum_t start_rev,
10947                      svn_revnum_t end_rev,
10948                      int max_files_per_dir,
10949                      apr_pool_t *scratch_pool)
10950 {
10951   const char *shard;
10952   const char *dst_subdir_shard;
10953   svn_revnum_t rev;
10954   apr_pool_t *iterpool;
10955
10956   /* Pre-compute paths for initial shard. */
10957   shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
10958   dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10959
10960   iterpool = svn_pool_create(scratch_pool);
10961   for (rev = start_rev; rev < end_rev; rev++)
10962     {
10963       const char *path;
10964       svn_pool_clear(iterpool);
10965
10966       /* If necessary, update paths for shard. */
10967       if (rev != start_rev && rev % max_files_per_dir == 0)
10968         {
10969           shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
10970           dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10971         }
10972
10973       /* remove files for REV */
10974       path = svn_dirent_join(dst_subdir_shard,
10975                              apr_psprintf(iterpool, "%ld", rev),
10976                              iterpool);
10977
10978       /* Make the rev file writable and remove it. */
10979       SVN_ERR(svn_io_set_file_read_write(path, TRUE, iterpool));
10980       SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
10981     }
10982
10983   svn_pool_destroy(iterpool);
10984
10985   return SVN_NO_ERROR;
10986 }
10987
10988 /* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
10989  * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
10990  * Use SCRATCH_POOL for temporary allocations. */
10991 static svn_error_t *
10992 hotcopy_remove_rev_files(svn_fs_t *dst_fs,
10993                          svn_revnum_t start_rev,
10994                          svn_revnum_t end_rev,
10995                          int max_files_per_dir,
10996                          apr_pool_t *scratch_pool)
10997 {
10998   SVN_ERR_ASSERT(start_rev <= end_rev);
10999   SVN_ERR(hotcopy_remove_files(dst_fs,
11000                                svn_dirent_join(dst_fs->path,
11001                                                PATH_REVS_DIR,
11002                                                scratch_pool),
11003                                start_rev, end_rev,
11004                                max_files_per_dir, scratch_pool));
11005
11006   return SVN_NO_ERROR;
11007 }
11008
11009 /* Remove revision properties between START_REV (inclusive) and END_REV
11010  * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
11011  * Use SCRATCH_POOL for temporary allocations.  Revision 0 revprops will
11012  * not be deleted. */
11013 static svn_error_t *
11014 hotcopy_remove_revprop_files(svn_fs_t *dst_fs,
11015                              svn_revnum_t start_rev,
11016                              svn_revnum_t end_rev,
11017                              int max_files_per_dir,
11018                              apr_pool_t *scratch_pool)
11019 {
11020   SVN_ERR_ASSERT(start_rev <= end_rev);
11021
11022   /* don't delete rev 0 props */
11023   SVN_ERR(hotcopy_remove_files(dst_fs,
11024                                svn_dirent_join(dst_fs->path,
11025                                                PATH_REVPROPS_DIR,
11026                                                scratch_pool),
11027                                start_rev ? start_rev : 1, end_rev,
11028                                max_files_per_dir, scratch_pool));
11029
11030   return SVN_NO_ERROR;
11031 }
11032
11033 /* Verify that DST_FS is a suitable destination for an incremental
11034  * hotcopy from SRC_FS. */
11035 static svn_error_t *
11036 hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
11037                                         svn_fs_t *dst_fs,
11038                                         apr_pool_t *pool)
11039 {
11040   fs_fs_data_t *src_ffd = src_fs->fsap_data;
11041   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11042
11043   /* We only support incremental hotcopy between the same format. */
11044   if (src_ffd->format != dst_ffd->format)
11045     return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11046       _("The FSFS format (%d) of the hotcopy source does not match the "
11047         "FSFS format (%d) of the hotcopy destination; please upgrade "
11048         "both repositories to the same format"),
11049       src_ffd->format, dst_ffd->format);
11050
11051   /* Make sure the UUID of source and destination match up.
11052    * We don't want to copy over a different repository. */
11053   if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
11054     return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
11055                             _("The UUID of the hotcopy source does "
11056                               "not match the UUID of the hotcopy "
11057                               "destination"));
11058
11059   /* Also require same shard size. */
11060   if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
11061     return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11062                             _("The sharding layout configuration "
11063                               "of the hotcopy source does not match "
11064                               "the sharding layout configuration of "
11065                               "the hotcopy destination"));
11066   return SVN_NO_ERROR;
11067 }
11068
11069 /* Remove folder PATH.  Ignore errors due to the sub-tree not being empty.
11070  * CANCEL_FUNC and CANCEL_BATON do the usual thing.
11071  * Use POOL for temporary allocations.
11072  */
11073 static svn_error_t *
11074 remove_folder(const char *path,
11075               svn_cancel_func_t cancel_func,
11076               void *cancel_baton,
11077               apr_pool_t *pool)
11078 {
11079   svn_error_t *err = svn_io_remove_dir2(path, TRUE,
11080                                         cancel_func, cancel_baton, pool);
11081
11082   if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
11083     {
11084       svn_error_clear(err);
11085       err = SVN_NO_ERROR;
11086     }
11087
11088   return svn_error_trace(err);
11089 }
11090
11091 /* Baton for hotcopy_body(). */
11092 struct hotcopy_body_baton {
11093   svn_fs_t *src_fs;
11094   svn_fs_t *dst_fs;
11095   svn_boolean_t incremental;
11096   svn_cancel_func_t cancel_func;
11097   void *cancel_baton;
11098 } hotcopy_body_baton;
11099
11100 /* Perform a hotcopy, either normal or incremental.
11101  *
11102  * Normal hotcopy assumes that the destination exists as an empty
11103  * directory. It behaves like an incremental hotcopy except that
11104  * none of the copied files already exist in the destination.
11105  *
11106  * An incremental hotcopy copies only changed or new files to the destination,
11107  * and removes files from the destination no longer present in the source.
11108  * While the incremental hotcopy is running, readers should still be able
11109  * to access the destintation repository without error and should not see
11110  * revisions currently in progress of being copied. Readers are able to see
11111  * new fully copied revisions even if the entire incremental hotcopy procedure
11112  * has not yet completed.
11113  *
11114  * Writers are blocked out completely during the entire incremental hotcopy
11115  * process to ensure consistency. This function assumes that the repository
11116  * write-lock is held.
11117  */
11118 static svn_error_t *
11119 hotcopy_body(void *baton, apr_pool_t *pool)
11120 {
11121   struct hotcopy_body_baton *hbb = baton;
11122   svn_fs_t *src_fs = hbb->src_fs;
11123   fs_fs_data_t *src_ffd = src_fs->fsap_data;
11124   svn_fs_t *dst_fs = hbb->dst_fs;
11125   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11126   int max_files_per_dir = src_ffd->max_files_per_dir;
11127   svn_boolean_t incremental = hbb->incremental;
11128   svn_cancel_func_t cancel_func = hbb->cancel_func;
11129   void* cancel_baton = hbb->cancel_baton;
11130   svn_revnum_t src_youngest;
11131   svn_revnum_t dst_youngest;
11132   svn_revnum_t rev;
11133   svn_revnum_t src_min_unpacked_rev;
11134   svn_revnum_t dst_min_unpacked_rev;
11135   const char *src_subdir;
11136   const char *dst_subdir;
11137   const char *revprop_src_subdir;
11138   const char *revprop_dst_subdir;
11139   apr_pool_t *iterpool;
11140   svn_node_kind_t kind;
11141
11142   /* Try to copy the config.
11143    *
11144    * ### We try copying the config file before doing anything else,
11145    * ### because higher layers will abort the hotcopy if we throw
11146    * ### an error from this function, and that renders the hotcopy
11147    * ### unusable anyway. */
11148   if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
11149     {
11150       svn_error_t *err;
11151
11152       err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
11153                                  pool);
11154       if (err)
11155         {
11156           if (APR_STATUS_IS_ENOENT(err->apr_err))
11157             {
11158               /* 1.6.0 to 1.6.11 did not copy the configuration file during
11159                * hotcopy. So if we're hotcopying a repository which has been
11160                * created as a hotcopy itself, it's possible that fsfs.conf
11161                * does not exist. Ask the user to re-create it.
11162                *
11163                * ### It would be nice to make this a non-fatal error,
11164                * ### but this function does not get an svn_fs_t object
11165                * ### so we have no way of just printing a warning via
11166                * ### the fs->warning() callback. */
11167
11168               const char *msg;
11169               const char *src_abspath;
11170               const char *dst_abspath;
11171               const char *config_relpath;
11172               svn_error_t *err2;
11173
11174               config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
11175               err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
11176               if (err2)
11177                 return svn_error_trace(svn_error_compose_create(err, err2));
11178               err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
11179               if (err2)
11180                 return svn_error_trace(svn_error_compose_create(err, err2));
11181
11182               /* ### hack: strip off the 'db/' directory from paths so
11183                * ### they make sense to the user */
11184               src_abspath = svn_dirent_dirname(src_abspath, pool);
11185               dst_abspath = svn_dirent_dirname(dst_abspath, pool);
11186
11187               msg = apr_psprintf(pool,
11188                                  _("Failed to create hotcopy at '%s'. "
11189                                    "The file '%s' is missing from the source "
11190                                    "repository. Please create this file, for "
11191                                    "instance by running 'svnadmin upgrade %s'"),
11192                                  dst_abspath, config_relpath, src_abspath);
11193               return svn_error_quick_wrap(err, msg);
11194             }
11195           else
11196             return svn_error_trace(err);
11197         }
11198     }
11199
11200   if (cancel_func)
11201     SVN_ERR(cancel_func(cancel_baton));
11202
11203   /* Find the youngest revision in the source and destination.
11204    * We only support hotcopies from sources with an equal or greater amount
11205    * of revisions than the destination.
11206    * This also catches the case where users accidentally swap the
11207    * source and destination arguments. */
11208   SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool));
11209   if (incremental)
11210     {
11211       SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool));
11212       if (src_youngest < dst_youngest)
11213         return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11214                  _("The hotcopy destination already contains more revisions "
11215                    "(%lu) than the hotcopy source contains (%lu); are source "
11216                    "and destination swapped?"),
11217                   dst_youngest, src_youngest);
11218     }
11219   else
11220     dst_youngest = 0;
11221
11222   if (cancel_func)
11223     SVN_ERR(cancel_func(cancel_baton));
11224
11225   /* Copy the min unpacked rev, and read its value. */
11226   if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11227     {
11228       const char *min_unpacked_rev_path;
11229
11230       min_unpacked_rev_path = svn_dirent_join(src_fs->path,
11231                                               PATH_MIN_UNPACKED_REV,
11232                                               pool);
11233       SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev,
11234                                     min_unpacked_rev_path,
11235                                     pool));
11236
11237       min_unpacked_rev_path = svn_dirent_join(dst_fs->path,
11238                                               PATH_MIN_UNPACKED_REV,
11239                                               pool);
11240       SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev,
11241                                     min_unpacked_rev_path,
11242                                     pool));
11243
11244       /* We only support packs coming from the hotcopy source.
11245        * The destination should not be packed independently from
11246        * the source. This also catches the case where users accidentally
11247        * swap the source and destination arguments. */
11248       if (src_min_unpacked_rev < dst_min_unpacked_rev)
11249         return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11250                                  _("The hotcopy destination already contains "
11251                                    "more packed revisions (%lu) than the "
11252                                    "hotcopy source contains (%lu)"),
11253                                    dst_min_unpacked_rev - 1,
11254                                    src_min_unpacked_rev - 1);
11255
11256       SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11257                                    PATH_MIN_UNPACKED_REV, pool));
11258     }
11259   else
11260     {
11261       src_min_unpacked_rev = 0;
11262       dst_min_unpacked_rev = 0;
11263     }
11264
11265   if (cancel_func)
11266     SVN_ERR(cancel_func(cancel_baton));
11267
11268   /*
11269    * Copy the necessary rev files.
11270    */
11271
11272   src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
11273   dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
11274   SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
11275
11276   iterpool = svn_pool_create(pool);
11277   /* First, copy packed shards. */
11278   for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
11279     {
11280       svn_pool_clear(iterpool);
11281
11282       if (cancel_func)
11283         SVN_ERR(cancel_func(cancel_baton));
11284
11285       /* Copy the packed shard. */
11286       SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11287                                         src_fs, dst_fs,
11288                                         rev, max_files_per_dir,
11289                                         iterpool));
11290
11291       /* If necessary, update 'current' to the most recent packed rev,
11292        * so readers can see new revisions which arrived in this pack. */
11293       SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs,
11294                                      rev + max_files_per_dir - 1,
11295                                      iterpool));
11296
11297       /* Remove revision files which are now packed. */
11298       if (incremental)
11299         {
11300           SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
11301                                            rev + max_files_per_dir,
11302                                            max_files_per_dir, iterpool));
11303           if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11304             SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
11305                                                  rev + max_files_per_dir,
11306                                                  max_files_per_dir,
11307                                                  iterpool));
11308         }
11309
11310       /* Now that all revisions have moved into the pack, the original
11311        * rev dir can be removed. */
11312       SVN_ERR(remove_folder(path_rev_shard(dst_fs, rev, iterpool),
11313                             cancel_func, cancel_baton, iterpool));
11314       if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11315         SVN_ERR(remove_folder(path_revprops_shard(dst_fs, rev, iterpool),
11316                               cancel_func, cancel_baton, iterpool));
11317     }
11318
11319   if (cancel_func)
11320     SVN_ERR(cancel_func(cancel_baton));
11321
11322   /* Now, copy pairs of non-packed revisions and revprop files.
11323    * If necessary, update 'current' after copying all files from a shard. */
11324   SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
11325   SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
11326   revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
11327   revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
11328   SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool));
11329   for (; rev <= src_youngest; rev++)
11330     {
11331       svn_error_t *err;
11332
11333       svn_pool_clear(iterpool);
11334
11335       if (cancel_func)
11336         SVN_ERR(cancel_func(cancel_baton));
11337
11338       /* Copy the rev file. */
11339       err = hotcopy_copy_shard_file(src_subdir, dst_subdir,
11340                                     rev, max_files_per_dir,
11341                                     iterpool);
11342       if (err)
11343         {
11344           if (APR_STATUS_IS_ENOENT(err->apr_err) &&
11345               src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11346             {
11347               svn_error_clear(err);
11348
11349               /* The source rev file does not exist. This can happen if the
11350                * source repository is being packed concurrently with this
11351                * hotcopy operation.
11352                *
11353                * If the new revision is now packed, and the youngest revision
11354                * we're interested in is not inside this pack, try to copy the
11355                * pack instead.
11356                *
11357                * If the youngest revision ended up being packed, don't try
11358                * to be smart and work around this. Just abort the hotcopy. */
11359               SVN_ERR(update_min_unpacked_rev(src_fs, pool));
11360               if (is_packed_rev(src_fs, rev))
11361                 {
11362                   if (is_packed_rev(src_fs, src_youngest))
11363                     return svn_error_createf(
11364                              SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11365                              _("The assumed HEAD revision (%lu) of the "
11366                                "hotcopy source has been packed while the "
11367                                "hotcopy was in progress; please restart "
11368                                "the hotcopy operation"),
11369                              src_youngest);
11370
11371                   SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11372                                                     src_fs, dst_fs,
11373                                                     rev, max_files_per_dir,
11374                                                     iterpool));
11375                   rev = dst_min_unpacked_rev;
11376                   continue;
11377                 }
11378               else
11379                 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11380                                          _("Revision %lu disappeared from the "
11381                                            "hotcopy source while hotcopy was "
11382                                            "in progress"), rev);
11383             }
11384           else
11385             return svn_error_trace(err);
11386         }
11387
11388       /* Copy the revprop file. */
11389       SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir,
11390                                       revprop_dst_subdir,
11391                                       rev, max_files_per_dir,
11392                                       iterpool));
11393
11394       /* After completing a full shard, update 'current'. */
11395       if (max_files_per_dir && rev % max_files_per_dir == 0)
11396         SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool));
11397     }
11398   svn_pool_destroy(iterpool);
11399
11400   if (cancel_func)
11401     SVN_ERR(cancel_func(cancel_baton));
11402
11403   /* We assume that all revisions were copied now, i.e. we didn't exit the
11404    * above loop early. 'rev' was last incremented during exit of the loop. */
11405   SVN_ERR_ASSERT(rev == src_youngest + 1);
11406
11407   /* All revisions were copied. Update 'current'. */
11408   SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool));
11409
11410   /* Replace the locks tree.
11411    * This is racy in case readers are currently trying to list locks in
11412    * the destination. However, we need to get rid of stale locks.
11413    * This is the simplest way of doing this, so we accept this small race. */
11414   dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
11415   SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
11416                              pool));
11417   src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
11418   SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11419   if (kind == svn_node_dir)
11420     SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
11421                                         PATH_LOCKS_DIR, TRUE,
11422                                         cancel_func, cancel_baton, pool));
11423
11424   /* Now copy the node-origins cache tree. */
11425   src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
11426   SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11427   if (kind == svn_node_dir)
11428     SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path,
11429                                             PATH_NODE_ORIGINS_DIR, TRUE,
11430                                             cancel_func, cancel_baton, pool));
11431
11432   /*
11433    * NB: Data copied below is only read by writers, not readers.
11434    *     Writers are still locked out at this point.
11435    */
11436
11437   if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
11438     {
11439       /* Copy the rep cache and then remove entries for revisions
11440        * younger than the destination's youngest revision. */
11441       src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
11442       dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
11443       SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11444       if (kind == svn_node_file)
11445         {
11446           SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
11447           SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool));
11448         }
11449     }
11450
11451   /* Copy the txn-current file. */
11452   if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11453     SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11454                                  PATH_TXN_CURRENT, pool));
11455
11456   /* If a revprop generation file exists in the source filesystem,
11457    * reset it to zero (since this is on a different path, it will not
11458    * overlap with data already in cache).  Also, clean up stale files
11459    * used for the named atomics implementation. */
11460   SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
11461                             &kind, pool));
11462   if (kind == svn_node_file)
11463     SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool));
11464
11465   SVN_ERR(cleanup_revprop_namespace(dst_fs));
11466
11467   /* Hotcopied FS is complete. Stamp it with a format file. */
11468   SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool),
11469                        dst_ffd->format, max_files_per_dir, TRUE, pool));
11470
11471   return SVN_NO_ERROR;
11472 }
11473
11474
11475 /* Set up shared data between SRC_FS and DST_FS. */
11476 static void
11477 hotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs)
11478 {
11479   fs_fs_data_t *src_ffd = src_fs->fsap_data;
11480   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11481
11482   /* The common pool and mutexes are shared between src and dst filesystems.
11483    * During hotcopy we only grab the mutexes for the destination, so there
11484    * is no risk of dead-lock. We don't write to the src filesystem. Shared
11485    * data for the src_fs has already been initialised in fs_hotcopy(). */
11486   dst_ffd->shared = src_ffd->shared;
11487 }
11488
11489 /* Create an empty filesystem at DST_FS at DST_PATH with the same
11490  * configuration as SRC_FS (uuid, format, and other parameters).
11491  * After creation DST_FS has no revisions, not even revision zero. */
11492 static svn_error_t *
11493 hotcopy_create_empty_dest(svn_fs_t *src_fs,
11494                           svn_fs_t *dst_fs,
11495                           const char *dst_path,
11496                           apr_pool_t *pool)
11497 {
11498   fs_fs_data_t *src_ffd = src_fs->fsap_data;
11499   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11500
11501   dst_fs->path = apr_pstrdup(pool, dst_path);
11502
11503   dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir;
11504   dst_ffd->config = src_ffd->config;
11505   dst_ffd->format = src_ffd->format;
11506
11507   /* Create the revision data directories. */
11508   if (dst_ffd->max_files_per_dir)
11509     SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool),
11510                                         pool));
11511   else
11512     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11513                                                         PATH_REVS_DIR, pool),
11514                                         pool));
11515
11516   /* Create the revprops directory. */
11517   if (src_ffd->max_files_per_dir)
11518     SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool),
11519                                         pool));
11520   else
11521     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11522                                                         PATH_REVPROPS_DIR,
11523                                                         pool),
11524                                         pool));
11525
11526   /* Create the transaction directory. */
11527   SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR,
11528                                                       pool),
11529                                       pool));
11530
11531   /* Create the protorevs directory. */
11532   if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
11533     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11534                                                         PATH_TXN_PROTOS_DIR,
11535                                                         pool),
11536                                         pool));
11537
11538   /* Create the 'current' file. */
11539   SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool),
11540                              (dst_ffd->format >=
11541                                 SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
11542                                 ? "0\n" : "0 1 1\n"),
11543                              pool));
11544
11545   /* Create lock file and UUID. */
11546   SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool));
11547   SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool));
11548
11549   /* Create the min unpacked rev file. */
11550   if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11551     SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool),
11552                                                      "0\n", pool));
11553   /* Create the txn-current file if the repository supports
11554      the transaction sequence file. */
11555   if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11556     {
11557       SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool),
11558                                  "0\n", pool));
11559       SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool),
11560                                  "", pool));
11561     }
11562
11563   dst_ffd->youngest_rev_cache = 0;
11564
11565   hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11566   SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11567
11568   return SVN_NO_ERROR;
11569 }
11570
11571 svn_error_t *
11572 svn_fs_fs__hotcopy(svn_fs_t *src_fs,
11573                    svn_fs_t *dst_fs,
11574                    const char *src_path,
11575                    const char *dst_path,
11576                    svn_boolean_t incremental,
11577                    svn_cancel_func_t cancel_func,
11578                    void *cancel_baton,
11579                    apr_pool_t *pool)
11580 {
11581   struct hotcopy_body_baton hbb;
11582
11583   if (cancel_func)
11584     SVN_ERR(cancel_func(cancel_baton));
11585
11586   SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
11587
11588   if (incremental)
11589     {
11590       const char *dst_format_abspath;
11591       svn_node_kind_t dst_format_kind;
11592
11593       /* Check destination format to be sure we know how to incrementally
11594        * hotcopy to the destination FS. */
11595       dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
11596       SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
11597       if (dst_format_kind == svn_node_none)
11598         {
11599           /* Destination doesn't exist yet. Perform a normal hotcopy to a
11600            * empty destination using the same configuration as the source. */
11601           SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11602         }
11603       else
11604         {
11605           /* Check the existing repository. */
11606           SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
11607           SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
11608                                                           pool));
11609           hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11610           SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11611         }
11612     }
11613   else
11614     {
11615       /* Start out with an empty destination using the same configuration
11616        * as the source. */
11617       SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11618     }
11619
11620   if (cancel_func)
11621     SVN_ERR(cancel_func(cancel_baton));
11622
11623   hbb.src_fs = src_fs;
11624   hbb.dst_fs = dst_fs;
11625   hbb.incremental = incremental;
11626   hbb.cancel_func = cancel_func;
11627   hbb.cancel_baton = cancel_baton;
11628   SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool));
11629
11630   return SVN_NO_ERROR;
11631 }