]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - contrib/subversion/subversion/libsvn_fs_fs/fs_fs.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.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   apr_file_t *file;
3992   svn_stream_t *stream;
3993   *final_path = path_revprops(fs, rev, pool);
3994
3995   /* ### do we have a directory sitting around already? we really shouldn't
3996      ### have to get the dirname here. */
3997   SVN_ERR(svn_io_open_unique_file3(&file, tmp_path,
3998                                    svn_dirent_dirname(*final_path, pool),
3999                                    svn_io_file_del_none, pool, pool));
4000   stream = svn_stream_from_aprfile2(file, TRUE, pool);
4001   SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4002   SVN_ERR(svn_stream_close(stream));
4003
4004   /* Flush temporary file to disk and close it. */
4005   SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4006   SVN_ERR(svn_io_file_close(file, pool));
4007
4008   return SVN_NO_ERROR;
4009 }
4010
4011 /* After writing the new revprop file(s), call this function to move the
4012  * file at TMP_PATH to FINAL_PATH and give it the permissions from
4013  * PERMS_REFERENCE.
4014  *
4015  * If indicated in BUMP_GENERATION, increase FS' revprop generation.
4016  * Finally, delete all the temporary files given in FILES_TO_DELETE.
4017  * The latter may be NULL.
4018  *
4019  * Use POOL for temporary allocations.
4020  */
4021 static svn_error_t *
4022 switch_to_new_revprop(svn_fs_t *fs,
4023                       const char *final_path,
4024                       const char *tmp_path,
4025                       const char *perms_reference,
4026                       apr_array_header_t *files_to_delete,
4027                       svn_boolean_t bump_generation,
4028                       apr_pool_t *pool)
4029 {
4030   /* Now, we may actually be replacing revprops. Make sure that all other
4031      threads and processes will know about this. */
4032   if (bump_generation)
4033     SVN_ERR(begin_revprop_change(fs, pool));
4034
4035   SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool));
4036
4037   /* Indicate that the update (if relevant) has been completed. */
4038   if (bump_generation)
4039     SVN_ERR(end_revprop_change(fs, pool));
4040
4041   /* Clean up temporary files, if necessary. */
4042   if (files_to_delete)
4043     {
4044       apr_pool_t *iterpool = svn_pool_create(pool);
4045       int i;
4046
4047       for (i = 0; i < files_to_delete->nelts; ++i)
4048         {
4049           const char *path = APR_ARRAY_IDX(files_to_delete, i, const char*);
4050           SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
4051           svn_pool_clear(iterpool);
4052         }
4053
4054       svn_pool_destroy(iterpool);
4055     }
4056   return SVN_NO_ERROR;
4057 }
4058
4059 /* Write a pack file header to STREAM that starts at revision START_REVISION
4060  * and contains the indexes [START,END) of SIZES.
4061  */
4062 static svn_error_t *
4063 serialize_revprops_header(svn_stream_t *stream,
4064                           svn_revnum_t start_revision,
4065                           apr_array_header_t *sizes,
4066                           int start,
4067                           int end,
4068                           apr_pool_t *pool)
4069 {
4070   apr_pool_t *iterpool = svn_pool_create(pool);
4071   int i;
4072
4073   SVN_ERR_ASSERT(start < end);
4074
4075   /* start revision and entry count */
4076   SVN_ERR(svn_stream_printf(stream, pool, "%ld\n", start_revision));
4077   SVN_ERR(svn_stream_printf(stream, pool, "%d\n", end - start));
4078
4079   /* the sizes array */
4080   for (i = start; i < end; ++i)
4081     {
4082       apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
4083       SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
4084                                 size));
4085     }
4086
4087   /* the double newline char indicates the end of the header */
4088   SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
4089
4090   svn_pool_clear(iterpool);
4091   return SVN_NO_ERROR;
4092 }
4093
4094 /* Writes the a pack file to FILE.  It copies the serialized data
4095  * from REVPROPS for the indexes [START,END) except for index CHANGED_INDEX.
4096  *
4097  * The data for the latter is taken from NEW_SERIALIZED.  Note, that
4098  * CHANGED_INDEX may be outside the [START,END) range, i.e. no new data is
4099  * taken in that case but only a subset of the old data will be copied.
4100  *
4101  * NEW_TOTAL_SIZE is a hint for pre-allocating buffers of appropriate size.
4102  * POOL is used for temporary allocations.
4103  */
4104 static svn_error_t *
4105 repack_revprops(svn_fs_t *fs,
4106                 packed_revprops_t *revprops,
4107                 int start,
4108                 int end,
4109                 int changed_index,
4110                 svn_stringbuf_t *new_serialized,
4111                 apr_off_t new_total_size,
4112                 apr_file_t *file,
4113                 apr_pool_t *pool)
4114 {
4115   fs_fs_data_t *ffd = fs->fsap_data;
4116   svn_stream_t *stream;
4117   int i;
4118
4119   /* create data empty buffers and the stream object */
4120   svn_stringbuf_t *uncompressed
4121     = svn_stringbuf_create_ensure((apr_size_t)new_total_size, pool);
4122   svn_stringbuf_t *compressed
4123     = svn_stringbuf_create_empty(pool);
4124   stream = svn_stream_from_stringbuf(uncompressed, pool);
4125
4126   /* write the header*/
4127   SVN_ERR(serialize_revprops_header(stream, revprops->start_revision + start,
4128                                     revprops->sizes, start, end, pool));
4129
4130   /* append the serialized revprops */
4131   for (i = start; i < end; ++i)
4132     if (i == changed_index)
4133       {
4134         SVN_ERR(svn_stream_write(stream,
4135                                  new_serialized->data,
4136                                  &new_serialized->len));
4137       }
4138     else
4139       {
4140         apr_size_t size
4141             = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
4142         apr_size_t offset
4143             = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
4144
4145         SVN_ERR(svn_stream_write(stream,
4146                                  revprops->packed_revprops->data + offset,
4147                                  &size));
4148       }
4149
4150   /* flush the stream buffer (if any) to our underlying data buffer */
4151   SVN_ERR(svn_stream_close(stream));
4152
4153   /* compress / store the data */
4154   SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
4155                         compressed,
4156                         ffd->compress_packed_revprops
4157                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
4158                           : SVN_DELTA_COMPRESSION_LEVEL_NONE));
4159
4160   /* finally, write the content to the target file, flush and close it */
4161   SVN_ERR(svn_io_file_write_full(file, compressed->data, compressed->len,
4162                                  NULL, pool));
4163   SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4164   SVN_ERR(svn_io_file_close(file, pool));
4165
4166   return SVN_NO_ERROR;
4167 }
4168
4169 /* Allocate a new pack file name for revisions
4170  *     [REVPROPS->START_REVISION + START, REVPROPS->START_REVISION + END - 1]
4171  * of REVPROPS->MANIFEST.  Add the name of old file to FILES_TO_DELETE,
4172  * auto-create that array if necessary.  Return an open file *FILE that is
4173  * allocated in POOL.
4174  */
4175 static svn_error_t *
4176 repack_file_open(apr_file_t **file,
4177                  svn_fs_t *fs,
4178                  packed_revprops_t *revprops,
4179                  int start,
4180                  int end,
4181                  apr_array_header_t **files_to_delete,
4182                  apr_pool_t *pool)
4183 {
4184   apr_int64_t tag;
4185   const char *tag_string;
4186   svn_string_t *new_filename;
4187   int i;
4188   int manifest_offset
4189     = (int)(revprops->start_revision - revprops->manifest_start);
4190
4191   /* get the old (= current) file name and enlist it for later deletion */
4192   const char *old_filename = APR_ARRAY_IDX(revprops->manifest,
4193                                            start + manifest_offset,
4194                                            const char*);
4195
4196   if (*files_to_delete == NULL)
4197     *files_to_delete = apr_array_make(pool, 3, sizeof(const char*));
4198
4199   APR_ARRAY_PUSH(*files_to_delete, const char*)
4200     = svn_dirent_join(revprops->folder, old_filename, pool);
4201
4202   /* increase the tag part, i.e. the counter after the dot */
4203   tag_string = strchr(old_filename, '.');
4204   if (tag_string == NULL)
4205     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
4206                              _("Packed file '%s' misses a tag"),
4207                              old_filename);
4208
4209   SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
4210   new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT,
4211                                     revprops->start_revision + start,
4212                                     ++tag);
4213
4214   /* update the manifest to point to the new file */
4215   for (i = start; i < end; ++i)
4216     APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
4217       = new_filename->data;
4218
4219   /* open the file */
4220   SVN_ERR(svn_io_file_open(file, svn_dirent_join(revprops->folder,
4221                                                  new_filename->data,
4222                                                  pool),
4223                            APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
4224
4225   return SVN_NO_ERROR;
4226 }
4227
4228 /* For revision REV in filesystem FS, set the revision properties to
4229  * PROPLIST.  Return a new file in *TMP_PATH that the caller shall move
4230  * to *FINAL_PATH to make the change visible.  Files to be deleted will
4231  * be listed in *FILES_TO_DELETE which may remain unchanged / unallocated.
4232  * Use POOL for allocations.
4233  */
4234 static svn_error_t *
4235 write_packed_revprop(const char **final_path,
4236                      const char **tmp_path,
4237                      apr_array_header_t **files_to_delete,
4238                      svn_fs_t *fs,
4239                      svn_revnum_t rev,
4240                      apr_hash_t *proplist,
4241                      apr_pool_t *pool)
4242 {
4243   fs_fs_data_t *ffd = fs->fsap_data;
4244   packed_revprops_t *revprops;
4245   apr_int64_t generation = 0;
4246   svn_stream_t *stream;
4247   apr_file_t *file;
4248   svn_stringbuf_t *serialized;
4249   apr_off_t new_total_size;
4250   int changed_index;
4251
4252   /* read the current revprop generation. This value will not change
4253    * while we hold the global write lock to this FS. */
4254   if (has_revprop_cache(fs, pool))
4255     SVN_ERR(read_revprop_generation(&generation, fs, pool));
4256
4257   /* read contents of the current pack file */
4258   SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, pool));
4259
4260   /* serialize the new revprops */
4261   serialized = svn_stringbuf_create_empty(pool);
4262   stream = svn_stream_from_stringbuf(serialized, pool);
4263   SVN_ERR(svn_hash_write2(proplist, stream, SVN_HASH_TERMINATOR, pool));
4264   SVN_ERR(svn_stream_close(stream));
4265
4266   /* calculate the size of the new data */
4267   changed_index = (int)(rev - revprops->start_revision);
4268   new_total_size = revprops->total_size - revprops->serialized_size
4269                  + serialized->len
4270                  + (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
4271
4272   APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
4273
4274   /* can we put the new data into the same pack as the before? */
4275   if (   new_total_size < ffd->revprop_pack_size
4276       || revprops->sizes->nelts == 1)
4277     {
4278       /* simply replace the old pack file with new content as we do it
4279        * in the non-packed case */
4280
4281       *final_path = svn_dirent_join(revprops->folder, revprops->filename,
4282                                     pool);
4283       SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder,
4284                                        svn_io_file_del_none, pool, pool));
4285       SVN_ERR(repack_revprops(fs, revprops, 0, revprops->sizes->nelts,
4286                               changed_index, serialized, new_total_size,
4287                               file, pool));
4288     }
4289   else
4290     {
4291       /* split the pack file into two of roughly equal size */
4292       int right_count, left_count, i;
4293
4294       int left = 0;
4295       int right = revprops->sizes->nelts - 1;
4296       apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
4297       apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
4298
4299       /* let left and right side grow such that their size difference
4300        * is minimal after each step. */
4301       while (left <= right)
4302         if (  left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4303             < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
4304           {
4305             left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
4306                       + SVN_INT64_BUFFER_SIZE;
4307             ++left;
4308           }
4309         else
4310           {
4311             right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
4312                         + SVN_INT64_BUFFER_SIZE;
4313             --right;
4314           }
4315
4316        /* since the items need much less than SVN_INT64_BUFFER_SIZE
4317         * bytes to represent their length, the split may not be optimal */
4318       left_count = left;
4319       right_count = revprops->sizes->nelts - left;
4320
4321       /* if new_size is large, one side may exceed the pack size limit.
4322        * In that case, split before and after the modified revprop.*/
4323       if (   left_size > ffd->revprop_pack_size
4324           || right_size > ffd->revprop_pack_size)
4325         {
4326           left_count = changed_index;
4327           right_count = revprops->sizes->nelts - left_count - 1;
4328         }
4329
4330       /* write the new, split files */
4331       if (left_count)
4332         {
4333           SVN_ERR(repack_file_open(&file, fs, revprops, 0,
4334                                    left_count, files_to_delete, pool));
4335           SVN_ERR(repack_revprops(fs, revprops, 0, left_count,
4336                                   changed_index, serialized, new_total_size,
4337                                   file, pool));
4338         }
4339
4340       if (left_count + right_count < revprops->sizes->nelts)
4341         {
4342           SVN_ERR(repack_file_open(&file, fs, revprops, changed_index,
4343                                    changed_index + 1, files_to_delete,
4344                                    pool));
4345           SVN_ERR(repack_revprops(fs, revprops, changed_index,
4346                                   changed_index + 1,
4347                                   changed_index, serialized, new_total_size,
4348                                   file, pool));
4349         }
4350
4351       if (right_count)
4352         {
4353           SVN_ERR(repack_file_open(&file, fs, revprops,
4354                                    revprops->sizes->nelts - right_count,
4355                                    revprops->sizes->nelts,
4356                                    files_to_delete, pool));
4357           SVN_ERR(repack_revprops(fs, revprops,
4358                                   revprops->sizes->nelts - right_count,
4359                                   revprops->sizes->nelts, changed_index,
4360                                   serialized, new_total_size, file,
4361                                   pool));
4362         }
4363
4364       /* write the new manifest */
4365       *final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
4366       SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder,
4367                                        svn_io_file_del_none, pool, pool));
4368
4369       for (i = 0; i < revprops->manifest->nelts; ++i)
4370         {
4371           const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
4372                                                const char*);
4373           SVN_ERR(svn_io_file_write_full(file, filename, strlen(filename),
4374                                          NULL, pool));
4375           SVN_ERR(svn_io_file_putc('\n', file, pool));
4376         }
4377
4378       SVN_ERR(svn_io_file_flush_to_disk(file, pool));
4379       SVN_ERR(svn_io_file_close(file, pool));
4380     }
4381
4382   return SVN_NO_ERROR;
4383 }
4384
4385 /* Set the revision property list of revision REV in filesystem FS to
4386    PROPLIST.  Use POOL for temporary allocations. */
4387 static svn_error_t *
4388 set_revision_proplist(svn_fs_t *fs,
4389                       svn_revnum_t rev,
4390                       apr_hash_t *proplist,
4391                       apr_pool_t *pool)
4392 {
4393   svn_boolean_t is_packed;
4394   svn_boolean_t bump_generation = FALSE;
4395   const char *final_path;
4396   const char *tmp_path;
4397   const char *perms_reference;
4398   apr_array_header_t *files_to_delete = NULL;
4399
4400   SVN_ERR(ensure_revision_exists(fs, rev, pool));
4401
4402   /* this info will not change while we hold the global FS write lock */
4403   is_packed = is_packed_revprop(fs, rev);
4404
4405   /* Test whether revprops already exist for this revision.
4406    * Only then will we need to bump the revprop generation. */
4407   if (has_revprop_cache(fs, pool))
4408     {
4409       if (is_packed)
4410         {
4411           bump_generation = TRUE;
4412         }
4413       else
4414         {
4415           svn_node_kind_t kind;
4416           SVN_ERR(svn_io_check_path(path_revprops(fs, rev, pool), &kind,
4417                                     pool));
4418           bump_generation = kind != svn_node_none;
4419         }
4420     }
4421
4422   /* Serialize the new revprop data */
4423   if (is_packed)
4424     SVN_ERR(write_packed_revprop(&final_path, &tmp_path, &files_to_delete,
4425                                  fs, rev, proplist, pool));
4426   else
4427     SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
4428                                      fs, rev, proplist, pool));
4429
4430   /* We use the rev file of this revision as the perms reference,
4431    * because when setting revprops for the first time, the revprop
4432    * file won't exist and therefore can't serve as its own reference.
4433    * (Whereas the rev file should already exist at this point.)
4434    */
4435   SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool));
4436
4437   /* Now, switch to the new revprop data. */
4438   SVN_ERR(switch_to_new_revprop(fs, final_path, tmp_path, perms_reference,
4439                                 files_to_delete, bump_generation, pool));
4440
4441   return SVN_NO_ERROR;
4442 }
4443
4444 svn_error_t *
4445 svn_fs_fs__revision_proplist(apr_hash_t **proplist_p,
4446                              svn_fs_t *fs,
4447                              svn_revnum_t rev,
4448                              apr_pool_t *pool)
4449 {
4450   SVN_ERR(get_revision_proplist(proplist_p, fs, rev, pool));
4451
4452   return SVN_NO_ERROR;
4453 }
4454
4455 /* Represents where in the current svndiff data block each
4456    representation is. */
4457 struct rep_state
4458 {
4459   apr_file_t *file;
4460                     /* The txdelta window cache to use or NULL. */
4461   svn_cache__t *window_cache;
4462                     /* Caches un-deltified windows. May be NULL. */
4463   svn_cache__t *combined_cache;
4464   apr_off_t start;  /* The starting offset for the raw
4465                        svndiff/plaintext data minus header. */
4466   apr_off_t off;    /* The current offset into the file. */
4467   apr_off_t end;    /* The end offset of the raw data. */
4468   int ver;          /* If a delta, what svndiff version? */
4469   int chunk_index;
4470 };
4471
4472 /* See create_rep_state, which wraps this and adds another error. */
4473 static svn_error_t *
4474 create_rep_state_body(struct rep_state **rep_state,
4475                       struct rep_args **rep_args,
4476                       apr_file_t **file_hint,
4477                       svn_revnum_t *rev_hint,
4478                       representation_t *rep,
4479                       svn_fs_t *fs,
4480                       apr_pool_t *pool)
4481 {
4482   fs_fs_data_t *ffd = fs->fsap_data;
4483   struct rep_state *rs = apr_pcalloc(pool, sizeof(*rs));
4484   struct rep_args *ra;
4485   unsigned char buf[4];
4486
4487   /* If the hint is
4488    * - given,
4489    * - refers to a valid revision,
4490    * - refers to a packed revision,
4491    * - as does the rep we want to read, and
4492    * - refers to the same pack file as the rep
4493    * ...
4494    */
4495   if (   file_hint && rev_hint && *file_hint
4496       && SVN_IS_VALID_REVNUM(*rev_hint)
4497       && *rev_hint < ffd->min_unpacked_rev
4498       && rep->revision < ffd->min_unpacked_rev
4499       && (   (*rev_hint / ffd->max_files_per_dir)
4500           == (rep->revision / ffd->max_files_per_dir)))
4501     {
4502       /* ... we can re-use the same, already open file object
4503        */
4504       apr_off_t offset;
4505       SVN_ERR(get_packed_offset(&offset, fs, rep->revision, pool));
4506
4507       offset += rep->offset;
4508       SVN_ERR(svn_io_file_seek(*file_hint, APR_SET, &offset, pool));
4509
4510       rs->file = *file_hint;
4511     }
4512   else
4513     {
4514       /* otherwise, create a new file object
4515        */
4516       SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
4517     }
4518
4519   /* remember the current file, if suggested by the caller */
4520   if (file_hint)
4521     *file_hint = rs->file;
4522   if (rev_hint)
4523     *rev_hint = rep->revision;
4524
4525   /* continue constructing RS and RA */
4526   rs->window_cache = ffd->txdelta_window_cache;
4527   rs->combined_cache = ffd->combined_window_cache;
4528
4529   SVN_ERR(read_rep_line(&ra, rs->file, pool));
4530   SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
4531   rs->off = rs->start;
4532   rs->end = rs->start + rep->size;
4533   *rep_state = rs;
4534   *rep_args = ra;
4535
4536   if (!ra->is_delta)
4537     /* This is a plaintext, so just return the current rep_state. */
4538     return SVN_NO_ERROR;
4539
4540   /* We are dealing with a delta, find out what version. */
4541   SVN_ERR(svn_io_file_read_full2(rs->file, buf, sizeof(buf),
4542                                  NULL, NULL, pool));
4543   /* ### Layering violation */
4544   if (! ((buf[0] == 'S') && (buf[1] == 'V') && (buf[2] == 'N')))
4545     return svn_error_create
4546       (SVN_ERR_FS_CORRUPT, NULL,
4547        _("Malformed svndiff data in representation"));
4548   rs->ver = buf[3];
4549   rs->chunk_index = 0;
4550   rs->off += 4;
4551
4552   return SVN_NO_ERROR;
4553 }
4554
4555 /* Read the rep args for REP in filesystem FS and create a rep_state
4556    for reading the representation.  Return the rep_state in *REP_STATE
4557    and the rep args in *REP_ARGS, both allocated in POOL.
4558
4559    When reading multiple reps, i.e. a skip delta chain, you may provide
4560    non-NULL FILE_HINT and REV_HINT.  (If FILE_HINT is not NULL, in the first
4561    call it should be a pointer to NULL.)  The function will use these variables
4562    to store the previous call results and tries to re-use them.  This may
4563    result in significant savings in I/O for packed files.
4564  */
4565 static svn_error_t *
4566 create_rep_state(struct rep_state **rep_state,
4567                  struct rep_args **rep_args,
4568                  apr_file_t **file_hint,
4569                  svn_revnum_t *rev_hint,
4570                  representation_t *rep,
4571                  svn_fs_t *fs,
4572                  apr_pool_t *pool)
4573 {
4574   svn_error_t *err = create_rep_state_body(rep_state, rep_args,
4575                                            file_hint, rev_hint,
4576                                            rep, fs, pool);
4577   if (err && err->apr_err == SVN_ERR_FS_CORRUPT)
4578     {
4579       fs_fs_data_t *ffd = fs->fsap_data;
4580
4581       /* ### This always returns "-1" for transaction reps, because
4582          ### this particular bit of code doesn't know if the rep is
4583          ### stored in the protorev or in the mutable area (for props
4584          ### or dir contents).  It is pretty rare for FSFS to *read*
4585          ### from the protorev file, though, so this is probably OK.
4586          ### And anyone going to debug corruption errors is probably
4587          ### going to jump straight to this comment anyway! */
4588       return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
4589                                "Corrupt representation '%s'",
4590                                rep
4591                                ? representation_string(rep, ffd->format, TRUE,
4592                                                        TRUE, pool)
4593                                : "(null)");
4594     }
4595   /* ### Call representation_string() ? */
4596   return svn_error_trace(err);
4597 }
4598
4599 struct rep_read_baton
4600 {
4601   /* The FS from which we're reading. */
4602   svn_fs_t *fs;
4603
4604   /* If not NULL, this is the base for the first delta window in rs_list */
4605   svn_stringbuf_t *base_window;
4606
4607   /* The state of all prior delta representations. */
4608   apr_array_header_t *rs_list;
4609
4610   /* The plaintext state, if there is a plaintext. */
4611   struct rep_state *src_state;
4612
4613   /* The index of the current delta chunk, if we are reading a delta. */
4614   int chunk_index;
4615
4616   /* The buffer where we store undeltified data. */
4617   char *buf;
4618   apr_size_t buf_pos;
4619   apr_size_t buf_len;
4620
4621   /* A checksum context for summing the data read in order to verify it.
4622      Note: we don't need to use the sha1 checksum because we're only doing
4623      data verification, for which md5 is perfectly safe.  */
4624   svn_checksum_ctx_t *md5_checksum_ctx;
4625
4626   svn_boolean_t checksum_finalized;
4627
4628   /* The stored checksum of the representation we are reading, its
4629      length, and the amount we've read so far.  Some of this
4630      information is redundant with rs_list and src_state, but it's
4631      convenient for the checksumming code to have it here. */
4632   svn_checksum_t *md5_checksum;
4633
4634   svn_filesize_t len;
4635   svn_filesize_t off;
4636
4637   /* The key for the fulltext cache for this rep, if there is a
4638      fulltext cache. */
4639   pair_cache_key_t fulltext_cache_key;
4640   /* The text we've been reading, if we're going to cache it. */
4641   svn_stringbuf_t *current_fulltext;
4642
4643   /* Used for temporary allocations during the read. */
4644   apr_pool_t *pool;
4645
4646   /* Pool used to store file handles and other data that is persistant
4647      for the entire stream read. */
4648   apr_pool_t *filehandle_pool;
4649 };
4650
4651 /* Combine the name of the rev file in RS with the given OFFSET to form
4652  * a cache lookup key.  Allocations will be made from POOL.  May return
4653  * NULL if the key cannot be constructed. */
4654 static const char*
4655 get_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool)
4656 {
4657   const char *name;
4658   const char *last_part;
4659   const char *name_last;
4660
4661   /* the rev file name containing the txdelta window.
4662    * If this fails we are in serious trouble anyways.
4663    * And if nobody else detects the problems, the file content checksum
4664    * comparison _will_ find them.
4665    */
4666   if (apr_file_name_get(&name, rs->file))
4667     return NULL;
4668
4669   /* Handle packed files as well by scanning backwards until we find the
4670    * revision or pack number. */
4671   name_last = name + strlen(name) - 1;
4672   while (! svn_ctype_isdigit(*name_last))
4673     --name_last;
4674
4675   last_part = name_last;
4676   while (svn_ctype_isdigit(*last_part))
4677     --last_part;
4678
4679   /* We must differentiate between packed files (as of today, the number
4680    * is being followed by a dot) and non-packed files (followed by \0).
4681    * Otherwise, there might be overlaps in the numbering range if the
4682    * repo gets packed after caching the txdeltas of non-packed revs.
4683    * => add the first non-digit char to the packed number. */
4684   if (name_last[1] != '\0')
4685     ++name_last;
4686
4687   /* copy one char MORE than the actual number to mark packed files,
4688    * i.e. packed revision file content uses different key space then
4689    * non-packed ones: keys for packed rev file content ends with a dot
4690    * for non-packed rev files they end with a digit. */
4691   name = apr_pstrndup(pool, last_part + 1, name_last - last_part);
4692   return svn_fs_fs__combine_number_and_string(offset, name, pool);
4693 }
4694
4695 /* Read the WINDOW_P for the rep state RS from the current FSFS session's
4696  * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4697  * cache has been given. If a cache is available IS_CACHED will inform
4698  * the caller about the success of the lookup. Allocations (of the window
4699  * in particualar) will be made from POOL.
4700  *
4701  * If the information could be found, put RS and the position within the
4702  * rev file into the same state as if the data had just been read from it.
4703  */
4704 static svn_error_t *
4705 get_cached_window(svn_txdelta_window_t **window_p,
4706                   struct rep_state *rs,
4707                   svn_boolean_t *is_cached,
4708                   apr_pool_t *pool)
4709 {
4710   if (! rs->window_cache)
4711     {
4712       /* txdelta window has not been enabled */
4713       *is_cached = FALSE;
4714     }
4715   else
4716     {
4717       /* ask the cache for the desired txdelta window */
4718       svn_fs_fs__txdelta_cached_window_t *cached_window;
4719       SVN_ERR(svn_cache__get((void **) &cached_window,
4720                              is_cached,
4721                              rs->window_cache,
4722                              get_window_key(rs, rs->off, pool),
4723                              pool));
4724
4725       if (*is_cached)
4726         {
4727           /* found it. Pass it back to the caller. */
4728           *window_p = cached_window->window;
4729
4730           /* manipulate the RS as if we just read the data */
4731           rs->chunk_index++;
4732           rs->off = cached_window->end_offset;
4733
4734           /* manipulate the rev file as if we just read from it */
4735           SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4736         }
4737     }
4738
4739   return SVN_NO_ERROR;
4740 }
4741
4742 /* Store the WINDOW read at OFFSET for the rep state RS in the current
4743  * FSFS session's cache. This will be a no-op if no cache has been given.
4744  * Temporary allocations will be made from SCRATCH_POOL. */
4745 static svn_error_t *
4746 set_cached_window(svn_txdelta_window_t *window,
4747                   struct rep_state *rs,
4748                   apr_off_t offset,
4749                   apr_pool_t *scratch_pool)
4750 {
4751   if (rs->window_cache)
4752     {
4753       /* store the window and the first offset _past_ it */
4754       svn_fs_fs__txdelta_cached_window_t cached_window;
4755
4756       cached_window.window = window;
4757       cached_window.end_offset = rs->off;
4758
4759       /* but key it with the start offset because that is the known state
4760        * when we will look it up */
4761       return svn_cache__set(rs->window_cache,
4762                             get_window_key(rs, offset, scratch_pool),
4763                             &cached_window,
4764                             scratch_pool);
4765     }
4766
4767   return SVN_NO_ERROR;
4768 }
4769
4770 /* Read the WINDOW_P for the rep state RS from the current FSFS session's
4771  * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
4772  * cache has been given. If a cache is available IS_CACHED will inform
4773  * the caller about the success of the lookup. Allocations (of the window
4774  * in particualar) will be made from POOL.
4775  */
4776 static svn_error_t *
4777 get_cached_combined_window(svn_stringbuf_t **window_p,
4778                            struct rep_state *rs,
4779                            svn_boolean_t *is_cached,
4780                            apr_pool_t *pool)
4781 {
4782   if (! rs->combined_cache)
4783     {
4784       /* txdelta window has not been enabled */
4785       *is_cached = FALSE;
4786     }
4787   else
4788     {
4789       /* ask the cache for the desired txdelta window */
4790       return svn_cache__get((void **)window_p,
4791                             is_cached,
4792                             rs->combined_cache,
4793                             get_window_key(rs, rs->start, pool),
4794                             pool);
4795     }
4796
4797   return SVN_NO_ERROR;
4798 }
4799
4800 /* Store the WINDOW read at OFFSET for the rep state RS in the current
4801  * FSFS session's cache. This will be a no-op if no cache has been given.
4802  * Temporary allocations will be made from SCRATCH_POOL. */
4803 static svn_error_t *
4804 set_cached_combined_window(svn_stringbuf_t *window,
4805                            struct rep_state *rs,
4806                            apr_off_t offset,
4807                            apr_pool_t *scratch_pool)
4808 {
4809   if (rs->combined_cache)
4810     {
4811       /* but key it with the start offset because that is the known state
4812        * when we will look it up */
4813       return svn_cache__set(rs->combined_cache,
4814                             get_window_key(rs, offset, scratch_pool),
4815                             window,
4816                             scratch_pool);
4817     }
4818
4819   return SVN_NO_ERROR;
4820 }
4821
4822 /* Build an array of rep_state structures in *LIST giving the delta
4823    reps from first_rep to a plain-text or self-compressed rep.  Set
4824    *SRC_STATE to the plain-text rep we find at the end of the chain,
4825    or to NULL if the final delta representation is self-compressed.
4826    The representation to start from is designated by filesystem FS, id
4827    ID, and representation REP.
4828    Also, set *WINDOW_P to the base window content for *LIST, if it
4829    could be found in cache. Otherwise, *LIST will contain the base
4830    representation for the whole delta chain.
4831    Finally, return the expanded size of the representation in
4832    *EXPANDED_SIZE. It will take care of cases where only the on-disk
4833    size is known.  */
4834 static svn_error_t *
4835 build_rep_list(apr_array_header_t **list,
4836                svn_stringbuf_t **window_p,
4837                struct rep_state **src_state,
4838                svn_filesize_t *expanded_size,
4839                svn_fs_t *fs,
4840                representation_t *first_rep,
4841                apr_pool_t *pool)
4842 {
4843   representation_t rep;
4844   struct rep_state *rs = NULL;
4845   struct rep_args *rep_args;
4846   svn_boolean_t is_cached = FALSE;
4847   apr_file_t *last_file = NULL;
4848   svn_revnum_t last_revision;
4849
4850   *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
4851   rep = *first_rep;
4852
4853   /* The value as stored in the data struct.
4854      0 is either for unknown length or actually zero length. */
4855   *expanded_size = first_rep->expanded_size;
4856
4857   /* for the top-level rep, we need the rep_args */
4858   SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4859                            &last_revision, &rep, fs, pool));
4860
4861   /* Unknown size or empty representation?
4862      That implies the this being the first iteration.
4863      Usually size equals on-disk size, except for empty,
4864      compressed representations (delta, size = 4).
4865      Please note that for all non-empty deltas have
4866      a 4-byte header _plus_ some data. */
4867   if (*expanded_size == 0)
4868     if (! rep_args->is_delta || first_rep->size != 4)
4869       *expanded_size = first_rep->size;
4870
4871   while (1)
4872     {
4873       /* fetch state, if that has not been done already */
4874       if (!rs)
4875         SVN_ERR(create_rep_state(&rs, &rep_args, &last_file,
4876                                 &last_revision, &rep, fs, pool));
4877
4878       SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
4879       if (is_cached)
4880         {
4881           /* We already have a reconstructed window in our cache.
4882              Write a pseudo rep_state with the full length. */
4883           rs->off = rs->start;
4884           rs->end = rs->start + (*window_p)->len;
4885           *src_state = rs;
4886           return SVN_NO_ERROR;
4887         }
4888
4889       if (!rep_args->is_delta)
4890         {
4891           /* This is a plaintext, so just return the current rep_state. */
4892           *src_state = rs;
4893           return SVN_NO_ERROR;
4894         }
4895
4896       /* Push this rep onto the list.  If it's self-compressed, we're done. */
4897       APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
4898       if (rep_args->is_delta_vs_empty)
4899         {
4900           *src_state = NULL;
4901           return SVN_NO_ERROR;
4902         }
4903
4904       rep.revision = rep_args->base_revision;
4905       rep.offset = rep_args->base_offset;
4906       rep.size = rep_args->base_length;
4907       rep.txn_id = NULL;
4908
4909       rs = NULL;
4910     }
4911 }
4912
4913
4914 /* Create a rep_read_baton structure for node revision NODEREV in
4915    filesystem FS and store it in *RB_P.  If FULLTEXT_CACHE_KEY is not
4916    NULL, it is the rep's key in the fulltext cache, and a stringbuf
4917    must be allocated to store the text.  Perform all allocations in
4918    POOL.  If rep is mutable, it must be for file contents. */
4919 static svn_error_t *
4920 rep_read_get_baton(struct rep_read_baton **rb_p,
4921                    svn_fs_t *fs,
4922                    representation_t *rep,
4923                    pair_cache_key_t fulltext_cache_key,
4924                    apr_pool_t *pool)
4925 {
4926   struct rep_read_baton *b;
4927
4928   b = apr_pcalloc(pool, sizeof(*b));
4929   b->fs = fs;
4930   b->base_window = NULL;
4931   b->chunk_index = 0;
4932   b->buf = NULL;
4933   b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
4934   b->checksum_finalized = FALSE;
4935   b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
4936   b->len = rep->expanded_size;
4937   b->off = 0;
4938   b->fulltext_cache_key = fulltext_cache_key;
4939   b->pool = svn_pool_create(pool);
4940   b->filehandle_pool = svn_pool_create(pool);
4941
4942   SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
4943                          &b->src_state, &b->len, fs, rep,
4944                          b->filehandle_pool));
4945
4946   if (SVN_IS_VALID_REVNUM(fulltext_cache_key.revision))
4947     b->current_fulltext = svn_stringbuf_create_ensure
4948                             ((apr_size_t)b->len,
4949                              b->filehandle_pool);
4950   else
4951     b->current_fulltext = NULL;
4952
4953   /* Save our output baton. */
4954   *rb_p = b;
4955
4956   return SVN_NO_ERROR;
4957 }
4958
4959 /* Skip forwards to THIS_CHUNK in REP_STATE and then read the next delta
4960    window into *NWIN. */
4961 static svn_error_t *
4962 read_delta_window(svn_txdelta_window_t **nwin, int this_chunk,
4963                   struct rep_state *rs, apr_pool_t *pool)
4964 {
4965   svn_stream_t *stream;
4966   svn_boolean_t is_cached;
4967   apr_off_t old_offset;
4968
4969   SVN_ERR_ASSERT(rs->chunk_index <= this_chunk);
4970
4971   /* RS->FILE may be shared between RS instances -> make sure we point
4972    * to the right data. */
4973   SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
4974
4975   /* Skip windows to reach the current chunk if we aren't there yet. */
4976   while (rs->chunk_index < this_chunk)
4977     {
4978       SVN_ERR(svn_txdelta_skip_svndiff_window(rs->file, rs->ver, pool));
4979       rs->chunk_index++;
4980       SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4981       if (rs->off >= rs->end)
4982         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
4983                                 _("Reading one svndiff window read "
4984                                   "beyond the end of the "
4985                                   "representation"));
4986     }
4987
4988   /* Read the next window. But first, try to find it in the cache. */
4989   SVN_ERR(get_cached_window(nwin, rs, &is_cached, pool));
4990   if (is_cached)
4991     return SVN_NO_ERROR;
4992
4993   /* Actually read the next window. */
4994   old_offset = rs->off;
4995   stream = svn_stream_from_aprfile2(rs->file, TRUE, pool);
4996   SVN_ERR(svn_txdelta_read_svndiff_window(nwin, stream, rs->ver, pool));
4997   rs->chunk_index++;
4998   SVN_ERR(get_file_offset(&rs->off, rs->file, pool));
4999
5000   if (rs->off > rs->end)
5001     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5002                             _("Reading one svndiff window read beyond "
5003                               "the end of the representation"));
5004
5005   /* the window has not been cached before, thus cache it now
5006    * (if caching is used for them at all) */
5007   return set_cached_window(*nwin, rs, old_offset, pool);
5008 }
5009
5010 /* Read SIZE bytes from the representation RS and return it in *NWIN. */
5011 static svn_error_t *
5012 read_plain_window(svn_stringbuf_t **nwin, struct rep_state *rs,
5013                   apr_size_t size, apr_pool_t *pool)
5014 {
5015   /* RS->FILE may be shared between RS instances -> make sure we point
5016    * to the right data. */
5017   SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
5018
5019   /* Read the plain data. */
5020   *nwin = svn_stringbuf_create_ensure(size, pool);
5021   SVN_ERR(svn_io_file_read_full2(rs->file, (*nwin)->data, size, NULL, NULL,
5022                                  pool));
5023   (*nwin)->data[size] = 0;
5024
5025   /* Update RS. */
5026   rs->off += (apr_off_t)size;
5027
5028   return SVN_NO_ERROR;
5029 }
5030
5031 /* Get the undeltified window that is a result of combining all deltas
5032    from the current desired representation identified in *RB with its
5033    base representation.  Store the window in *RESULT. */
5034 static svn_error_t *
5035 get_combined_window(svn_stringbuf_t **result,
5036                     struct rep_read_baton *rb)
5037 {
5038   apr_pool_t *pool, *new_pool, *window_pool;
5039   int i;
5040   svn_txdelta_window_t *window;
5041   apr_array_header_t *windows;
5042   svn_stringbuf_t *source, *buf = rb->base_window;
5043   struct rep_state *rs;
5044
5045   /* Read all windows that we need to combine. This is fine because
5046      the size of each window is relatively small (100kB) and skip-
5047      delta limits the number of deltas in a chain to well under 100.
5048      Stop early if one of them does not depend on its predecessors. */
5049   window_pool = svn_pool_create(rb->pool);
5050   windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
5051   for (i = 0; i < rb->rs_list->nelts; ++i)
5052     {
5053       rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5054       SVN_ERR(read_delta_window(&window, rb->chunk_index, rs, window_pool));
5055
5056       APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
5057       if (window->src_ops == 0)
5058         {
5059           ++i;
5060           break;
5061         }
5062     }
5063
5064   /* Combine in the windows from the other delta reps. */
5065   pool = svn_pool_create(rb->pool);
5066   for (--i; i >= 0; --i)
5067     {
5068
5069       rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
5070       window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
5071
5072       /* Maybe, we've got a PLAIN start representation.  If we do, read
5073          as much data from it as the needed for the txdelta window's source
5074          view.
5075          Note that BUF / SOURCE may only be NULL in the first iteration.
5076          Also note that we may have short-cut reading the delta chain --
5077          in which case SRC_OPS is 0 and it might not be a PLAIN rep. */
5078       source = buf;
5079       if (source == NULL && rb->src_state != NULL && window->src_ops)
5080         SVN_ERR(read_plain_window(&source, rb->src_state, window->sview_len,
5081                                   pool));
5082
5083       /* Combine this window with the current one. */
5084       new_pool = svn_pool_create(rb->pool);
5085       buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
5086       buf->len = window->tview_len;
5087
5088       svn_txdelta_apply_instructions(window, source ? source->data : NULL,
5089                                      buf->data, &buf->len);
5090       if (buf->len != window->tview_len)
5091         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
5092                                 _("svndiff window length is "
5093                                   "corrupt"));
5094
5095       /* Cache windows only if the whole rep content could be read as a
5096          single chunk.  Only then will no other chunk need a deeper RS
5097          list than the cached chunk. */
5098       if ((rb->chunk_index == 0) && (rs->off == rs->end))
5099         SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
5100
5101       /* Cycle pools so that we only need to hold three windows at a time. */
5102       svn_pool_destroy(pool);
5103       pool = new_pool;
5104     }
5105
5106   svn_pool_destroy(window_pool);
5107
5108   *result = buf;
5109   return SVN_NO_ERROR;
5110 }
5111
5112 /* Returns whether or not the expanded fulltext of the file is cachable
5113  * based on its size SIZE.  The decision depends on the cache used by RB.
5114  */
5115 static svn_boolean_t
5116 fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
5117 {
5118   return (size < APR_SIZE_MAX)
5119       && svn_cache__is_cachable(ffd->fulltext_cache, (apr_size_t)size);
5120 }
5121
5122 /* Close method used on streams returned by read_representation().
5123  */
5124 static svn_error_t *
5125 rep_read_contents_close(void *baton)
5126 {
5127   struct rep_read_baton *rb = baton;
5128
5129   svn_pool_destroy(rb->pool);
5130   svn_pool_destroy(rb->filehandle_pool);
5131
5132   return SVN_NO_ERROR;
5133 }
5134
5135 /* Return the next *LEN bytes of the rep and store them in *BUF. */
5136 static svn_error_t *
5137 get_contents(struct rep_read_baton *rb,
5138              char *buf,
5139              apr_size_t *len)
5140 {
5141   apr_size_t copy_len, remaining = *len;
5142   char *cur = buf;
5143   struct rep_state *rs;
5144
5145   /* Special case for when there are no delta reps, only a plain
5146      text. */
5147   if (rb->rs_list->nelts == 0)
5148     {
5149       copy_len = remaining;
5150       rs = rb->src_state;
5151
5152       if (rb->base_window != NULL)
5153         {
5154           /* We got the desired rep directly from the cache.
5155              This is where we need the pseudo rep_state created
5156              by build_rep_list(). */
5157           apr_size_t offset = (apr_size_t)(rs->off - rs->start);
5158           if (copy_len + offset > rb->base_window->len)
5159             copy_len = offset < rb->base_window->len
5160                      ? rb->base_window->len - offset
5161                      : 0ul;
5162
5163           memcpy (cur, rb->base_window->data + offset, copy_len);
5164         }
5165       else
5166         {
5167           if (((apr_off_t) copy_len) > rs->end - rs->off)
5168             copy_len = (apr_size_t) (rs->end - rs->off);
5169           SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
5170                                          NULL, rb->pool));
5171         }
5172
5173       rs->off += copy_len;
5174       *len = copy_len;
5175       return SVN_NO_ERROR;
5176     }
5177
5178   while (remaining > 0)
5179     {
5180       /* If we have buffered data from a previous chunk, use that. */
5181       if (rb->buf)
5182         {
5183           /* Determine how much to copy from the buffer. */
5184           copy_len = rb->buf_len - rb->buf_pos;
5185           if (copy_len > remaining)
5186             copy_len = remaining;
5187
5188           /* Actually copy the data. */
5189           memcpy(cur, rb->buf + rb->buf_pos, copy_len);
5190           rb->buf_pos += copy_len;
5191           cur += copy_len;
5192           remaining -= copy_len;
5193
5194           /* If the buffer is all used up, clear it and empty the
5195              local pool. */
5196           if (rb->buf_pos == rb->buf_len)
5197             {
5198               svn_pool_clear(rb->pool);
5199               rb->buf = NULL;
5200             }
5201         }
5202       else
5203         {
5204           svn_stringbuf_t *sbuf = NULL;
5205
5206           rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
5207           if (rs->off == rs->end)
5208             break;
5209
5210           /* Get more buffered data by evaluating a chunk. */
5211           SVN_ERR(get_combined_window(&sbuf, rb));
5212
5213           rb->chunk_index++;
5214           rb->buf_len = sbuf->len;
5215           rb->buf = sbuf->data;
5216           rb->buf_pos = 0;
5217         }
5218     }
5219
5220   *len = cur - buf;
5221
5222   return SVN_NO_ERROR;
5223 }
5224
5225 /* BATON is of type `rep_read_baton'; read the next *LEN bytes of the
5226    representation and store them in *BUF.  Sum as we read and verify
5227    the MD5 sum at the end. */
5228 static svn_error_t *
5229 rep_read_contents(void *baton,
5230                   char *buf,
5231                   apr_size_t *len)
5232 {
5233   struct rep_read_baton *rb = baton;
5234
5235   /* Get the next block of data. */
5236   SVN_ERR(get_contents(rb, buf, len));
5237
5238   if (rb->current_fulltext)
5239     svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
5240
5241   /* Perform checksumming.  We want to check the checksum as soon as
5242      the last byte of data is read, in case the caller never performs
5243      a short read, but we don't want to finalize the MD5 context
5244      twice. */
5245   if (!rb->checksum_finalized)
5246     {
5247       SVN_ERR(svn_checksum_update(rb->md5_checksum_ctx, buf, *len));
5248       rb->off += *len;
5249       if (rb->off == rb->len)
5250         {
5251           svn_checksum_t *md5_checksum;
5252
5253           rb->checksum_finalized = TRUE;
5254           SVN_ERR(svn_checksum_final(&md5_checksum, rb->md5_checksum_ctx,
5255                                      rb->pool));
5256           if (!svn_checksum_match(md5_checksum, rb->md5_checksum))
5257             return svn_error_create(SVN_ERR_FS_CORRUPT,
5258                     svn_checksum_mismatch_err(rb->md5_checksum, md5_checksum,
5259                         rb->pool,
5260                         _("Checksum mismatch while reading representation")),
5261                     NULL);
5262         }
5263     }
5264
5265   if (rb->off == rb->len && rb->current_fulltext)
5266     {
5267       fs_fs_data_t *ffd = rb->fs->fsap_data;
5268       SVN_ERR(svn_cache__set(ffd->fulltext_cache, &rb->fulltext_cache_key,
5269                              rb->current_fulltext, rb->pool));
5270       rb->current_fulltext = NULL;
5271     }
5272
5273   return SVN_NO_ERROR;
5274 }
5275
5276
5277 /* Return a stream in *CONTENTS_P that will read the contents of a
5278    representation stored at the location given by REP.  Appropriate
5279    for any kind of immutable representation, but only for file
5280    contents (not props or directory contents) in mutable
5281    representations.
5282
5283    If REP is NULL, the representation is assumed to be empty, and the
5284    empty stream is returned.
5285 */
5286 static svn_error_t *
5287 read_representation(svn_stream_t **contents_p,
5288                     svn_fs_t *fs,
5289                     representation_t *rep,
5290                     apr_pool_t *pool)
5291 {
5292   if (! rep)
5293     {
5294       *contents_p = svn_stream_empty(pool);
5295     }
5296   else
5297     {
5298       fs_fs_data_t *ffd = fs->fsap_data;
5299       pair_cache_key_t fulltext_cache_key = { 0 };
5300       svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
5301       struct rep_read_baton *rb;
5302
5303       fulltext_cache_key.revision = rep->revision;
5304       fulltext_cache_key.second = rep->offset;
5305       if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5306           && fulltext_size_is_cachable(ffd, len))
5307         {
5308           svn_stringbuf_t *fulltext;
5309           svn_boolean_t is_cached;
5310           SVN_ERR(svn_cache__get((void **) &fulltext, &is_cached,
5311                                  ffd->fulltext_cache, &fulltext_cache_key,
5312                                  pool));
5313           if (is_cached)
5314             {
5315               *contents_p = svn_stream_from_stringbuf(fulltext, pool);
5316               return SVN_NO_ERROR;
5317             }
5318         }
5319       else
5320         fulltext_cache_key.revision = SVN_INVALID_REVNUM;
5321
5322       SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
5323
5324       *contents_p = svn_stream_create(rb, pool);
5325       svn_stream_set_read(*contents_p, rep_read_contents);
5326       svn_stream_set_close(*contents_p, rep_read_contents_close);
5327     }
5328
5329   return SVN_NO_ERROR;
5330 }
5331
5332 svn_error_t *
5333 svn_fs_fs__get_contents(svn_stream_t **contents_p,
5334                         svn_fs_t *fs,
5335                         node_revision_t *noderev,
5336                         apr_pool_t *pool)
5337 {
5338   return read_representation(contents_p, fs, noderev->data_rep, pool);
5339 }
5340
5341 /* Baton used when reading delta windows. */
5342 struct delta_read_baton
5343 {
5344   struct rep_state *rs;
5345   svn_checksum_t *checksum;
5346 };
5347
5348 /* This implements the svn_txdelta_next_window_fn_t interface. */
5349 static svn_error_t *
5350 delta_read_next_window(svn_txdelta_window_t **window, void *baton,
5351                        apr_pool_t *pool)
5352 {
5353   struct delta_read_baton *drb = baton;
5354
5355   if (drb->rs->off == drb->rs->end)
5356     {
5357       *window = NULL;
5358       return SVN_NO_ERROR;
5359     }
5360
5361   return read_delta_window(window, drb->rs->chunk_index, drb->rs, pool);
5362 }
5363
5364 /* This implements the svn_txdelta_md5_digest_fn_t interface. */
5365 static const unsigned char *
5366 delta_read_md5_digest(void *baton)
5367 {
5368   struct delta_read_baton *drb = baton;
5369
5370   if (drb->checksum->kind == svn_checksum_md5)
5371     return drb->checksum->digest;
5372   else
5373     return NULL;
5374 }
5375
5376 svn_error_t *
5377 svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
5378                                  svn_fs_t *fs,
5379                                  node_revision_t *source,
5380                                  node_revision_t *target,
5381                                  apr_pool_t *pool)
5382 {
5383   svn_stream_t *source_stream, *target_stream;
5384
5385   /* Try a shortcut: if the target is stored as a delta against the source,
5386      then just use that delta. */
5387   if (source && source->data_rep && target->data_rep)
5388     {
5389       struct rep_state *rep_state;
5390       struct rep_args *rep_args;
5391
5392       /* Read target's base rep if any. */
5393       SVN_ERR(create_rep_state(&rep_state, &rep_args, NULL, NULL,
5394                                target->data_rep, fs, pool));
5395
5396       /* If that matches source, then use this delta as is.
5397          Note that we want an actual delta here.  E.g. a self-delta would
5398          not be good enough. */
5399       if (rep_args->is_delta
5400           && rep_args->base_revision == source->data_rep->revision
5401           && rep_args->base_offset == source->data_rep->offset)
5402         {
5403           /* Create the delta read baton. */
5404           struct delta_read_baton *drb = apr_pcalloc(pool, sizeof(*drb));
5405           drb->rs = rep_state;
5406           drb->checksum = svn_checksum_dup(target->data_rep->md5_checksum,
5407                                            pool);
5408           *stream_p = svn_txdelta_stream_create(drb, delta_read_next_window,
5409                                                 delta_read_md5_digest, pool);
5410           return SVN_NO_ERROR;
5411         }
5412       else
5413         SVN_ERR(svn_io_file_close(rep_state->file, pool));
5414     }
5415
5416   /* Read both fulltexts and construct a delta. */
5417   if (source)
5418     SVN_ERR(read_representation(&source_stream, fs, source->data_rep, pool));
5419   else
5420     source_stream = svn_stream_empty(pool);
5421   SVN_ERR(read_representation(&target_stream, fs, target->data_rep, pool));
5422
5423   /* Because source and target stream will already verify their content,
5424    * there is no need to do this once more.  In particular if the stream
5425    * content is being fetched from cache. */
5426   svn_txdelta2(stream_p, source_stream, target_stream, FALSE, pool);
5427
5428   return SVN_NO_ERROR;
5429 }
5430
5431 /* Baton for cache_access_wrapper. Wraps the original parameters of
5432  * svn_fs_fs__try_process_file_content().
5433  */
5434 typedef struct cache_access_wrapper_baton_t
5435 {
5436   svn_fs_process_contents_func_t func;
5437   void* baton;
5438 } cache_access_wrapper_baton_t;
5439
5440 /* Wrapper to translate between svn_fs_process_contents_func_t and
5441  * svn_cache__partial_getter_func_t.
5442  */
5443 static svn_error_t *
5444 cache_access_wrapper(void **out,
5445                      const void *data,
5446                      apr_size_t data_len,
5447                      void *baton,
5448                      apr_pool_t *pool)
5449 {
5450   cache_access_wrapper_baton_t *wrapper_baton = baton;
5451
5452   SVN_ERR(wrapper_baton->func((const unsigned char *)data,
5453                               data_len - 1, /* cache adds terminating 0 */
5454                               wrapper_baton->baton,
5455                               pool));
5456
5457   /* non-NULL value to signal the calling cache that all went well */
5458   *out = baton;
5459
5460   return SVN_NO_ERROR;
5461 }
5462
5463 svn_error_t *
5464 svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
5465                                      svn_fs_t *fs,
5466                                      node_revision_t *noderev,
5467                                      svn_fs_process_contents_func_t processor,
5468                                      void* baton,
5469                                      apr_pool_t *pool)
5470 {
5471   representation_t *rep = noderev->data_rep;
5472   if (rep)
5473     {
5474       fs_fs_data_t *ffd = fs->fsap_data;
5475       pair_cache_key_t fulltext_cache_key = { 0 };
5476
5477       fulltext_cache_key.revision = rep->revision;
5478       fulltext_cache_key.second = rep->offset;
5479       if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
5480           && fulltext_size_is_cachable(ffd, rep->expanded_size))
5481         {
5482           cache_access_wrapper_baton_t wrapper_baton;
5483           void *dummy = NULL;
5484
5485           wrapper_baton.func = processor;
5486           wrapper_baton.baton = baton;
5487           return svn_cache__get_partial(&dummy, success,
5488                                         ffd->fulltext_cache,
5489                                         &fulltext_cache_key,
5490                                         cache_access_wrapper,
5491                                         &wrapper_baton,
5492                                         pool);
5493         }
5494     }
5495
5496   *success = FALSE;
5497   return SVN_NO_ERROR;
5498 }
5499
5500 /* Fetch the contents of a directory into ENTRIES.  Values are stored
5501    as filename to string mappings; further conversion is necessary to
5502    convert them into svn_fs_dirent_t values. */
5503 static svn_error_t *
5504 get_dir_contents(apr_hash_t *entries,
5505                  svn_fs_t *fs,
5506                  node_revision_t *noderev,
5507                  apr_pool_t *pool)
5508 {
5509   svn_stream_t *contents;
5510
5511   if (noderev->data_rep && noderev->data_rep->txn_id)
5512     {
5513       const char *filename = path_txn_node_children(fs, noderev->id, pool);
5514
5515       /* The representation is mutable.  Read the old directory
5516          contents from the mutable children file, followed by the
5517          changes we've made in this transaction. */
5518       SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
5519       SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5520       SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
5521       SVN_ERR(svn_stream_close(contents));
5522     }
5523   else if (noderev->data_rep)
5524     {
5525       /* use a temporary pool for temp objects.
5526        * Also undeltify content before parsing it. Otherwise, we could only
5527        * parse it byte-by-byte.
5528        */
5529       apr_pool_t *text_pool = svn_pool_create(pool);
5530       apr_size_t len = noderev->data_rep->expanded_size
5531                      ? (apr_size_t)noderev->data_rep->expanded_size
5532                      : (apr_size_t)noderev->data_rep->size;
5533       svn_stringbuf_t *text = svn_stringbuf_create_ensure(len, text_pool);
5534       text->len = len;
5535
5536       /* The representation is immutable.  Read it normally. */
5537       SVN_ERR(read_representation(&contents, fs, noderev->data_rep, text_pool));
5538       SVN_ERR(svn_stream_read(contents, text->data, &text->len));
5539       SVN_ERR(svn_stream_close(contents));
5540
5541       /* de-serialize hash */
5542       contents = svn_stream_from_stringbuf(text, text_pool);
5543       SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
5544
5545       svn_pool_destroy(text_pool);
5546     }
5547
5548   return SVN_NO_ERROR;
5549 }
5550
5551
5552 static const char *
5553 unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
5554                   apr_pool_t *pool)
5555 {
5556   return apr_psprintf(pool, "%s %s",
5557                       (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
5558                       svn_fs_fs__id_unparse(id, pool)->data);
5559 }
5560
5561 /* Given a hash ENTRIES of dirent structions, return a hash in
5562    *STR_ENTRIES_P, that has svn_string_t as the values in the format
5563    specified by the fs_fs directory contents file.  Perform
5564    allocations in POOL. */
5565 static svn_error_t *
5566 unparse_dir_entries(apr_hash_t **str_entries_p,
5567                     apr_hash_t *entries,
5568                     apr_pool_t *pool)
5569 {
5570   apr_hash_index_t *hi;
5571
5572   /* For now, we use a our own hash function to ensure that we get a
5573    * (largely) stable order when serializing the data.  It also gives
5574    * us some performance improvement.
5575    *
5576    * ### TODO ###
5577    * Use some sorted or other fixed order data container.
5578    */
5579   *str_entries_p = svn_hash__make(pool);
5580
5581   for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
5582     {
5583       const void *key;
5584       apr_ssize_t klen;
5585       svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
5586       const char *new_val;
5587
5588       apr_hash_this(hi, &key, &klen, NULL);
5589       new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
5590       apr_hash_set(*str_entries_p, key, klen,
5591                    svn_string_create(new_val, pool));
5592     }
5593
5594   return SVN_NO_ERROR;
5595 }
5596
5597
5598 /* Given a hash STR_ENTRIES with values as svn_string_t as specified
5599    in an FSFS directory contents listing, return a hash of dirents in
5600    *ENTRIES_P.  Perform allocations in POOL. */
5601 static svn_error_t *
5602 parse_dir_entries(apr_hash_t **entries_p,
5603                   apr_hash_t *str_entries,
5604                   const char *unparsed_id,
5605                   apr_pool_t *pool)
5606 {
5607   apr_hash_index_t *hi;
5608
5609   *entries_p = apr_hash_make(pool);
5610
5611   /* Translate the string dir entries into real entries. */
5612   for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
5613     {
5614       const char *name = svn__apr_hash_index_key(hi);
5615       svn_string_t *str_val = svn__apr_hash_index_val(hi);
5616       char *str, *last_str;
5617       svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
5618
5619       last_str = apr_pstrdup(pool, str_val->data);
5620       dirent->name = apr_pstrdup(pool, name);
5621
5622       str = svn_cstring_tokenize(" ", &last_str);
5623       if (str == NULL)
5624         return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5625                                  _("Directory entry corrupt in '%s'"),
5626                                  unparsed_id);
5627
5628       if (strcmp(str, KIND_FILE) == 0)
5629         {
5630           dirent->kind = svn_node_file;
5631         }
5632       else if (strcmp(str, KIND_DIR) == 0)
5633         {
5634           dirent->kind = svn_node_dir;
5635         }
5636       else
5637         {
5638           return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5639                                    _("Directory entry corrupt in '%s'"),
5640                                    unparsed_id);
5641         }
5642
5643       str = svn_cstring_tokenize(" ", &last_str);
5644       if (str == NULL)
5645           return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
5646                                    _("Directory entry corrupt in '%s'"),
5647                                    unparsed_id);
5648
5649       dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
5650
5651       svn_hash_sets(*entries_p, dirent->name, dirent);
5652     }
5653
5654   return SVN_NO_ERROR;
5655 }
5656
5657 /* Return the cache object in FS responsible to storing the directory
5658  * the NODEREV. If none exists, return NULL. */
5659 static svn_cache__t *
5660 locate_dir_cache(svn_fs_t *fs,
5661                  node_revision_t *noderev)
5662 {
5663   fs_fs_data_t *ffd = fs->fsap_data;
5664   return svn_fs_fs__id_txn_id(noderev->id)
5665       ? ffd->txn_dir_cache
5666       : ffd->dir_cache;
5667 }
5668
5669 svn_error_t *
5670 svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
5671                             svn_fs_t *fs,
5672                             node_revision_t *noderev,
5673                             apr_pool_t *pool)
5674 {
5675   const char *unparsed_id = NULL;
5676   apr_hash_t *unparsed_entries, *parsed_entries;
5677
5678   /* find the cache we may use */
5679   svn_cache__t *cache = locate_dir_cache(fs, noderev);
5680   if (cache)
5681     {
5682       svn_boolean_t found;
5683
5684       unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
5685       SVN_ERR(svn_cache__get((void **) entries_p, &found, cache,
5686                              unparsed_id, pool));
5687       if (found)
5688         return SVN_NO_ERROR;
5689     }
5690
5691   /* Read in the directory hash. */
5692   unparsed_entries = apr_hash_make(pool);
5693   SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
5694   SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
5695                             unparsed_id, pool));
5696
5697   /* Update the cache, if we are to use one. */
5698   if (cache)
5699     SVN_ERR(svn_cache__set(cache, unparsed_id, parsed_entries, pool));
5700
5701   *entries_p = parsed_entries;
5702   return SVN_NO_ERROR;
5703 }
5704
5705 svn_error_t *
5706 svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
5707                                   svn_fs_t *fs,
5708                                   node_revision_t *noderev,
5709                                   const char *name,
5710                                   apr_pool_t *result_pool,
5711                                   apr_pool_t *scratch_pool)
5712 {
5713   svn_boolean_t found = FALSE;
5714
5715   /* find the cache we may use */
5716   svn_cache__t *cache = locate_dir_cache(fs, noderev);
5717   if (cache)
5718     {
5719       const char *unparsed_id =
5720         svn_fs_fs__id_unparse(noderev->id, scratch_pool)->data;
5721
5722       /* Cache lookup. */
5723       SVN_ERR(svn_cache__get_partial((void **)dirent,
5724                                      &found,
5725                                      cache,
5726                                      unparsed_id,
5727                                      svn_fs_fs__extract_dir_entry,
5728                                      (void*)name,
5729                                      result_pool));
5730     }
5731
5732   /* fetch data from disk if we did not find it in the cache */
5733   if (! found)
5734     {
5735       apr_hash_t *entries;
5736       svn_fs_dirent_t *entry;
5737       svn_fs_dirent_t *entry_copy = NULL;
5738
5739       /* read the dir from the file system. It will probably be put it
5740          into the cache for faster lookup in future calls. */
5741       SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
5742                                           scratch_pool));
5743
5744       /* find desired entry and return a copy in POOL, if found */
5745       entry = svn_hash_gets(entries, name);
5746       if (entry != NULL)
5747         {
5748           entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
5749           entry_copy->name = apr_pstrdup(result_pool, entry->name);
5750           entry_copy->id = svn_fs_fs__id_copy(entry->id, result_pool);
5751           entry_copy->kind = entry->kind;
5752         }
5753
5754       *dirent = entry_copy;
5755     }
5756
5757   return SVN_NO_ERROR;
5758 }
5759
5760 svn_error_t *
5761 svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
5762                         svn_fs_t *fs,
5763                         node_revision_t *noderev,
5764                         apr_pool_t *pool)
5765 {
5766   apr_hash_t *proplist;
5767   svn_stream_t *stream;
5768
5769   if (noderev->prop_rep && noderev->prop_rep->txn_id)
5770     {
5771       const char *filename = path_txn_node_props(fs, noderev->id, pool);
5772       proplist = apr_hash_make(pool);
5773
5774       SVN_ERR(svn_stream_open_readonly(&stream, filename, pool, pool));
5775       SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5776       SVN_ERR(svn_stream_close(stream));
5777     }
5778   else if (noderev->prop_rep)
5779     {
5780       fs_fs_data_t *ffd = fs->fsap_data;
5781       representation_t *rep = noderev->prop_rep;
5782       pair_cache_key_t key = { 0 };
5783
5784       key.revision = rep->revision;
5785       key.second = rep->offset;
5786       if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5787         {
5788           svn_boolean_t is_cached;
5789           SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
5790                                  ffd->properties_cache, &key, pool));
5791           if (is_cached)
5792             return SVN_NO_ERROR;
5793         }
5794
5795       proplist = apr_hash_make(pool);
5796       SVN_ERR(read_representation(&stream, fs, noderev->prop_rep, pool));
5797       SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
5798       SVN_ERR(svn_stream_close(stream));
5799
5800       if (ffd->properties_cache && SVN_IS_VALID_REVNUM(rep->revision))
5801         SVN_ERR(svn_cache__set(ffd->properties_cache, &key, proplist, pool));
5802     }
5803   else
5804     {
5805       /* return an empty prop list if the node doesn't have any props */
5806       proplist = apr_hash_make(pool);
5807     }
5808
5809   *proplist_p = proplist;
5810
5811   return SVN_NO_ERROR;
5812 }
5813
5814 svn_error_t *
5815 svn_fs_fs__file_length(svn_filesize_t *length,
5816                        node_revision_t *noderev,
5817                        apr_pool_t *pool)
5818 {
5819   if (noderev->data_rep)
5820     *length = noderev->data_rep->expanded_size;
5821   else
5822     *length = 0;
5823
5824   return SVN_NO_ERROR;
5825 }
5826
5827 svn_boolean_t
5828 svn_fs_fs__noderev_same_rep_key(representation_t *a,
5829                                 representation_t *b)
5830 {
5831   if (a == b)
5832     return TRUE;
5833
5834   if (a == NULL || b == NULL)
5835     return FALSE;
5836
5837   if (a->offset != b->offset)
5838     return FALSE;
5839
5840   if (a->revision != b->revision)
5841     return FALSE;
5842
5843   if (a->uniquifier == b->uniquifier)
5844     return TRUE;
5845
5846   if (a->uniquifier == NULL || b->uniquifier == NULL)
5847     return FALSE;
5848
5849   return strcmp(a->uniquifier, b->uniquifier) == 0;
5850 }
5851
5852 svn_error_t *
5853 svn_fs_fs__file_checksum(svn_checksum_t **checksum,
5854                          node_revision_t *noderev,
5855                          svn_checksum_kind_t kind,
5856                          apr_pool_t *pool)
5857 {
5858   if (noderev->data_rep)
5859     {
5860       switch(kind)
5861         {
5862           case svn_checksum_md5:
5863             *checksum = svn_checksum_dup(noderev->data_rep->md5_checksum,
5864                                          pool);
5865             break;
5866           case svn_checksum_sha1:
5867             *checksum = svn_checksum_dup(noderev->data_rep->sha1_checksum,
5868                                          pool);
5869             break;
5870           default:
5871             *checksum = NULL;
5872         }
5873     }
5874   else
5875     *checksum = NULL;
5876
5877   return SVN_NO_ERROR;
5878 }
5879
5880 representation_t *
5881 svn_fs_fs__rep_copy(representation_t *rep,
5882                     apr_pool_t *pool)
5883 {
5884   representation_t *rep_new;
5885
5886   if (rep == NULL)
5887     return NULL;
5888
5889   rep_new = apr_pcalloc(pool, sizeof(*rep_new));
5890
5891   memcpy(rep_new, rep, sizeof(*rep_new));
5892   rep_new->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
5893   rep_new->sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
5894   rep_new->uniquifier = apr_pstrdup(pool, rep->uniquifier);
5895
5896   return rep_new;
5897 }
5898
5899 /* Merge the internal-use-only CHANGE into a hash of public-FS
5900    svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
5901    single summarical (is that real word?) change per path.  Also keep
5902    the COPYFROM_CACHE up to date with new adds and replaces.  */
5903 static svn_error_t *
5904 fold_change(apr_hash_t *changes,
5905             const change_t *change,
5906             apr_hash_t *copyfrom_cache)
5907 {
5908   apr_pool_t *pool = apr_hash_pool_get(changes);
5909   svn_fs_path_change2_t *old_change, *new_change;
5910   const char *path;
5911   apr_size_t path_len = strlen(change->path);
5912
5913   if ((old_change = apr_hash_get(changes, change->path, path_len)))
5914     {
5915       /* This path already exists in the hash, so we have to merge
5916          this change into the already existing one. */
5917
5918       /* Sanity check:  only allow NULL node revision ID in the
5919          `reset' case. */
5920       if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
5921         return svn_error_create
5922           (SVN_ERR_FS_CORRUPT, NULL,
5923            _("Missing required node revision ID"));
5924
5925       /* Sanity check: we should be talking about the same node
5926          revision ID as our last change except where the last change
5927          was a deletion. */
5928       if (change->noderev_id
5929           && (! svn_fs_fs__id_eq(old_change->node_rev_id, change->noderev_id))
5930           && (old_change->change_kind != svn_fs_path_change_delete))
5931         return svn_error_create
5932           (SVN_ERR_FS_CORRUPT, NULL,
5933            _("Invalid change ordering: new node revision ID "
5934              "without delete"));
5935
5936       /* Sanity check: an add, replacement, or reset must be the first
5937          thing to follow a deletion. */
5938       if ((old_change->change_kind == svn_fs_path_change_delete)
5939           && (! ((change->kind == svn_fs_path_change_replace)
5940                  || (change->kind == svn_fs_path_change_reset)
5941                  || (change->kind == svn_fs_path_change_add))))
5942         return svn_error_create
5943           (SVN_ERR_FS_CORRUPT, NULL,
5944            _("Invalid change ordering: non-add change on deleted path"));
5945
5946       /* Sanity check: an add can't follow anything except
5947          a delete or reset.  */
5948       if ((change->kind == svn_fs_path_change_add)
5949           && (old_change->change_kind != svn_fs_path_change_delete)
5950           && (old_change->change_kind != svn_fs_path_change_reset))
5951         return svn_error_create
5952           (SVN_ERR_FS_CORRUPT, NULL,
5953            _("Invalid change ordering: add change on preexisting path"));
5954
5955       /* Now, merge that change in. */
5956       switch (change->kind)
5957         {
5958         case svn_fs_path_change_reset:
5959           /* A reset here will simply remove the path change from the
5960              hash. */
5961           old_change = NULL;
5962           break;
5963
5964         case svn_fs_path_change_delete:
5965           if (old_change->change_kind == svn_fs_path_change_add)
5966             {
5967               /* If the path was introduced in this transaction via an
5968                  add, and we are deleting it, just remove the path
5969                  altogether. */
5970               old_change = NULL;
5971             }
5972           else
5973             {
5974               /* A deletion overrules all previous changes. */
5975               old_change->change_kind = svn_fs_path_change_delete;
5976               old_change->text_mod = change->text_mod;
5977               old_change->prop_mod = change->prop_mod;
5978               old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5979               old_change->copyfrom_path = NULL;
5980             }
5981           break;
5982
5983         case svn_fs_path_change_add:
5984         case svn_fs_path_change_replace:
5985           /* An add at this point must be following a previous delete,
5986              so treat it just like a replace. */
5987           old_change->change_kind = svn_fs_path_change_replace;
5988           old_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id,
5989                                                        pool);
5990           old_change->text_mod = change->text_mod;
5991           old_change->prop_mod = change->prop_mod;
5992           if (change->copyfrom_rev == SVN_INVALID_REVNUM)
5993             {
5994               old_change->copyfrom_rev = SVN_INVALID_REVNUM;
5995               old_change->copyfrom_path = NULL;
5996             }
5997           else
5998             {
5999               old_change->copyfrom_rev = change->copyfrom_rev;
6000               old_change->copyfrom_path = apr_pstrdup(pool,
6001                                                       change->copyfrom_path);
6002             }
6003           break;
6004
6005         case svn_fs_path_change_modify:
6006         default:
6007           if (change->text_mod)
6008             old_change->text_mod = TRUE;
6009           if (change->prop_mod)
6010             old_change->prop_mod = TRUE;
6011           break;
6012         }
6013
6014       /* Point our new_change to our (possibly modified) old_change. */
6015       new_change = old_change;
6016     }
6017   else
6018     {
6019       /* This change is new to the hash, so make a new public change
6020          structure from the internal one (in the hash's pool), and dup
6021          the path into the hash's pool, too. */
6022       new_change = apr_pcalloc(pool, sizeof(*new_change));
6023       new_change->node_rev_id = svn_fs_fs__id_copy(change->noderev_id, pool);
6024       new_change->change_kind = change->kind;
6025       new_change->text_mod = change->text_mod;
6026       new_change->prop_mod = change->prop_mod;
6027       /* In FSFS, copyfrom_known is *always* true, since we've always
6028        * stored copyfroms in changed paths lists. */
6029       new_change->copyfrom_known = TRUE;
6030       if (change->copyfrom_rev != SVN_INVALID_REVNUM)
6031         {
6032           new_change->copyfrom_rev = change->copyfrom_rev;
6033           new_change->copyfrom_path = apr_pstrdup(pool, change->copyfrom_path);
6034         }
6035       else
6036         {
6037           new_change->copyfrom_rev = SVN_INVALID_REVNUM;
6038           new_change->copyfrom_path = NULL;
6039         }
6040     }
6041
6042   if (new_change)
6043     new_change->node_kind = change->node_kind;
6044
6045   /* Add (or update) this path.
6046
6047      Note: this key might already be present, and it would be nice to
6048      re-use its value, but there is no way to fetch it. The API makes no
6049      guarantees that this (new) key will not be retained. Thus, we (again)
6050      copy the key into the target pool to ensure a proper lifetime.  */
6051   path = apr_pstrmemdup(pool, change->path, path_len);
6052   apr_hash_set(changes, path, path_len, new_change);
6053
6054   /* Update the copyfrom cache, if any. */
6055   if (copyfrom_cache)
6056     {
6057       apr_pool_t *copyfrom_pool = apr_hash_pool_get(copyfrom_cache);
6058       const char *copyfrom_string = NULL, *copyfrom_key = path;
6059       if (new_change)
6060         {
6061           if (SVN_IS_VALID_REVNUM(new_change->copyfrom_rev))
6062             copyfrom_string = apr_psprintf(copyfrom_pool, "%ld %s",
6063                                            new_change->copyfrom_rev,
6064                                            new_change->copyfrom_path);
6065           else
6066             copyfrom_string = "";
6067         }
6068       /* We need to allocate a copy of the key in the copyfrom_pool if
6069        * we're not doing a deletion and if it isn't already there. */
6070       if (   copyfrom_string
6071           && (   ! apr_hash_count(copyfrom_cache)
6072               || ! apr_hash_get(copyfrom_cache, copyfrom_key, path_len)))
6073         copyfrom_key = apr_pstrmemdup(copyfrom_pool, copyfrom_key, path_len);
6074
6075       apr_hash_set(copyfrom_cache, copyfrom_key, path_len,
6076                    copyfrom_string);
6077     }
6078
6079   return SVN_NO_ERROR;
6080 }
6081
6082 /* The 256 is an arbitrary size large enough to hold the node id and the
6083  * various flags. */
6084 #define MAX_CHANGE_LINE_LEN FSFS_MAX_PATH_LEN + 256
6085
6086 /* Read the next entry in the changes record from file FILE and store
6087    the resulting change in *CHANGE_P.  If there is no next record,
6088    store NULL there.  Perform all allocations from POOL. */
6089 static svn_error_t *
6090 read_change(change_t **change_p,
6091             apr_file_t *file,
6092             apr_pool_t *pool)
6093 {
6094   char buf[MAX_CHANGE_LINE_LEN];
6095   apr_size_t len = sizeof(buf);
6096   change_t *change;
6097   char *str, *last_str = buf, *kind_str;
6098   svn_error_t *err;
6099
6100   /* Default return value. */
6101   *change_p = NULL;
6102
6103   err = svn_io_read_length_line(file, buf, &len, pool);
6104
6105   /* Check for a blank line. */
6106   if (err || (len == 0))
6107     {
6108       if (err && APR_STATUS_IS_EOF(err->apr_err))
6109         {
6110           svn_error_clear(err);
6111           return SVN_NO_ERROR;
6112         }
6113       if ((len == 0) && (! err))
6114         return SVN_NO_ERROR;
6115       return svn_error_trace(err);
6116     }
6117
6118   change = apr_pcalloc(pool, sizeof(*change));
6119
6120   /* Get the node-id of the change. */
6121   str = svn_cstring_tokenize(" ", &last_str);
6122   if (str == NULL)
6123     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6124                             _("Invalid changes line in rev-file"));
6125
6126   change->noderev_id = svn_fs_fs__id_parse(str, strlen(str), pool);
6127   if (change->noderev_id == NULL)
6128     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6129                             _("Invalid changes line in rev-file"));
6130
6131   /* Get the change type. */
6132   str = svn_cstring_tokenize(" ", &last_str);
6133   if (str == NULL)
6134     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6135                             _("Invalid changes line in rev-file"));
6136
6137   /* Don't bother to check the format number before looking for
6138    * node-kinds: just read them if you find them. */
6139   change->node_kind = svn_node_unknown;
6140   kind_str = strchr(str, '-');
6141   if (kind_str)
6142     {
6143       /* Cap off the end of "str" (the action). */
6144       *kind_str = '\0';
6145       kind_str++;
6146       if (strcmp(kind_str, KIND_FILE) == 0)
6147         change->node_kind = svn_node_file;
6148       else if (strcmp(kind_str, KIND_DIR) == 0)
6149         change->node_kind = svn_node_dir;
6150       else
6151         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6152                                 _("Invalid changes line in rev-file"));
6153     }
6154
6155   if (strcmp(str, ACTION_MODIFY) == 0)
6156     {
6157       change->kind = svn_fs_path_change_modify;
6158     }
6159   else if (strcmp(str, ACTION_ADD) == 0)
6160     {
6161       change->kind = svn_fs_path_change_add;
6162     }
6163   else if (strcmp(str, ACTION_DELETE) == 0)
6164     {
6165       change->kind = svn_fs_path_change_delete;
6166     }
6167   else if (strcmp(str, ACTION_REPLACE) == 0)
6168     {
6169       change->kind = svn_fs_path_change_replace;
6170     }
6171   else if (strcmp(str, ACTION_RESET) == 0)
6172     {
6173       change->kind = svn_fs_path_change_reset;
6174     }
6175   else
6176     {
6177       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6178                               _("Invalid change kind in rev file"));
6179     }
6180
6181   /* Get the text-mod flag. */
6182   str = svn_cstring_tokenize(" ", &last_str);
6183   if (str == NULL)
6184     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6185                             _("Invalid changes line in rev-file"));
6186
6187   if (strcmp(str, FLAG_TRUE) == 0)
6188     {
6189       change->text_mod = TRUE;
6190     }
6191   else if (strcmp(str, FLAG_FALSE) == 0)
6192     {
6193       change->text_mod = FALSE;
6194     }
6195   else
6196     {
6197       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6198                               _("Invalid text-mod flag in rev-file"));
6199     }
6200
6201   /* Get the prop-mod flag. */
6202   str = svn_cstring_tokenize(" ", &last_str);
6203   if (str == NULL)
6204     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6205                             _("Invalid changes line in rev-file"));
6206
6207   if (strcmp(str, FLAG_TRUE) == 0)
6208     {
6209       change->prop_mod = TRUE;
6210     }
6211   else if (strcmp(str, FLAG_FALSE) == 0)
6212     {
6213       change->prop_mod = FALSE;
6214     }
6215   else
6216     {
6217       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6218                               _("Invalid prop-mod flag in rev-file"));
6219     }
6220
6221   /* Get the changed path. */
6222   change->path = apr_pstrdup(pool, last_str);
6223
6224
6225   /* Read the next line, the copyfrom line. */
6226   len = sizeof(buf);
6227   SVN_ERR(svn_io_read_length_line(file, buf, &len, pool));
6228
6229   if (len == 0)
6230     {
6231       change->copyfrom_rev = SVN_INVALID_REVNUM;
6232       change->copyfrom_path = NULL;
6233     }
6234   else
6235     {
6236       last_str = buf;
6237       str = svn_cstring_tokenize(" ", &last_str);
6238       if (! str)
6239         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6240                                 _("Invalid changes line in rev-file"));
6241       change->copyfrom_rev = SVN_STR_TO_REV(str);
6242
6243       if (! last_str)
6244         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6245                                 _("Invalid changes line in rev-file"));
6246
6247       change->copyfrom_path = apr_pstrdup(pool, last_str);
6248     }
6249
6250   *change_p = change;
6251
6252   return SVN_NO_ERROR;
6253 }
6254
6255 /* Examine all the changed path entries in CHANGES and store them in
6256    *CHANGED_PATHS.  Folding is done to remove redundant or unnecessary
6257    *data.  Store a hash of paths to copyfrom "REV PATH" strings in
6258    COPYFROM_HASH if it is non-NULL.  If PREFOLDED is true, assume that
6259    the changed-path entries have already been folded (by
6260    write_final_changed_path_info) and may be out of order, so we shouldn't
6261    remove children of replaced or deleted directories.  Do all
6262    allocations in POOL. */
6263 static svn_error_t *
6264 process_changes(apr_hash_t *changed_paths,
6265                 apr_hash_t *copyfrom_cache,
6266                 apr_array_header_t *changes,
6267                 svn_boolean_t prefolded,
6268                 apr_pool_t *pool)
6269 {
6270   apr_pool_t *iterpool = svn_pool_create(pool);
6271   int i;
6272
6273   /* Read in the changes one by one, folding them into our local hash
6274      as necessary. */
6275
6276   for (i = 0; i < changes->nelts; ++i)
6277     {
6278       change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
6279
6280       SVN_ERR(fold_change(changed_paths, change, copyfrom_cache));
6281
6282       /* Now, if our change was a deletion or replacement, we have to
6283          blow away any changes thus far on paths that are (or, were)
6284          children of this path.
6285          ### i won't bother with another iteration pool here -- at
6286          most we talking about a few extra dups of paths into what
6287          is already a temporary subpool.
6288       */
6289
6290       if (((change->kind == svn_fs_path_change_delete)
6291            || (change->kind == svn_fs_path_change_replace))
6292           && ! prefolded)
6293         {
6294           apr_hash_index_t *hi;
6295
6296           /* a potential child path must contain at least 2 more chars
6297              (the path separator plus at least one char for the name).
6298              Also, we should not assume that all paths have been normalized
6299              i.e. some might have trailing path separators.
6300           */
6301           apr_ssize_t change_path_len = strlen(change->path);
6302           apr_ssize_t min_child_len = change_path_len == 0
6303                                     ? 1
6304                                     : change->path[change_path_len-1] == '/'
6305                                         ? change_path_len + 1
6306                                         : change_path_len + 2;
6307
6308           /* CAUTION: This is the inner loop of an O(n^2) algorithm.
6309              The number of changes to process may be >> 1000.
6310              Therefore, keep the inner loop as tight as possible.
6311           */
6312           for (hi = apr_hash_first(iterpool, changed_paths);
6313                hi;
6314                hi = apr_hash_next(hi))
6315             {
6316               /* KEY is the path. */
6317               const void *path;
6318               apr_ssize_t klen;
6319               apr_hash_this(hi, &path, &klen, NULL);
6320
6321               /* If we come across a child of our path, remove it.
6322                  Call svn_dirent_is_child only if there is a chance that
6323                  this is actually a sub-path.
6324                */
6325               if (   klen >= min_child_len
6326                   && svn_dirent_is_child(change->path, path, iterpool))
6327                 apr_hash_set(changed_paths, path, klen, NULL);
6328             }
6329         }
6330
6331       /* Clear the per-iteration subpool. */
6332       svn_pool_clear(iterpool);
6333     }
6334
6335   /* Destroy the per-iteration subpool. */
6336   svn_pool_destroy(iterpool);
6337
6338   return SVN_NO_ERROR;
6339 }
6340
6341 /* Fetch all the changes from FILE and store them in *CHANGES.  Do all
6342    allocations in POOL. */
6343 static svn_error_t *
6344 read_all_changes(apr_array_header_t **changes,
6345                  apr_file_t *file,
6346                  apr_pool_t *pool)
6347 {
6348   change_t *change;
6349
6350   /* pre-allocate enough room for most change lists
6351      (will be auto-expanded as necessary) */
6352   *changes = apr_array_make(pool, 30, sizeof(change_t *));
6353
6354   SVN_ERR(read_change(&change, file, pool));
6355   while (change)
6356     {
6357       APR_ARRAY_PUSH(*changes, change_t*) = change;
6358       SVN_ERR(read_change(&change, file, pool));
6359     }
6360
6361   return SVN_NO_ERROR;
6362 }
6363
6364 svn_error_t *
6365 svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
6366                              svn_fs_t *fs,
6367                              const char *txn_id,
6368                              apr_pool_t *pool)
6369 {
6370   apr_file_t *file;
6371   apr_hash_t *changed_paths = apr_hash_make(pool);
6372   apr_array_header_t *changes;
6373   apr_pool_t *scratch_pool = svn_pool_create(pool);
6374
6375   SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
6376                            APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6377
6378   SVN_ERR(read_all_changes(&changes, file, scratch_pool));
6379   SVN_ERR(process_changes(changed_paths, NULL, changes, FALSE, pool));
6380   svn_pool_destroy(scratch_pool);
6381
6382   SVN_ERR(svn_io_file_close(file, pool));
6383
6384   *changed_paths_p = changed_paths;
6385
6386   return SVN_NO_ERROR;
6387 }
6388
6389 /* Fetch the list of change in revision REV in FS and return it in *CHANGES.
6390  * Allocate the result in POOL.
6391  */
6392 static svn_error_t *
6393 get_changes(apr_array_header_t **changes,
6394             svn_fs_t *fs,
6395             svn_revnum_t rev,
6396             apr_pool_t *pool)
6397 {
6398   apr_off_t changes_offset;
6399   apr_file_t *revision_file;
6400   svn_boolean_t found;
6401   fs_fs_data_t *ffd = fs->fsap_data;
6402
6403   /* try cache lookup first */
6404
6405   if (ffd->changes_cache)
6406     {
6407       SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
6408                              &rev, pool));
6409       if (found)
6410         return SVN_NO_ERROR;
6411     }
6412
6413   /* read changes from revision file */
6414
6415   SVN_ERR(ensure_revision_exists(fs, rev, pool));
6416
6417   SVN_ERR(open_pack_or_rev_file(&revision_file, fs, rev, pool));
6418
6419   SVN_ERR(get_root_changes_offset(NULL, &changes_offset, revision_file, fs,
6420                                   rev, pool));
6421
6422   SVN_ERR(svn_io_file_seek(revision_file, APR_SET, &changes_offset, pool));
6423   SVN_ERR(read_all_changes(changes, revision_file, pool));
6424
6425   SVN_ERR(svn_io_file_close(revision_file, pool));
6426
6427   /* cache for future reference */
6428
6429   if (ffd->changes_cache)
6430     SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes, pool));
6431
6432   return SVN_NO_ERROR;
6433 }
6434
6435
6436 svn_error_t *
6437 svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
6438                          svn_fs_t *fs,
6439                          svn_revnum_t rev,
6440                          apr_hash_t *copyfrom_cache,
6441                          apr_pool_t *pool)
6442 {
6443   apr_hash_t *changed_paths;
6444   apr_array_header_t *changes;
6445   apr_pool_t *scratch_pool = svn_pool_create(pool);
6446
6447   SVN_ERR(get_changes(&changes, fs, rev, scratch_pool));
6448
6449   changed_paths = svn_hash__make(pool);
6450
6451   SVN_ERR(process_changes(changed_paths, copyfrom_cache, changes,
6452                           TRUE, pool));
6453   svn_pool_destroy(scratch_pool);
6454
6455   *changed_paths_p = changed_paths;
6456
6457   return SVN_NO_ERROR;
6458 }
6459
6460 /* Copy a revision node-rev SRC into the current transaction TXN_ID in
6461    the filesystem FS.  This is only used to create the root of a transaction.
6462    Allocations are from POOL.  */
6463 static svn_error_t *
6464 create_new_txn_noderev_from_rev(svn_fs_t *fs,
6465                                 const char *txn_id,
6466                                 svn_fs_id_t *src,
6467                                 apr_pool_t *pool)
6468 {
6469   node_revision_t *noderev;
6470   const char *node_id, *copy_id;
6471
6472   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool));
6473
6474   if (svn_fs_fs__id_txn_id(noderev->id))
6475     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6476                             _("Copying from transactions not allowed"));
6477
6478   noderev->predecessor_id = noderev->id;
6479   noderev->predecessor_count++;
6480   noderev->copyfrom_path = NULL;
6481   noderev->copyfrom_rev = SVN_INVALID_REVNUM;
6482
6483   /* For the transaction root, the copyroot never changes. */
6484
6485   node_id = svn_fs_fs__id_node_id(noderev->id);
6486   copy_id = svn_fs_fs__id_copy_id(noderev->id);
6487   noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6488
6489   return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
6490 }
6491
6492 /* A structure used by get_and_increment_txn_key_body(). */
6493 struct get_and_increment_txn_key_baton {
6494   svn_fs_t *fs;
6495   char *txn_id;
6496   apr_pool_t *pool;
6497 };
6498
6499 /* Callback used in the implementation of create_txn_dir().  This gets
6500    the current base 36 value in PATH_TXN_CURRENT and increments it.
6501    It returns the original value by the baton. */
6502 static svn_error_t *
6503 get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
6504 {
6505   struct get_and_increment_txn_key_baton *cb = baton;
6506   const char *txn_current_filename = path_txn_current(cb->fs, pool);
6507   const char *tmp_filename;
6508   char next_txn_id[MAX_KEY_SIZE+3];
6509   apr_size_t len;
6510
6511   svn_stringbuf_t *buf;
6512   SVN_ERR(read_content(&buf, txn_current_filename, cb->pool));
6513
6514   /* remove trailing newlines */
6515   svn_stringbuf_strip_whitespace(buf);
6516   cb->txn_id = buf->data;
6517   len = buf->len;
6518
6519   /* Increment the key and add a trailing \n to the string so the
6520      txn-current file has a newline in it. */
6521   svn_fs_fs__next_key(cb->txn_id, &len, next_txn_id);
6522   next_txn_id[len] = '\n';
6523   ++len;
6524   next_txn_id[len] = '\0';
6525
6526   SVN_ERR(svn_io_write_unique(&tmp_filename,
6527                               svn_dirent_dirname(txn_current_filename, pool),
6528                               next_txn_id, len, svn_io_file_del_none, pool));
6529   SVN_ERR(move_into_place(tmp_filename, txn_current_filename,
6530                           txn_current_filename, pool));
6531
6532   return SVN_NO_ERROR;
6533 }
6534
6535 /* Create a unique directory for a transaction in FS based on revision
6536    REV.  Return the ID for this transaction in *ID_P.  Use a sequence
6537    value in the transaction ID to prevent reuse of transaction IDs. */
6538 static svn_error_t *
6539 create_txn_dir(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6540                apr_pool_t *pool)
6541 {
6542   struct get_and_increment_txn_key_baton cb;
6543   const char *txn_dir;
6544
6545   /* Get the current transaction sequence value, which is a base-36
6546      number, from the txn-current file, and write an
6547      incremented value back out to the file.  Place the revision
6548      number the transaction is based off into the transaction id. */
6549   cb.pool = pool;
6550   cb.fs = fs;
6551   SVN_ERR(with_txn_current_lock(fs,
6552                                 get_and_increment_txn_key_body,
6553                                 &cb,
6554                                 pool));
6555   *id_p = apr_psprintf(pool, "%ld-%s", rev, cb.txn_id);
6556
6557   txn_dir = svn_dirent_join_many(pool,
6558                                  fs->path,
6559                                  PATH_TXNS_DIR,
6560                                  apr_pstrcat(pool, *id_p, PATH_EXT_TXN,
6561                                              (char *)NULL),
6562                                  NULL);
6563
6564   return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
6565 }
6566
6567 /* Create a unique directory for a transaction in FS based on revision
6568    REV.  Return the ID for this transaction in *ID_P.  This
6569    implementation is used in svn 1.4 and earlier repositories and is
6570    kept in 1.5 and greater to support the --pre-1.4-compatible and
6571    --pre-1.5-compatible repository creation options.  Reused
6572    transaction IDs are possible with this implementation. */
6573 static svn_error_t *
6574 create_txn_dir_pre_1_5(const char **id_p, svn_fs_t *fs, svn_revnum_t rev,
6575                        apr_pool_t *pool)
6576 {
6577   unsigned int i;
6578   apr_pool_t *subpool;
6579   const char *unique_path, *prefix;
6580
6581   /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
6582   prefix = svn_dirent_join_many(pool, fs->path, PATH_TXNS_DIR,
6583                                 apr_psprintf(pool, "%ld", rev), NULL);
6584
6585   subpool = svn_pool_create(pool);
6586   for (i = 1; i <= 99999; i++)
6587     {
6588       svn_error_t *err;
6589
6590       svn_pool_clear(subpool);
6591       unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
6592       err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
6593       if (! err)
6594         {
6595           /* We succeeded.  Return the basename minus the ".txn" extension. */
6596           const char *name = svn_dirent_basename(unique_path, subpool);
6597           *id_p = apr_pstrndup(pool, name,
6598                                strlen(name) - strlen(PATH_EXT_TXN));
6599           svn_pool_destroy(subpool);
6600           return SVN_NO_ERROR;
6601         }
6602       if (! APR_STATUS_IS_EEXIST(err->apr_err))
6603         return svn_error_trace(err);
6604       svn_error_clear(err);
6605     }
6606
6607   return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
6608                            NULL,
6609                            _("Unable to create transaction directory "
6610                              "in '%s' for revision %ld"),
6611                            svn_dirent_local_style(fs->path, pool),
6612                            rev);
6613 }
6614
6615 svn_error_t *
6616 svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
6617                       svn_fs_t *fs,
6618                       svn_revnum_t rev,
6619                       apr_pool_t *pool)
6620 {
6621   fs_fs_data_t *ffd = fs->fsap_data;
6622   svn_fs_txn_t *txn;
6623   svn_fs_id_t *root_id;
6624
6625   txn = apr_pcalloc(pool, sizeof(*txn));
6626
6627   /* Get the txn_id. */
6628   if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
6629     SVN_ERR(create_txn_dir(&txn->id, fs, rev, pool));
6630   else
6631     SVN_ERR(create_txn_dir_pre_1_5(&txn->id, fs, rev, pool));
6632
6633   txn->fs = fs;
6634   txn->base_rev = rev;
6635
6636   txn->vtable = &txn_vtable;
6637   *txn_p = txn;
6638
6639   /* Create a new root node for this transaction. */
6640   SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool));
6641   SVN_ERR(create_new_txn_noderev_from_rev(fs, txn->id, root_id, pool));
6642
6643   /* Create an empty rev file. */
6644   SVN_ERR(svn_io_file_create(path_txn_proto_rev(fs, txn->id, pool), "",
6645                              pool));
6646
6647   /* Create an empty rev-lock file. */
6648   SVN_ERR(svn_io_file_create(path_txn_proto_rev_lock(fs, txn->id, pool), "",
6649                              pool));
6650
6651   /* Create an empty changes file. */
6652   SVN_ERR(svn_io_file_create(path_txn_changes(fs, txn->id, pool), "",
6653                              pool));
6654
6655   /* Create the next-ids file. */
6656   return svn_io_file_create(path_txn_next_ids(fs, txn->id, pool), "0 0\n",
6657                             pool);
6658 }
6659
6660 /* Store the property list for transaction TXN_ID in PROPLIST.
6661    Perform temporary allocations in POOL. */
6662 static svn_error_t *
6663 get_txn_proplist(apr_hash_t *proplist,
6664                  svn_fs_t *fs,
6665                  const char *txn_id,
6666                  apr_pool_t *pool)
6667 {
6668   svn_stream_t *stream;
6669
6670   /* Check for issue #3696. (When we find and fix the cause, we can change
6671    * this to an assertion.) */
6672   if (txn_id == NULL)
6673     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
6674                             _("Internal error: a null transaction id was "
6675                               "passed to get_txn_proplist()"));
6676
6677   /* Open the transaction properties file. */
6678   SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
6679                                    pool, pool));
6680
6681   /* Read in the property list. */
6682   SVN_ERR(svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool));
6683
6684   return svn_stream_close(stream);
6685 }
6686
6687 svn_error_t *
6688 svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
6689                            const char *name,
6690                            const svn_string_t *value,
6691                            apr_pool_t *pool)
6692 {
6693   apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
6694   svn_prop_t prop;
6695
6696   prop.name = name;
6697   prop.value = value;
6698   APR_ARRAY_PUSH(props, svn_prop_t) = prop;
6699
6700   return svn_fs_fs__change_txn_props(txn, props, pool);
6701 }
6702
6703 svn_error_t *
6704 svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
6705                             const apr_array_header_t *props,
6706                             apr_pool_t *pool)
6707 {
6708   const char *txn_prop_filename;
6709   svn_stringbuf_t *buf;
6710   svn_stream_t *stream;
6711   apr_hash_t *txn_prop = apr_hash_make(pool);
6712   int i;
6713   svn_error_t *err;
6714
6715   err = get_txn_proplist(txn_prop, txn->fs, txn->id, pool);
6716   /* Here - and here only - we need to deal with the possibility that the
6717      transaction property file doesn't yet exist.  The rest of the
6718      implementation assumes that the file exists, but we're called to set the
6719      initial transaction properties as the transaction is being created. */
6720   if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
6721     svn_error_clear(err);
6722   else if (err)
6723     return svn_error_trace(err);
6724
6725   for (i = 0; i < props->nelts; i++)
6726     {
6727       svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
6728
6729       svn_hash_sets(txn_prop, prop->name, prop->value);
6730     }
6731
6732   /* Create a new version of the file and write out the new props. */
6733   /* Open the transaction properties file. */
6734   buf = svn_stringbuf_create_ensure(1024, pool);
6735   stream = svn_stream_from_stringbuf(buf, pool);
6736   SVN_ERR(svn_hash_write2(txn_prop, stream, SVN_HASH_TERMINATOR, pool));
6737   SVN_ERR(svn_stream_close(stream));
6738   SVN_ERR(svn_io_write_unique(&txn_prop_filename,
6739                               path_txn_dir(txn->fs, txn->id, pool),
6740                               buf->data,
6741                               buf->len,
6742                               svn_io_file_del_none,
6743                               pool));
6744   return svn_io_file_rename(txn_prop_filename,
6745                             path_txn_props(txn->fs, txn->id, pool),
6746                             pool);
6747 }
6748
6749 svn_error_t *
6750 svn_fs_fs__get_txn(transaction_t **txn_p,
6751                    svn_fs_t *fs,
6752                    const char *txn_id,
6753                    apr_pool_t *pool)
6754 {
6755   transaction_t *txn;
6756   node_revision_t *noderev;
6757   svn_fs_id_t *root_id;
6758
6759   txn = apr_pcalloc(pool, sizeof(*txn));
6760   txn->proplist = apr_hash_make(pool);
6761
6762   SVN_ERR(get_txn_proplist(txn->proplist, fs, txn_id, pool));
6763   root_id = svn_fs_fs__id_txn_create("0", "0", txn_id, pool);
6764
6765   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool));
6766
6767   txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
6768   txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
6769   txn->copies = NULL;
6770
6771   *txn_p = txn;
6772
6773   return SVN_NO_ERROR;
6774 }
6775
6776 /* Write out the currently available next node_id NODE_ID and copy_id
6777    COPY_ID for transaction TXN_ID in filesystem FS.  The next node-id is
6778    used both for creating new unique nodes for the given transaction, as
6779    well as uniquifying representations.  Perform temporary allocations in
6780    POOL. */
6781 static svn_error_t *
6782 write_next_ids(svn_fs_t *fs,
6783                const char *txn_id,
6784                const char *node_id,
6785                const char *copy_id,
6786                apr_pool_t *pool)
6787 {
6788   apr_file_t *file;
6789   svn_stream_t *out_stream;
6790
6791   SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6792                            APR_WRITE | APR_TRUNCATE,
6793                            APR_OS_DEFAULT, pool));
6794
6795   out_stream = svn_stream_from_aprfile2(file, TRUE, pool);
6796
6797   SVN_ERR(svn_stream_printf(out_stream, pool, "%s %s\n", node_id, copy_id));
6798
6799   SVN_ERR(svn_stream_close(out_stream));
6800   return svn_io_file_close(file, pool);
6801 }
6802
6803 /* Find out what the next unique node-id and copy-id are for
6804    transaction TXN_ID in filesystem FS.  Store the results in *NODE_ID
6805    and *COPY_ID.  The next node-id is used both for creating new unique
6806    nodes for the given transaction, as well as uniquifying representations.
6807    Perform all allocations in POOL. */
6808 static svn_error_t *
6809 read_next_ids(const char **node_id,
6810               const char **copy_id,
6811               svn_fs_t *fs,
6812               const char *txn_id,
6813               apr_pool_t *pool)
6814 {
6815   apr_file_t *file;
6816   char buf[MAX_KEY_SIZE*2+3];
6817   apr_size_t limit;
6818   char *str, *last_str = buf;
6819
6820   SVN_ERR(svn_io_file_open(&file, path_txn_next_ids(fs, txn_id, pool),
6821                            APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool));
6822
6823   limit = sizeof(buf);
6824   SVN_ERR(svn_io_read_length_line(file, buf, &limit, pool));
6825
6826   SVN_ERR(svn_io_file_close(file, pool));
6827
6828   /* Parse this into two separate strings. */
6829
6830   str = svn_cstring_tokenize(" ", &last_str);
6831   if (! str)
6832     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6833                             _("next-id file corrupt"));
6834
6835   *node_id = apr_pstrdup(pool, str);
6836
6837   str = svn_cstring_tokenize(" ", &last_str);
6838   if (! str)
6839     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
6840                             _("next-id file corrupt"));
6841
6842   *copy_id = apr_pstrdup(pool, str);
6843
6844   return SVN_NO_ERROR;
6845 }
6846
6847 /* Get a new and unique to this transaction node-id for transaction
6848    TXN_ID in filesystem FS.  Store the new node-id in *NODE_ID_P.
6849    Node-ids are guaranteed to be unique to this transction, but may
6850    not necessarily be sequential.  Perform all allocations in POOL. */
6851 static svn_error_t *
6852 get_new_txn_node_id(const char **node_id_p,
6853                     svn_fs_t *fs,
6854                     const char *txn_id,
6855                     apr_pool_t *pool)
6856 {
6857   const char *cur_node_id, *cur_copy_id;
6858   char *node_id;
6859   apr_size_t len;
6860
6861   /* First read in the current next-ids file. */
6862   SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
6863
6864   node_id = apr_pcalloc(pool, strlen(cur_node_id) + 2);
6865
6866   len = strlen(cur_node_id);
6867   svn_fs_fs__next_key(cur_node_id, &len, node_id);
6868
6869   SVN_ERR(write_next_ids(fs, txn_id, node_id, cur_copy_id, pool));
6870
6871   *node_id_p = apr_pstrcat(pool, "_", cur_node_id, (char *)NULL);
6872
6873   return SVN_NO_ERROR;
6874 }
6875
6876 svn_error_t *
6877 svn_fs_fs__create_node(const svn_fs_id_t **id_p,
6878                        svn_fs_t *fs,
6879                        node_revision_t *noderev,
6880                        const char *copy_id,
6881                        const char *txn_id,
6882                        apr_pool_t *pool)
6883 {
6884   const char *node_id;
6885   const svn_fs_id_t *id;
6886
6887   /* Get a new node-id for this node. */
6888   SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
6889
6890   id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
6891
6892   noderev->id = id;
6893
6894   SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
6895
6896   *id_p = id;
6897
6898   return SVN_NO_ERROR;
6899 }
6900
6901 svn_error_t *
6902 svn_fs_fs__purge_txn(svn_fs_t *fs,
6903                      const char *txn_id,
6904                      apr_pool_t *pool)
6905 {
6906   fs_fs_data_t *ffd = fs->fsap_data;
6907
6908   /* Remove the shared transaction object associated with this transaction. */
6909   SVN_ERR(purge_shared_txn(fs, txn_id, pool));
6910   /* Remove the directory associated with this transaction. */
6911   SVN_ERR(svn_io_remove_dir2(path_txn_dir(fs, txn_id, pool), FALSE,
6912                              NULL, NULL, pool));
6913   if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
6914     {
6915       /* Delete protorev and its lock, which aren't in the txn
6916          directory.  It's OK if they don't exist (for example, if this
6917          is post-commit and the proto-rev has been moved into
6918          place). */
6919       SVN_ERR(svn_io_remove_file2(path_txn_proto_rev(fs, txn_id, pool),
6920                                   TRUE, pool));
6921       SVN_ERR(svn_io_remove_file2(path_txn_proto_rev_lock(fs, txn_id, pool),
6922                                   TRUE, pool));
6923     }
6924   return SVN_NO_ERROR;
6925 }
6926
6927
6928 svn_error_t *
6929 svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
6930                      apr_pool_t *pool)
6931 {
6932   SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
6933
6934   /* Now, purge the transaction. */
6935   SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
6936             apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
6937                          txn->id));
6938
6939   return SVN_NO_ERROR;
6940 }
6941
6942
6943 svn_error_t *
6944 svn_fs_fs__set_entry(svn_fs_t *fs,
6945                      const char *txn_id,
6946                      node_revision_t *parent_noderev,
6947                      const char *name,
6948                      const svn_fs_id_t *id,
6949                      svn_node_kind_t kind,
6950                      apr_pool_t *pool)
6951 {
6952   representation_t *rep = parent_noderev->data_rep;
6953   const char *filename = path_txn_node_children(fs, parent_noderev->id, pool);
6954   apr_file_t *file;
6955   svn_stream_t *out;
6956   fs_fs_data_t *ffd = fs->fsap_data;
6957   apr_pool_t *subpool = svn_pool_create(pool);
6958
6959   if (!rep || !rep->txn_id)
6960     {
6961       const char *unique_suffix;
6962       apr_hash_t *entries;
6963
6964       /* Before we can modify the directory, we need to dump its old
6965          contents into a mutable representation file. */
6966       SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
6967                                           subpool));
6968       SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
6969       SVN_ERR(svn_io_file_open(&file, filename,
6970                                APR_WRITE | APR_CREATE | APR_BUFFERED,
6971                                APR_OS_DEFAULT, pool));
6972       out = svn_stream_from_aprfile2(file, TRUE, pool);
6973       SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
6974
6975       svn_pool_clear(subpool);
6976
6977       /* Mark the node-rev's data rep as mutable. */
6978       rep = apr_pcalloc(pool, sizeof(*rep));
6979       rep->revision = SVN_INVALID_REVNUM;
6980       rep->txn_id = txn_id;
6981
6982       if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
6983         {
6984           SVN_ERR(get_new_txn_node_id(&unique_suffix, fs, txn_id, pool));
6985           rep->uniquifier = apr_psprintf(pool, "%s/%s", txn_id, unique_suffix);
6986         }
6987
6988       parent_noderev->data_rep = rep;
6989       SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
6990                                            parent_noderev, FALSE, pool));
6991     }
6992   else
6993     {
6994       /* The directory rep is already mutable, so just open it for append. */
6995       SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
6996                                APR_OS_DEFAULT, pool));
6997       out = svn_stream_from_aprfile2(file, TRUE, pool);
6998     }
6999
7000   /* if we have a directory cache for this transaction, update it */
7001   if (ffd->txn_dir_cache)
7002     {
7003       /* build parameters: (name, new entry) pair */
7004       const char *key =
7005           svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
7006       replace_baton_t baton;
7007
7008       baton.name = name;
7009       baton.new_entry = NULL;
7010
7011       if (id)
7012         {
7013           baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
7014           baton.new_entry->name = name;
7015           baton.new_entry->kind = kind;
7016           baton.new_entry->id = id;
7017         }
7018
7019       /* actually update the cached directory (if cached) */
7020       SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
7021                                      svn_fs_fs__replace_dir_entry, &baton,
7022                                      subpool));
7023     }
7024   svn_pool_clear(subpool);
7025
7026   /* Append an incremental hash entry for the entry change. */
7027   if (id)
7028     {
7029       const char *val = unparse_dir_entry(kind, id, subpool);
7030
7031       SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n"
7032                                 "V %" APR_SIZE_T_FMT "\n%s\n",
7033                                 strlen(name), name,
7034                                 strlen(val), val));
7035     }
7036   else
7037     {
7038       SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
7039                                 strlen(name), name));
7040     }
7041
7042   SVN_ERR(svn_io_file_close(file, subpool));
7043   svn_pool_destroy(subpool);
7044   return SVN_NO_ERROR;
7045 }
7046
7047 /* Write a single change entry, path PATH, change CHANGE, and copyfrom
7048    string COPYFROM, into the file specified by FILE.  Only include the
7049    node kind field if INCLUDE_NODE_KIND is true.  All temporary
7050    allocations are in POOL. */
7051 static svn_error_t *
7052 write_change_entry(apr_file_t *file,
7053                    const char *path,
7054                    svn_fs_path_change2_t *change,
7055                    svn_boolean_t include_node_kind,
7056                    apr_pool_t *pool)
7057 {
7058   const char *idstr, *buf;
7059   const char *change_string = NULL;
7060   const char *kind_string = "";
7061
7062   switch (change->change_kind)
7063     {
7064     case svn_fs_path_change_modify:
7065       change_string = ACTION_MODIFY;
7066       break;
7067     case svn_fs_path_change_add:
7068       change_string = ACTION_ADD;
7069       break;
7070     case svn_fs_path_change_delete:
7071       change_string = ACTION_DELETE;
7072       break;
7073     case svn_fs_path_change_replace:
7074       change_string = ACTION_REPLACE;
7075       break;
7076     case svn_fs_path_change_reset:
7077       change_string = ACTION_RESET;
7078       break;
7079     default:
7080       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7081                                _("Invalid change type %d"),
7082                                change->change_kind);
7083     }
7084
7085   if (change->node_rev_id)
7086     idstr = svn_fs_fs__id_unparse(change->node_rev_id, pool)->data;
7087   else
7088     idstr = ACTION_RESET;
7089
7090   if (include_node_kind)
7091     {
7092       SVN_ERR_ASSERT(change->node_kind == svn_node_dir
7093                      || change->node_kind == svn_node_file);
7094       kind_string = apr_psprintf(pool, "-%s",
7095                                  change->node_kind == svn_node_dir
7096                                  ? KIND_DIR : KIND_FILE);
7097     }
7098   buf = apr_psprintf(pool, "%s %s%s %s %s %s\n",
7099                      idstr, change_string, kind_string,
7100                      change->text_mod ? FLAG_TRUE : FLAG_FALSE,
7101                      change->prop_mod ? FLAG_TRUE : FLAG_FALSE,
7102                      path);
7103
7104   SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7105
7106   if (SVN_IS_VALID_REVNUM(change->copyfrom_rev))
7107     {
7108       buf = apr_psprintf(pool, "%ld %s", change->copyfrom_rev,
7109                          change->copyfrom_path);
7110       SVN_ERR(svn_io_file_write_full(file, buf, strlen(buf), NULL, pool));
7111     }
7112
7113   return svn_io_file_write_full(file, "\n", 1, NULL, pool);
7114 }
7115
7116 svn_error_t *
7117 svn_fs_fs__add_change(svn_fs_t *fs,
7118                       const char *txn_id,
7119                       const char *path,
7120                       const svn_fs_id_t *id,
7121                       svn_fs_path_change_kind_t change_kind,
7122                       svn_boolean_t text_mod,
7123                       svn_boolean_t prop_mod,
7124                       svn_node_kind_t node_kind,
7125                       svn_revnum_t copyfrom_rev,
7126                       const char *copyfrom_path,
7127                       apr_pool_t *pool)
7128 {
7129   apr_file_t *file;
7130   svn_fs_path_change2_t *change;
7131
7132   SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
7133                            APR_APPEND | APR_WRITE | APR_CREATE
7134                            | APR_BUFFERED, APR_OS_DEFAULT, pool));
7135
7136   change = svn_fs__path_change_create_internal(id, change_kind, pool);
7137   change->text_mod = text_mod;
7138   change->prop_mod = prop_mod;
7139   change->node_kind = node_kind;
7140   change->copyfrom_rev = copyfrom_rev;
7141   change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
7142
7143   SVN_ERR(write_change_entry(file, path, change, TRUE, pool));
7144
7145   return svn_io_file_close(file, pool);
7146 }
7147
7148 /* This baton is used by the representation writing streams.  It keeps
7149    track of the checksum information as well as the total size of the
7150    representation so far. */
7151 struct rep_write_baton
7152 {
7153   /* The FS we are writing to. */
7154   svn_fs_t *fs;
7155
7156   /* Actual file to which we are writing. */
7157   svn_stream_t *rep_stream;
7158
7159   /* A stream from the delta combiner.  Data written here gets
7160      deltified, then eventually written to rep_stream. */
7161   svn_stream_t *delta_stream;
7162
7163   /* Where is this representation header stored. */
7164   apr_off_t rep_offset;
7165
7166   /* Start of the actual data. */
7167   apr_off_t delta_start;
7168
7169   /* How many bytes have been written to this rep already. */
7170   svn_filesize_t rep_size;
7171
7172   /* The node revision for which we're writing out info. */
7173   node_revision_t *noderev;
7174
7175   /* Actual output file. */
7176   apr_file_t *file;
7177   /* Lock 'cookie' used to unlock the output file once we've finished
7178      writing to it. */
7179   void *lockcookie;
7180
7181   svn_checksum_ctx_t *md5_checksum_ctx;
7182   svn_checksum_ctx_t *sha1_checksum_ctx;
7183
7184   apr_pool_t *pool;
7185
7186   apr_pool_t *parent_pool;
7187 };
7188
7189 /* Handler for the write method of the representation writable stream.
7190    BATON is a rep_write_baton, DATA is the data to write, and *LEN is
7191    the length of this data. */
7192 static svn_error_t *
7193 rep_write_contents(void *baton,
7194                    const char *data,
7195                    apr_size_t *len)
7196 {
7197   struct rep_write_baton *b = baton;
7198
7199   SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
7200   SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
7201   b->rep_size += *len;
7202
7203   /* If we are writing a delta, use that stream. */
7204   if (b->delta_stream)
7205     return svn_stream_write(b->delta_stream, data, len);
7206   else
7207     return svn_stream_write(b->rep_stream, data, len);
7208 }
7209
7210 /* Given a node-revision NODEREV in filesystem FS, return the
7211    representation in *REP to use as the base for a text representation
7212    delta if PROPS is FALSE.  If PROPS has been set, a suitable props
7213    base representation will be returned.  Perform temporary allocations
7214    in *POOL. */
7215 static svn_error_t *
7216 choose_delta_base(representation_t **rep,
7217                   svn_fs_t *fs,
7218                   node_revision_t *noderev,
7219                   svn_boolean_t props,
7220                   apr_pool_t *pool)
7221 {
7222   int count;
7223   int walk;
7224   node_revision_t *base;
7225   fs_fs_data_t *ffd = fs->fsap_data;
7226   svn_boolean_t maybe_shared_rep = FALSE;
7227
7228   /* If we have no predecessors, then use the empty stream as a
7229      base. */
7230   if (! noderev->predecessor_count)
7231     {
7232       *rep = NULL;
7233       return SVN_NO_ERROR;
7234     }
7235
7236   /* Flip the rightmost '1' bit of the predecessor count to determine
7237      which file rev (counting from 0) we want to use.  (To see why
7238      count & (count - 1) unsets the rightmost set bit, think about how
7239      you decrement a binary number.) */
7240   count = noderev->predecessor_count;
7241   count = count & (count - 1);
7242
7243   /* We use skip delta for limiting the number of delta operations
7244      along very long node histories.  Close to HEAD however, we create
7245      a linear history to minimize delta size.  */
7246   walk = noderev->predecessor_count - count;
7247   if (walk < (int)ffd->max_linear_deltification)
7248     count = noderev->predecessor_count - 1;
7249
7250   /* Finding the delta base over a very long distance can become extremely
7251      expensive for very deep histories, possibly causing client timeouts etc.
7252      OTOH, this is a rare operation and its gains are minimal. Lets simply
7253      start deltification anew close every other 1000 changes or so.  */
7254   if (walk > (int)ffd->max_deltification_walk)
7255     {
7256       *rep = NULL;
7257       return SVN_NO_ERROR;
7258     }
7259
7260   /* Walk back a number of predecessors equal to the difference
7261      between count and the original predecessor count.  (For example,
7262      if noderev has ten predecessors and we want the eighth file rev,
7263      walk back two predecessors.) */
7264   base = noderev;
7265   while ((count++) < noderev->predecessor_count)
7266     {
7267       SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
7268                                            base->predecessor_id, pool));
7269
7270       /* If there is a shared rep along the way, we need to limit the
7271        * length of the deltification chain.
7272        *
7273        * Please note that copied nodes - such as branch directories - will
7274        * look the same (false positive) while reps shared within the same
7275        * revision will not be caught (false negative).
7276        */
7277       if (props)
7278         {
7279           if (   base->prop_rep
7280               && svn_fs_fs__id_rev(base->id) > base->prop_rep->revision)
7281             maybe_shared_rep = TRUE;
7282         }
7283       else
7284         {
7285           if (   base->data_rep
7286               && svn_fs_fs__id_rev(base->id) > base->data_rep->revision)
7287             maybe_shared_rep = TRUE;
7288         }
7289     }
7290
7291   /* return a suitable base representation */
7292   *rep = props ? base->prop_rep : base->data_rep;
7293
7294   /* if we encountered a shared rep, it's parent chain may be different
7295    * from the node-rev parent chain. */
7296   if (*rep && maybe_shared_rep)
7297     {
7298       /* Check whether the length of the deltification chain is acceptable.
7299        * Otherwise, shared reps may form a non-skipping delta chain in
7300        * extreme cases. */
7301       apr_pool_t *sub_pool = svn_pool_create(pool);
7302       representation_t base_rep = **rep;
7303
7304       /* Some reasonable limit, depending on how acceptable longer linear
7305        * chains are in this repo.  Also, allow for some minimal chain. */
7306       int max_chain_length = 2 * (int)ffd->max_linear_deltification + 2;
7307
7308       /* re-use open files between iterations */
7309       svn_revnum_t rev_hint = SVN_INVALID_REVNUM;
7310       apr_file_t *file_hint = NULL;
7311
7312       /* follow the delta chain towards the end but for at most
7313        * MAX_CHAIN_LENGTH steps. */
7314       for (; max_chain_length; --max_chain_length)
7315         {
7316           struct rep_state *rep_state;
7317           struct rep_args *rep_args;
7318
7319           SVN_ERR(create_rep_state_body(&rep_state,
7320                                         &rep_args,
7321                                         &file_hint,
7322                                         &rev_hint,
7323                                         &base_rep,
7324                                         fs,
7325                                         sub_pool));
7326           if (!rep_args->is_delta  || !rep_args->base_revision)
7327             break;
7328
7329           base_rep.revision = rep_args->base_revision;
7330           base_rep.offset = rep_args->base_offset;
7331           base_rep.size = rep_args->base_length;
7332           base_rep.txn_id = NULL;
7333         }
7334
7335       /* start new delta chain if the current one has grown too long */
7336       if (max_chain_length == 0)
7337         *rep = NULL;
7338
7339       svn_pool_destroy(sub_pool);
7340     }
7341
7342   /* verify that the reps don't form a degenerated '*/
7343   return SVN_NO_ERROR;
7344 }
7345
7346 /* Something went wrong and the pool for the rep write is being
7347    cleared before we've finished writing the rep.  So we need
7348    to remove the rep from the protorevfile and we need to unlock
7349    the protorevfile. */
7350 static apr_status_t
7351 rep_write_cleanup(void *data)
7352 {
7353   struct rep_write_baton *b = data;
7354   const char *txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7355   svn_error_t *err;
7356
7357   /* Truncate and close the protorevfile. */
7358   err = svn_io_file_trunc(b->file, b->rep_offset, b->pool);
7359   err = svn_error_compose_create(err, svn_io_file_close(b->file, b->pool));
7360
7361   /* Remove our lock regardless of any preceeding errors so that the
7362      being_written flag is always removed and stays consistent with the
7363      file lock which will be removed no matter what since the pool is
7364      going away. */
7365   err = svn_error_compose_create(err, unlock_proto_rev(b->fs, txn_id,
7366                                                        b->lockcookie, b->pool));
7367   if (err)
7368     {
7369       apr_status_t rc = err->apr_err;
7370       svn_error_clear(err);
7371       return rc;
7372     }
7373
7374   return APR_SUCCESS;
7375 }
7376
7377
7378 /* Get a rep_write_baton and store it in *WB_P for the representation
7379    indicated by NODEREV in filesystem FS.  Perform allocations in
7380    POOL.  Only appropriate for file contents, not for props or
7381    directory contents. */
7382 static svn_error_t *
7383 rep_write_get_baton(struct rep_write_baton **wb_p,
7384                     svn_fs_t *fs,
7385                     node_revision_t *noderev,
7386                     apr_pool_t *pool)
7387 {
7388   struct rep_write_baton *b;
7389   apr_file_t *file;
7390   representation_t *base_rep;
7391   svn_stream_t *source;
7392   const char *header;
7393   svn_txdelta_window_handler_t wh;
7394   void *whb;
7395   fs_fs_data_t *ffd = fs->fsap_data;
7396   int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7397
7398   b = apr_pcalloc(pool, sizeof(*b));
7399
7400   b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7401   b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7402
7403   b->fs = fs;
7404   b->parent_pool = pool;
7405   b->pool = svn_pool_create(pool);
7406   b->rep_size = 0;
7407   b->noderev = noderev;
7408
7409   /* Open the prototype rev file and seek to its end. */
7410   SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
7411                                  fs, svn_fs_fs__id_txn_id(noderev->id),
7412                                  b->pool));
7413
7414   b->file = file;
7415   b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->pool);
7416
7417   SVN_ERR(get_file_offset(&b->rep_offset, file, b->pool));
7418
7419   /* Get the base for this delta. */
7420   SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->pool));
7421   SVN_ERR(read_representation(&source, fs, base_rep, b->pool));
7422
7423   /* Write out the rep header. */
7424   if (base_rep)
7425     {
7426       header = apr_psprintf(b->pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7427                             SVN_FILESIZE_T_FMT "\n",
7428                             base_rep->revision, base_rep->offset,
7429                             base_rep->size);
7430     }
7431   else
7432     {
7433       header = REP_DELTA "\n";
7434     }
7435   SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7436                                  b->pool));
7437
7438   /* Now determine the offset of the actual svndiff data. */
7439   SVN_ERR(get_file_offset(&b->delta_start, file, b->pool));
7440
7441   /* Cleanup in case something goes wrong. */
7442   apr_pool_cleanup_register(b->pool, b, rep_write_cleanup,
7443                             apr_pool_cleanup_null);
7444
7445   /* Prepare to write the svndiff data. */
7446   svn_txdelta_to_svndiff3(&wh,
7447                           &whb,
7448                           b->rep_stream,
7449                           diff_version,
7450                           SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7451                           pool);
7452
7453   b->delta_stream = svn_txdelta_target_push(wh, whb, source, b->pool);
7454
7455   *wb_p = b;
7456
7457   return SVN_NO_ERROR;
7458 }
7459
7460 /* For the hash REP->SHA1, try to find an already existing representation
7461    in FS and return it in *OUT_REP.  If no such representation exists or
7462    if rep sharing has been disabled for FS, NULL will be returned.  Since
7463    there may be new duplicate representations within the same uncommitted
7464    revision, those can be passed in REPS_HASH (maps a sha1 digest onto
7465    representation_t*), otherwise pass in NULL for REPS_HASH.
7466    POOL will be used for allocations. The lifetime of the returned rep is
7467    limited by both, POOL and REP lifetime.
7468  */
7469 static svn_error_t *
7470 get_shared_rep(representation_t **old_rep,
7471                svn_fs_t *fs,
7472                representation_t *rep,
7473                apr_hash_t *reps_hash,
7474                apr_pool_t *pool)
7475 {
7476   svn_error_t *err;
7477   fs_fs_data_t *ffd = fs->fsap_data;
7478
7479   /* Return NULL, if rep sharing has been disabled. */
7480   *old_rep = NULL;
7481   if (!ffd->rep_sharing_allowed)
7482     return SVN_NO_ERROR;
7483
7484   /* Check and see if we already have a representation somewhere that's
7485      identical to the one we just wrote out.  Start with the hash lookup
7486      because it is cheepest. */
7487   if (reps_hash)
7488     *old_rep = apr_hash_get(reps_hash,
7489                             rep->sha1_checksum->digest,
7490                             APR_SHA1_DIGESTSIZE);
7491
7492   /* If we haven't found anything yet, try harder and consult our DB. */
7493   if (*old_rep == NULL)
7494     {
7495       err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum,
7496                                          pool);
7497       /* ### Other error codes that we shouldn't mask out? */
7498       if (err == SVN_NO_ERROR)
7499         {
7500           if (*old_rep)
7501             SVN_ERR(verify_walker(*old_rep, NULL, fs, pool));
7502         }
7503       else if (err->apr_err == SVN_ERR_FS_CORRUPT
7504                || SVN_ERROR_IN_CATEGORY(err->apr_err,
7505                                         SVN_ERR_MALFUNC_CATEGORY_START))
7506         {
7507           /* Fatal error; don't mask it.
7508
7509              In particular, this block is triggered when the rep-cache refers
7510              to revisions in the future.  We signal that as a corruption situation
7511              since, once those revisions are less than youngest (because of more
7512              commits), the rep-cache would be invalid.
7513            */
7514           SVN_ERR(err);
7515         }
7516       else
7517         {
7518           /* Something's wrong with the rep-sharing index.  We can continue
7519              without rep-sharing, but warn.
7520            */
7521           (fs->warning)(fs->warning_baton, err);
7522           svn_error_clear(err);
7523           *old_rep = NULL;
7524         }
7525     }
7526
7527   /* look for intra-revision matches (usually data reps but not limited
7528      to them in case props happen to look like some data rep)
7529    */
7530   if (*old_rep == NULL && rep->txn_id)
7531     {
7532       svn_node_kind_t kind;
7533       const char *file_name
7534         = path_txn_sha1(fs, rep->txn_id, rep->sha1_checksum, pool);
7535
7536       /* in our txn, is there a rep file named with the wanted SHA1?
7537          If so, read it and use that rep.
7538        */
7539       SVN_ERR(svn_io_check_path(file_name, &kind, pool));
7540       if (kind == svn_node_file)
7541         {
7542           svn_stringbuf_t *rep_string;
7543           SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name, pool));
7544           SVN_ERR(read_rep_offsets_body(old_rep, rep_string->data,
7545                                         rep->txn_id, FALSE, pool));
7546         }
7547     }
7548
7549   /* Add information that is missing in the cached data. */
7550   if (*old_rep)
7551     {
7552       /* Use the old rep for this content. */
7553       (*old_rep)->md5_checksum = rep->md5_checksum;
7554       (*old_rep)->uniquifier = rep->uniquifier;
7555     }
7556
7557   return SVN_NO_ERROR;
7558 }
7559
7560 /* Close handler for the representation write stream.  BATON is a
7561    rep_write_baton.  Writes out a new node-rev that correctly
7562    references the representation we just finished writing. */
7563 static svn_error_t *
7564 rep_write_contents_close(void *baton)
7565 {
7566   struct rep_write_baton *b = baton;
7567   const char *unique_suffix;
7568   representation_t *rep;
7569   representation_t *old_rep;
7570   apr_off_t offset;
7571   fs_fs_data_t *ffd = b->fs->fsap_data;
7572
7573   rep = apr_pcalloc(b->parent_pool, sizeof(*rep));
7574   rep->offset = b->rep_offset;
7575
7576   /* Close our delta stream so the last bits of svndiff are written
7577      out. */
7578   if (b->delta_stream)
7579     SVN_ERR(svn_stream_close(b->delta_stream));
7580
7581   /* Determine the length of the svndiff data. */
7582   SVN_ERR(get_file_offset(&offset, b->file, b->pool));
7583   rep->size = offset - b->delta_start;
7584
7585   /* Fill in the rest of the representation field. */
7586   rep->expanded_size = b->rep_size;
7587   rep->txn_id = svn_fs_fs__id_txn_id(b->noderev->id);
7588
7589   if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
7590     {
7591       SVN_ERR(get_new_txn_node_id(&unique_suffix, b->fs, rep->txn_id, b->pool));
7592       rep->uniquifier = apr_psprintf(b->parent_pool, "%s/%s", rep->txn_id,
7593                                      unique_suffix);
7594     }
7595   rep->revision = SVN_INVALID_REVNUM;
7596
7597   /* Finalize the checksum. */
7598   SVN_ERR(svn_checksum_final(&rep->md5_checksum, b->md5_checksum_ctx,
7599                               b->parent_pool));
7600   SVN_ERR(svn_checksum_final(&rep->sha1_checksum, b->sha1_checksum_ctx,
7601                               b->parent_pool));
7602
7603   /* Check and see if we already have a representation somewhere that's
7604      identical to the one we just wrote out. */
7605   SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool));
7606
7607   if (old_rep)
7608     {
7609       /* We need to erase from the protorev the data we just wrote. */
7610       SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool));
7611
7612       /* Use the old rep for this content. */
7613       b->noderev->data_rep = old_rep;
7614     }
7615   else
7616     {
7617       /* Write out our cosmetic end marker. */
7618       SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
7619
7620       b->noderev->data_rep = rep;
7621     }
7622
7623   /* Remove cleanup callback. */
7624   apr_pool_cleanup_kill(b->pool, b, rep_write_cleanup);
7625
7626   /* Write out the new node-rev information. */
7627   SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev, FALSE,
7628                                        b->pool));
7629   if (!old_rep)
7630     SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->pool));
7631
7632   SVN_ERR(svn_io_file_close(b->file, b->pool));
7633   SVN_ERR(unlock_proto_rev(b->fs, rep->txn_id, b->lockcookie, b->pool));
7634   svn_pool_destroy(b->pool);
7635
7636   return SVN_NO_ERROR;
7637 }
7638
7639 /* Store a writable stream in *CONTENTS_P that will receive all data
7640    written and store it as the file data representation referenced by
7641    NODEREV in filesystem FS.  Perform temporary allocations in
7642    POOL.  Only appropriate for file data, not props or directory
7643    contents. */
7644 static svn_error_t *
7645 set_representation(svn_stream_t **contents_p,
7646                    svn_fs_t *fs,
7647                    node_revision_t *noderev,
7648                    apr_pool_t *pool)
7649 {
7650   struct rep_write_baton *wb;
7651
7652   if (! svn_fs_fs__id_txn_id(noderev->id))
7653     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
7654                              _("Attempted to write to non-transaction '%s'"),
7655                              svn_fs_fs__id_unparse(noderev->id, pool)->data);
7656
7657   SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
7658
7659   *contents_p = svn_stream_create(wb, pool);
7660   svn_stream_set_write(*contents_p, rep_write_contents);
7661   svn_stream_set_close(*contents_p, rep_write_contents_close);
7662
7663   return SVN_NO_ERROR;
7664 }
7665
7666 svn_error_t *
7667 svn_fs_fs__set_contents(svn_stream_t **stream,
7668                         svn_fs_t *fs,
7669                         node_revision_t *noderev,
7670                         apr_pool_t *pool)
7671 {
7672   if (noderev->kind != svn_node_file)
7673     return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
7674                             _("Can't set text contents of a directory"));
7675
7676   return set_representation(stream, fs, noderev, pool);
7677 }
7678
7679 svn_error_t *
7680 svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
7681                             svn_fs_t *fs,
7682                             const svn_fs_id_t *old_idp,
7683                             node_revision_t *new_noderev,
7684                             const char *copy_id,
7685                             const char *txn_id,
7686                             apr_pool_t *pool)
7687 {
7688   const svn_fs_id_t *id;
7689
7690   if (! copy_id)
7691     copy_id = svn_fs_fs__id_copy_id(old_idp);
7692   id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
7693                                 txn_id, pool);
7694
7695   new_noderev->id = id;
7696
7697   if (! new_noderev->copyroot_path)
7698     {
7699       new_noderev->copyroot_path = apr_pstrdup(pool,
7700                                                new_noderev->created_path);
7701       new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
7702     }
7703
7704   SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
7705                                        pool));
7706
7707   *new_id_p = id;
7708
7709   return SVN_NO_ERROR;
7710 }
7711
7712 svn_error_t *
7713 svn_fs_fs__set_proplist(svn_fs_t *fs,
7714                         node_revision_t *noderev,
7715                         apr_hash_t *proplist,
7716                         apr_pool_t *pool)
7717 {
7718   const char *filename = path_txn_node_props(fs, noderev->id, pool);
7719   apr_file_t *file;
7720   svn_stream_t *out;
7721
7722   /* Dump the property list to the mutable property file. */
7723   SVN_ERR(svn_io_file_open(&file, filename,
7724                            APR_WRITE | APR_CREATE | APR_TRUNCATE
7725                            | APR_BUFFERED, APR_OS_DEFAULT, pool));
7726   out = svn_stream_from_aprfile2(file, TRUE, pool);
7727   SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
7728   SVN_ERR(svn_io_file_close(file, pool));
7729
7730   /* Mark the node-rev's prop rep as mutable, if not already done. */
7731   if (!noderev->prop_rep || !noderev->prop_rep->txn_id)
7732     {
7733       noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
7734       noderev->prop_rep->txn_id = svn_fs_fs__id_txn_id(noderev->id);
7735       SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
7736     }
7737
7738   return SVN_NO_ERROR;
7739 }
7740
7741 /* Read the 'current' file for filesystem FS and store the next
7742    available node id in *NODE_ID, and the next available copy id in
7743    *COPY_ID.  Allocations are performed from POOL. */
7744 static svn_error_t *
7745 get_next_revision_ids(const char **node_id,
7746                       const char **copy_id,
7747                       svn_fs_t *fs,
7748                       apr_pool_t *pool)
7749 {
7750   char *buf;
7751   char *str;
7752   svn_stringbuf_t *content;
7753
7754   SVN_ERR(read_content(&content, svn_fs_fs__path_current(fs, pool), pool));
7755   buf = content->data;
7756
7757   str = svn_cstring_tokenize(" ", &buf);
7758   if (! str)
7759     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7760                             _("Corrupt 'current' file"));
7761
7762   str = svn_cstring_tokenize(" ", &buf);
7763   if (! str)
7764     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7765                             _("Corrupt 'current' file"));
7766
7767   *node_id = apr_pstrdup(pool, str);
7768
7769   str = svn_cstring_tokenize(" \n", &buf);
7770   if (! str)
7771     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
7772                             _("Corrupt 'current' file"));
7773
7774   *copy_id = apr_pstrdup(pool, str);
7775
7776   return SVN_NO_ERROR;
7777 }
7778
7779 /* This baton is used by the stream created for write_hash_rep. */
7780 struct write_hash_baton
7781 {
7782   svn_stream_t *stream;
7783
7784   apr_size_t size;
7785
7786   svn_checksum_ctx_t *md5_ctx;
7787   svn_checksum_ctx_t *sha1_ctx;
7788 };
7789
7790 /* The handler for the write_hash_rep stream.  BATON is a
7791    write_hash_baton, DATA has the data to write and *LEN is the number
7792    of bytes to write. */
7793 static svn_error_t *
7794 write_hash_handler(void *baton,
7795                    const char *data,
7796                    apr_size_t *len)
7797 {
7798   struct write_hash_baton *whb = baton;
7799
7800   SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
7801   SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
7802
7803   SVN_ERR(svn_stream_write(whb->stream, data, len));
7804   whb->size += *len;
7805
7806   return SVN_NO_ERROR;
7807 }
7808
7809 /* Write out the hash HASH as a text representation to file FILE.  In
7810    the process, record position, the total size of the dump and MD5 as
7811    well as SHA1 in REP.   If rep sharing has been enabled and REPS_HASH
7812    is not NULL, it will be used in addition to the on-disk cache to find
7813    earlier reps with the same content.  When such existing reps can be
7814    found, we will truncate the one just written from the file and return
7815    the existing rep.  Perform temporary allocations in POOL. */
7816 static svn_error_t *
7817 write_hash_rep(representation_t *rep,
7818                apr_file_t *file,
7819                apr_hash_t *hash,
7820                svn_fs_t *fs,
7821                apr_hash_t *reps_hash,
7822                apr_pool_t *pool)
7823 {
7824   svn_stream_t *stream;
7825   struct write_hash_baton *whb;
7826   representation_t *old_rep;
7827
7828   SVN_ERR(get_file_offset(&rep->offset, file, pool));
7829
7830   whb = apr_pcalloc(pool, sizeof(*whb));
7831
7832   whb->stream = svn_stream_from_aprfile2(file, TRUE, pool);
7833   whb->size = 0;
7834   whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7835   whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7836
7837   stream = svn_stream_create(whb, pool);
7838   svn_stream_set_write(stream, write_hash_handler);
7839
7840   SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
7841
7842   SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7843
7844   /* Store the results. */
7845   SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7846   SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7847
7848   /* Check and see if we already have a representation somewhere that's
7849      identical to the one we just wrote out. */
7850   SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7851
7852   if (old_rep)
7853     {
7854       /* We need to erase from the protorev the data we just wrote. */
7855       SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7856
7857       /* Use the old rep for this content. */
7858       memcpy(rep, old_rep, sizeof (*rep));
7859     }
7860   else
7861     {
7862       /* Write out our cosmetic end marker. */
7863       SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
7864
7865       /* update the representation */
7866       rep->size = whb->size;
7867       rep->expanded_size = whb->size;
7868     }
7869
7870   return SVN_NO_ERROR;
7871 }
7872
7873 /* Write out the hash HASH pertaining to the NODEREV in FS as a deltified
7874    text representation to file FILE.  In the process, record the total size
7875    and the md5 digest in REP.  If rep sharing has been enabled and REPS_HASH
7876    is not NULL, it will be used in addition to the on-disk cache to find
7877    earlier reps with the same content.  When such existing reps can be found,
7878    we will truncate the one just written from the file and return the existing
7879    rep.  If PROPS is set, assume that we want to a props representation as
7880    the base for our delta.  Perform temporary allocations in POOL. */
7881 static svn_error_t *
7882 write_hash_delta_rep(representation_t *rep,
7883                      apr_file_t *file,
7884                      apr_hash_t *hash,
7885                      svn_fs_t *fs,
7886                      node_revision_t *noderev,
7887                      apr_hash_t *reps_hash,
7888                      svn_boolean_t props,
7889                      apr_pool_t *pool)
7890 {
7891   svn_txdelta_window_handler_t diff_wh;
7892   void *diff_whb;
7893
7894   svn_stream_t *file_stream;
7895   svn_stream_t *stream;
7896   representation_t *base_rep;
7897   representation_t *old_rep;
7898   svn_stream_t *source;
7899   const char *header;
7900
7901   apr_off_t rep_end = 0;
7902   apr_off_t delta_start = 0;
7903
7904   struct write_hash_baton *whb;
7905   fs_fs_data_t *ffd = fs->fsap_data;
7906   int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
7907
7908   /* Get the base for this delta. */
7909   SVN_ERR(choose_delta_base(&base_rep, fs, noderev, props, pool));
7910   SVN_ERR(read_representation(&source, fs, base_rep, pool));
7911
7912   SVN_ERR(get_file_offset(&rep->offset, file, pool));
7913
7914   /* Write out the rep header. */
7915   if (base_rep)
7916     {
7917       header = apr_psprintf(pool, REP_DELTA " %ld %" APR_OFF_T_FMT " %"
7918                             SVN_FILESIZE_T_FMT "\n",
7919                             base_rep->revision, base_rep->offset,
7920                             base_rep->size);
7921     }
7922   else
7923     {
7924       header = REP_DELTA "\n";
7925     }
7926   SVN_ERR(svn_io_file_write_full(file, header, strlen(header), NULL,
7927                                  pool));
7928
7929   SVN_ERR(get_file_offset(&delta_start, file, pool));
7930   file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
7931
7932   /* Prepare to write the svndiff data. */
7933   svn_txdelta_to_svndiff3(&diff_wh,
7934                           &diff_whb,
7935                           file_stream,
7936                           diff_version,
7937                           SVN_DELTA_COMPRESSION_LEVEL_DEFAULT,
7938                           pool);
7939
7940   whb = apr_pcalloc(pool, sizeof(*whb));
7941   whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
7942   whb->size = 0;
7943   whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
7944   whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
7945
7946   /* serialize the hash */
7947   stream = svn_stream_create(whb, pool);
7948   svn_stream_set_write(stream, write_hash_handler);
7949
7950   SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
7951   SVN_ERR(svn_stream_close(whb->stream));
7952
7953   /* Store the results. */
7954   SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
7955   SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
7956
7957   /* Check and see if we already have a representation somewhere that's
7958      identical to the one we just wrote out. */
7959   SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
7960
7961   if (old_rep)
7962     {
7963       /* We need to erase from the protorev the data we just wrote. */
7964       SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
7965
7966       /* Use the old rep for this content. */
7967       memcpy(rep, old_rep, sizeof (*rep));
7968     }
7969   else
7970     {
7971       /* Write out our cosmetic end marker. */
7972       SVN_ERR(get_file_offset(&rep_end, file, pool));
7973       SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
7974
7975       /* update the representation */
7976       rep->expanded_size = whb->size;
7977       rep->size = rep_end - delta_start;
7978     }
7979
7980   return SVN_NO_ERROR;
7981 }
7982
7983 /* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
7984    of (not yet committed) revision REV in FS.  Use POOL for temporary
7985    allocations.
7986
7987    If you change this function, consider updating svn_fs_fs__verify() too.
7988  */
7989 static svn_error_t *
7990 validate_root_noderev(svn_fs_t *fs,
7991                       node_revision_t *root_noderev,
7992                       svn_revnum_t rev,
7993                       apr_pool_t *pool)
7994 {
7995   svn_revnum_t head_revnum = rev-1;
7996   int head_predecessor_count;
7997
7998   SVN_ERR_ASSERT(rev > 0);
7999
8000   /* Compute HEAD_PREDECESSOR_COUNT. */
8001   {
8002     svn_fs_root_t *head_revision;
8003     const svn_fs_id_t *head_root_id;
8004     node_revision_t *head_root_noderev;
8005
8006     /* Get /@HEAD's noderev. */
8007     SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
8008     SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
8009     SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
8010                                          pool));
8011
8012     head_predecessor_count = head_root_noderev->predecessor_count;
8013   }
8014
8015   /* Check that the root noderev's predecessor count equals REV.
8016
8017      This kind of corruption was seen on svn.apache.org (both on
8018      the root noderev and on other fspaths' noderevs); see
8019      issue #4129.
8020
8021      Normally (rev == root_noderev->predecessor_count), but here we
8022      use a more roundabout check that should only trigger on new instances
8023      of the corruption, rather then trigger on each and every new commit
8024      to a repository that has triggered the bug somewhere in its root
8025      noderev's history.
8026    */
8027   if (root_noderev->predecessor_count != -1
8028       && (root_noderev->predecessor_count - head_predecessor_count)
8029          != (rev - head_revnum))
8030     {
8031       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
8032                                _("predecessor count for "
8033                                  "the root node-revision is wrong: "
8034                                  "found (%d+%ld != %d), committing r%ld"),
8035                                  head_predecessor_count,
8036                                  rev - head_revnum, /* This is equal to 1. */
8037                                  root_noderev->predecessor_count,
8038                                  rev);
8039     }
8040
8041   return SVN_NO_ERROR;
8042 }
8043
8044 /* Copy a node-revision specified by id ID in fileystem FS from a
8045    transaction into the proto-rev-file FILE.  Set *NEW_ID_P to a
8046    pointer to the new node-id which will be allocated in POOL.
8047    If this is a directory, copy all children as well.
8048
8049    START_NODE_ID and START_COPY_ID are
8050    the first available node and copy ids for this filesystem, for older
8051    FS formats.
8052
8053    REV is the revision number that this proto-rev-file will represent.
8054
8055    INITIAL_OFFSET is the offset of the proto-rev-file on entry to
8056    commit_body.
8057
8058    If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
8059    REPS_POOL) of each data rep that is new in this revision.
8060
8061    If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
8062    of the representations of each property rep that is new in this
8063    revision.
8064
8065    AT_ROOT is true if the node revision being written is the root
8066    node-revision.  It is only controls additional sanity checking
8067    logic.
8068
8069    Temporary allocations are also from POOL. */
8070 static svn_error_t *
8071 write_final_rev(const svn_fs_id_t **new_id_p,
8072                 apr_file_t *file,
8073                 svn_revnum_t rev,
8074                 svn_fs_t *fs,
8075                 const svn_fs_id_t *id,
8076                 const char *start_node_id,
8077                 const char *start_copy_id,
8078                 apr_off_t initial_offset,
8079                 apr_array_header_t *reps_to_cache,
8080                 apr_hash_t *reps_hash,
8081                 apr_pool_t *reps_pool,
8082                 svn_boolean_t at_root,
8083                 apr_pool_t *pool)
8084 {
8085   node_revision_t *noderev;
8086   apr_off_t my_offset;
8087   char my_node_id_buf[MAX_KEY_SIZE + 2];
8088   char my_copy_id_buf[MAX_KEY_SIZE + 2];
8089   const svn_fs_id_t *new_id;
8090   const char *node_id, *copy_id, *my_node_id, *my_copy_id;
8091   fs_fs_data_t *ffd = fs->fsap_data;
8092
8093   *new_id_p = NULL;
8094
8095   /* Check to see if this is a transaction node. */
8096   if (! svn_fs_fs__id_txn_id(id))
8097     return SVN_NO_ERROR;
8098
8099   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
8100
8101   if (noderev->kind == svn_node_dir)
8102     {
8103       apr_pool_t *subpool;
8104       apr_hash_t *entries, *str_entries;
8105       apr_array_header_t *sorted_entries;
8106       int i;
8107
8108       /* This is a directory.  Write out all the children first. */
8109       subpool = svn_pool_create(pool);
8110
8111       SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool));
8112       /* For the sake of the repository administrator sort the entries
8113          so that the final file is deterministic and repeatable,
8114          however the rest of the FSFS code doesn't require any
8115          particular order here. */
8116       sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically,
8117                                       pool);
8118       for (i = 0; i < sorted_entries->nelts; ++i)
8119         {
8120           svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i,
8121                                                   svn_sort__item_t).value;
8122
8123           svn_pool_clear(subpool);
8124           SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
8125                                   start_node_id, start_copy_id, initial_offset,
8126                                   reps_to_cache, reps_hash, reps_pool, FALSE,
8127                                   subpool));
8128           if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
8129             dirent->id = svn_fs_fs__id_copy(new_id, pool);
8130         }
8131       svn_pool_destroy(subpool);
8132
8133       if (noderev->data_rep && noderev->data_rep->txn_id)
8134         {
8135           /* Write out the contents of this directory as a text rep. */
8136           SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
8137
8138           noderev->data_rep->txn_id = NULL;
8139           noderev->data_rep->revision = rev;
8140
8141           if (ffd->deltify_directories)
8142             SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
8143                                          str_entries, fs, noderev, NULL,
8144                                          FALSE, pool));
8145           else
8146             SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
8147                                    fs, NULL, pool));
8148         }
8149     }
8150   else
8151     {
8152       /* This is a file.  We should make sure the data rep, if it
8153          exists in a "this" state, gets rewritten to our new revision
8154          num. */
8155
8156       if (noderev->data_rep && noderev->data_rep->txn_id)
8157         {
8158           noderev->data_rep->txn_id = NULL;
8159           noderev->data_rep->revision = rev;
8160
8161           /* See issue 3845.  Some unknown mechanism caused the
8162              protorev file to get truncated, so check for that
8163              here.  */
8164           if (noderev->data_rep->offset + noderev->data_rep->size
8165               > initial_offset)
8166             return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
8167                                     _("Truncated protorev file detected"));
8168         }
8169     }
8170
8171   /* Fix up the property reps. */
8172   if (noderev->prop_rep && noderev->prop_rep->txn_id)
8173     {
8174       apr_hash_t *proplist;
8175       SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
8176
8177       noderev->prop_rep->txn_id = NULL;
8178       noderev->prop_rep->revision = rev;
8179
8180       if (ffd->deltify_properties)
8181         SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
8182                                      proplist, fs, noderev, reps_hash,
8183                                      TRUE, pool));
8184       else
8185         SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
8186                                fs, reps_hash, pool));
8187     }
8188
8189
8190   /* Convert our temporary ID into a permanent revision one. */
8191   SVN_ERR(get_file_offset(&my_offset, file, pool));
8192
8193   node_id = svn_fs_fs__id_node_id(noderev->id);
8194   if (*node_id == '_')
8195     {
8196       if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8197         my_node_id = apr_psprintf(pool, "%s-%ld", node_id + 1, rev);
8198       else
8199         {
8200           svn_fs_fs__add_keys(start_node_id, node_id + 1, my_node_id_buf);
8201           my_node_id = my_node_id_buf;
8202         }
8203     }
8204   else
8205     my_node_id = node_id;
8206
8207   copy_id = svn_fs_fs__id_copy_id(noderev->id);
8208   if (*copy_id == '_')
8209     {
8210       if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8211         my_copy_id = apr_psprintf(pool, "%s-%ld", copy_id + 1, rev);
8212       else
8213         {
8214           svn_fs_fs__add_keys(start_copy_id, copy_id + 1, my_copy_id_buf);
8215           my_copy_id = my_copy_id_buf;
8216         }
8217     }
8218   else
8219     my_copy_id = copy_id;
8220
8221   if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
8222     noderev->copyroot_rev = rev;
8223
8224   new_id = svn_fs_fs__id_rev_create(my_node_id, my_copy_id, rev, my_offset,
8225                                     pool);
8226
8227   noderev->id = new_id;
8228
8229   if (ffd->rep_sharing_allowed)
8230     {
8231       /* Save the data representation's hash in the rep cache. */
8232       if (   noderev->data_rep && noderev->kind == svn_node_file
8233           && noderev->data_rep->revision == rev)
8234         {
8235           SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8236           APR_ARRAY_PUSH(reps_to_cache, representation_t *)
8237             = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
8238         }
8239
8240       if (noderev->prop_rep && noderev->prop_rep->revision == rev)
8241         {
8242           /* Add new property reps to hash and on-disk cache. */
8243           representation_t *copy
8244             = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
8245
8246           SVN_ERR_ASSERT(reps_to_cache && reps_pool);
8247           APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
8248
8249           apr_hash_set(reps_hash,
8250                         copy->sha1_checksum->digest,
8251                         APR_SHA1_DIGESTSIZE,
8252                         copy);
8253         }
8254     }
8255
8256   /* don't serialize SHA1 for dirs to disk (waste of space) */
8257   if (noderev->data_rep && noderev->kind == svn_node_dir)
8258     noderev->data_rep->sha1_checksum = NULL;
8259
8260   /* don't serialize SHA1 for props to disk (waste of space) */
8261   if (noderev->prop_rep)
8262     noderev->prop_rep->sha1_checksum = NULL;
8263
8264   /* Workaround issue #4031: is-fresh-txn-root in revision files. */
8265   noderev->is_fresh_txn_root = FALSE;
8266
8267   /* Write out our new node-revision. */
8268   if (at_root)
8269     SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
8270
8271   SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(file, TRUE, pool),
8272                                    noderev, ffd->format,
8273                                    svn_fs_fs__fs_supports_mergeinfo(fs),
8274                                    pool));
8275
8276   /* Return our ID that references the revision file. */
8277   *new_id_p = noderev->id;
8278
8279   return SVN_NO_ERROR;
8280 }
8281
8282 /* Write the changed path info from transaction TXN_ID in filesystem
8283    FS to the permanent rev-file FILE.  *OFFSET_P is set the to offset
8284    in the file of the beginning of this information.  Perform
8285    temporary allocations in POOL. */
8286 static svn_error_t *
8287 write_final_changed_path_info(apr_off_t *offset_p,
8288                               apr_file_t *file,
8289                               svn_fs_t *fs,
8290                               const char *txn_id,
8291                               apr_pool_t *pool)
8292 {
8293   apr_hash_t *changed_paths;
8294   apr_off_t offset;
8295   apr_pool_t *iterpool = svn_pool_create(pool);
8296   fs_fs_data_t *ffd = fs->fsap_data;
8297   svn_boolean_t include_node_kinds =
8298       ffd->format >= SVN_FS_FS__MIN_KIND_IN_CHANGED_FORMAT;
8299   apr_array_header_t *sorted_changed_paths;
8300   int i;
8301
8302   SVN_ERR(get_file_offset(&offset, file, pool));
8303
8304   SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool));
8305   /* For the sake of the repository administrator sort the changes so
8306      that the final file is deterministic and repeatable, however the
8307      rest of the FSFS code doesn't require any particular order here. */
8308   sorted_changed_paths = svn_sort__hash(changed_paths,
8309                                         svn_sort_compare_items_lexically, pool);
8310
8311   /* Iterate through the changed paths one at a time, and convert the
8312      temporary node-id into a permanent one for each change entry. */
8313   for (i = 0; i < sorted_changed_paths->nelts; ++i)
8314     {
8315       node_revision_t *noderev;
8316       const svn_fs_id_t *id;
8317       svn_fs_path_change2_t *change;
8318       const char *path;
8319
8320       svn_pool_clear(iterpool);
8321
8322       change = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).value;
8323       path = APR_ARRAY_IDX(sorted_changed_paths, i, svn_sort__item_t).key;
8324
8325       id = change->node_rev_id;
8326
8327       /* If this was a delete of a mutable node, then it is OK to
8328          leave the change entry pointing to the non-existent temporary
8329          node, since it will never be used. */
8330       if ((change->change_kind != svn_fs_path_change_delete) &&
8331           (! svn_fs_fs__id_txn_id(id)))
8332         {
8333           SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, iterpool));
8334
8335           /* noderev has the permanent node-id at this point, so we just
8336              substitute it for the temporary one. */
8337           change->node_rev_id = noderev->id;
8338         }
8339
8340       /* Write out the new entry into the final rev-file. */
8341       SVN_ERR(write_change_entry(file, path, change, include_node_kinds,
8342                                  iterpool));
8343     }
8344
8345   svn_pool_destroy(iterpool);
8346
8347   *offset_p = offset;
8348
8349   return SVN_NO_ERROR;
8350 }
8351
8352 /* Atomically update the 'current' file to hold the specifed REV,
8353    NEXT_NODE_ID, and NEXT_COPY_ID.  (The two next-ID parameters are
8354    ignored and may be NULL if the FS format does not use them.)
8355    Perform temporary allocations in POOL. */
8356 static svn_error_t *
8357 write_current(svn_fs_t *fs, svn_revnum_t rev, const char *next_node_id,
8358               const char *next_copy_id, apr_pool_t *pool)
8359 {
8360   char *buf;
8361   const char *tmp_name, *name;
8362   fs_fs_data_t *ffd = fs->fsap_data;
8363
8364   /* Now we can just write out this line. */
8365   if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8366     buf = apr_psprintf(pool, "%ld\n", rev);
8367   else
8368     buf = apr_psprintf(pool, "%ld %s %s\n", rev, next_node_id, next_copy_id);
8369
8370   name = svn_fs_fs__path_current(fs, pool);
8371   SVN_ERR(svn_io_write_unique(&tmp_name,
8372                               svn_dirent_dirname(name, pool),
8373                               buf, strlen(buf),
8374                               svn_io_file_del_none, pool));
8375
8376   return move_into_place(tmp_name, name, name, pool);
8377 }
8378
8379 /* Open a new svn_fs_t handle to FS, set that handle's concept of "current
8380    youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
8381    NEW_REV's revision root.
8382
8383    Intended to be called as the very last step in a commit before 'current'
8384    is bumped.  This implies that we are holding the write lock. */
8385 static svn_error_t *
8386 verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
8387                                             svn_revnum_t new_rev,
8388                                             apr_pool_t *pool)
8389 {
8390 #ifdef SVN_DEBUG
8391   fs_fs_data_t *ffd = fs->fsap_data;
8392   svn_fs_t *ft; /* fs++ == ft */
8393   svn_fs_root_t *root;
8394   fs_fs_data_t *ft_ffd;
8395   apr_hash_t *fs_config;
8396
8397   SVN_ERR_ASSERT(ffd->svn_fs_open_);
8398
8399   /* make sure FT does not simply return data cached by other instances
8400    * but actually retrieves it from disk at least once.
8401    */
8402   fs_config = apr_hash_make(pool);
8403   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
8404                            svn_uuid_generate(pool));
8405   SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
8406                             fs_config,
8407                             pool));
8408   ft_ffd = ft->fsap_data;
8409   /* Don't let FT consult rep-cache.db, either. */
8410   ft_ffd->rep_sharing_allowed = FALSE;
8411
8412   /* Time travel! */
8413   ft_ffd->youngest_rev_cache = new_rev;
8414
8415   SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
8416   SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
8417   SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
8418   SVN_ERR(svn_fs_fs__verify_root(root, pool));
8419 #endif /* SVN_DEBUG */
8420
8421   return SVN_NO_ERROR;
8422 }
8423
8424 /* Update the 'current' file to hold the correct next node and copy_ids
8425    from transaction TXN_ID in filesystem FS.  The current revision is
8426    set to REV.  Perform temporary allocations in POOL. */
8427 static svn_error_t *
8428 write_final_current(svn_fs_t *fs,
8429                     const char *txn_id,
8430                     svn_revnum_t rev,
8431                     const char *start_node_id,
8432                     const char *start_copy_id,
8433                     apr_pool_t *pool)
8434 {
8435   const char *txn_node_id, *txn_copy_id;
8436   char new_node_id[MAX_KEY_SIZE + 2];
8437   char new_copy_id[MAX_KEY_SIZE + 2];
8438   fs_fs_data_t *ffd = fs->fsap_data;
8439
8440   if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8441     return write_current(fs, rev, NULL, NULL, pool);
8442
8443   /* To find the next available ids, we add the id that used to be in
8444      the 'current' file, to the next ids from the transaction file. */
8445   SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
8446
8447   svn_fs_fs__add_keys(start_node_id, txn_node_id, new_node_id);
8448   svn_fs_fs__add_keys(start_copy_id, txn_copy_id, new_copy_id);
8449
8450   return write_current(fs, rev, new_node_id, new_copy_id, pool);
8451 }
8452
8453 /* Verify that the user registed with FS has all the locks necessary to
8454    permit all the changes associate with TXN_NAME.
8455    The FS write lock is assumed to be held by the caller. */
8456 static svn_error_t *
8457 verify_locks(svn_fs_t *fs,
8458              const char *txn_name,
8459              apr_pool_t *pool)
8460 {
8461   apr_pool_t *subpool = svn_pool_create(pool);
8462   apr_hash_t *changes;
8463   apr_hash_index_t *hi;
8464   apr_array_header_t *changed_paths;
8465   svn_stringbuf_t *last_recursed = NULL;
8466   int i;
8467
8468   /* Fetch the changes for this transaction. */
8469   SVN_ERR(svn_fs_fs__txn_changes_fetch(&changes, fs, txn_name, pool));
8470
8471   /* Make an array of the changed paths, and sort them depth-first-ily.  */
8472   changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
8473                                  sizeof(const char *));
8474   for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
8475     APR_ARRAY_PUSH(changed_paths, const char *) = svn__apr_hash_index_key(hi);
8476   qsort(changed_paths->elts, changed_paths->nelts,
8477         changed_paths->elt_size, svn_sort_compare_paths);
8478
8479   /* Now, traverse the array of changed paths, verify locks.  Note
8480      that if we need to do a recursive verification a path, we'll skip
8481      over children of that path when we get to them. */
8482   for (i = 0; i < changed_paths->nelts; i++)
8483     {
8484       const char *path;
8485       svn_fs_path_change2_t *change;
8486       svn_boolean_t recurse = TRUE;
8487
8488       svn_pool_clear(subpool);
8489       path = APR_ARRAY_IDX(changed_paths, i, const char *);
8490
8491       /* If this path has already been verified as part of a recursive
8492          check of one of its parents, no need to do it again.  */
8493       if (last_recursed
8494           && svn_dirent_is_child(last_recursed->data, path, subpool))
8495         continue;
8496
8497       /* Fetch the change associated with our path.  */
8498       change = svn_hash_gets(changes, path);
8499
8500       /* What does it mean to succeed at lock verification for a given
8501          path?  For an existing file or directory getting modified
8502          (text, props), it means we hold the lock on the file or
8503          directory.  For paths being added or removed, we need to hold
8504          the locks for that path and any children of that path.
8505
8506          WHEW!  We have no reliable way to determine the node kind
8507          of deleted items, but fortunately we are going to do a
8508          recursive check on deleted paths regardless of their kind.  */
8509       if (change->change_kind == svn_fs_path_change_modify)
8510         recurse = FALSE;
8511       SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
8512                                                 subpool));
8513
8514       /* If we just did a recursive check, remember the path we
8515          checked (so children can be skipped).  */
8516       if (recurse)
8517         {
8518           if (! last_recursed)
8519             last_recursed = svn_stringbuf_create(path, pool);
8520           else
8521             svn_stringbuf_set(last_recursed, path);
8522         }
8523     }
8524   svn_pool_destroy(subpool);
8525   return SVN_NO_ERROR;
8526 }
8527
8528 /* Baton used for commit_body below. */
8529 struct commit_baton {
8530   svn_revnum_t *new_rev_p;
8531   svn_fs_t *fs;
8532   svn_fs_txn_t *txn;
8533   apr_array_header_t *reps_to_cache;
8534   apr_hash_t *reps_hash;
8535   apr_pool_t *reps_pool;
8536 };
8537
8538 /* The work-horse for svn_fs_fs__commit, called with the FS write lock.
8539    This implements the svn_fs_fs__with_write_lock() 'body' callback
8540    type.  BATON is a 'struct commit_baton *'. */
8541 static svn_error_t *
8542 commit_body(void *baton, apr_pool_t *pool)
8543 {
8544   struct commit_baton *cb = baton;
8545   fs_fs_data_t *ffd = cb->fs->fsap_data;
8546   const char *old_rev_filename, *rev_filename, *proto_filename;
8547   const char *revprop_filename, *final_revprop;
8548   const svn_fs_id_t *root_id, *new_root_id;
8549   const char *start_node_id = NULL, *start_copy_id = NULL;
8550   svn_revnum_t old_rev, new_rev;
8551   apr_file_t *proto_file;
8552   void *proto_file_lockcookie;
8553   apr_off_t initial_offset, changed_path_offset;
8554   char *buf;
8555   apr_hash_t *txnprops;
8556   apr_array_header_t *txnprop_list;
8557   svn_prop_t prop;
8558   svn_string_t date;
8559
8560   /* Get the current youngest revision. */
8561   SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
8562
8563   /* Check to make sure this transaction is based off the most recent
8564      revision. */
8565   if (cb->txn->base_rev != old_rev)
8566     return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
8567                             _("Transaction out of date"));
8568
8569   /* Locks may have been added (or stolen) between the calling of
8570      previous svn_fs.h functions and svn_fs_commit_txn(), so we need
8571      to re-examine every changed-path in the txn and re-verify all
8572      discovered locks. */
8573   SVN_ERR(verify_locks(cb->fs, cb->txn->id, pool));
8574
8575   /* Get the next node_id and copy_id to use. */
8576   if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
8577     SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
8578                                   pool));
8579
8580   /* We are going to be one better than this puny old revision. */
8581   new_rev = old_rev + 1;
8582
8583   /* Get a write handle on the proto revision file. */
8584   SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
8585                                  cb->fs, cb->txn->id, pool));
8586   SVN_ERR(get_file_offset(&initial_offset, proto_file, pool));
8587
8588   /* Write out all the node-revisions and directory contents. */
8589   root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
8590   SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
8591                           start_node_id, start_copy_id, initial_offset,
8592                           cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
8593                           TRUE, pool));
8594
8595   /* Write the changed-path information. */
8596   SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
8597                                         cb->fs, cb->txn->id, pool));
8598
8599   /* Write the final line. */
8600   buf = apr_psprintf(pool, "\n%" APR_OFF_T_FMT " %" APR_OFF_T_FMT "\n",
8601                      svn_fs_fs__id_offset(new_root_id),
8602                      changed_path_offset);
8603   SVN_ERR(svn_io_file_write_full(proto_file, buf, strlen(buf), NULL,
8604                                  pool));
8605   SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
8606   SVN_ERR(svn_io_file_close(proto_file, pool));
8607
8608   /* We don't unlock the prototype revision file immediately to avoid a
8609      race with another caller writing to the prototype revision file
8610      before we commit it. */
8611
8612   /* Remove any temporary txn props representing 'flags'. */
8613   SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, cb->txn, pool));
8614   txnprop_list = apr_array_make(pool, 3, sizeof(svn_prop_t));
8615   prop.value = NULL;
8616
8617   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
8618     {
8619       prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
8620       APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8621     }
8622
8623   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
8624     {
8625       prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
8626       APR_ARRAY_PUSH(txnprop_list, svn_prop_t) = prop;
8627     }
8628
8629   if (! apr_is_empty_array(txnprop_list))
8630     SVN_ERR(svn_fs_fs__change_txn_props(cb->txn, txnprop_list, pool));
8631
8632   /* Create the shard for the rev and revprop file, if we're sharding and
8633      this is the first revision of a new shard.  We don't care if this
8634      fails because the shard already existed for some reason. */
8635   if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
8636     {
8637       /* Create the revs shard. */
8638         {
8639           const char *new_dir = path_rev_shard(cb->fs, new_rev, pool);
8640           svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8641           if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8642             return svn_error_trace(err);
8643           svn_error_clear(err);
8644           SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8645                                                     PATH_REVS_DIR,
8646                                                     pool),
8647                                     new_dir, pool));
8648         }
8649
8650       /* Create the revprops shard. */
8651       SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8652         {
8653           const char *new_dir = path_revprops_shard(cb->fs, new_rev, pool);
8654           svn_error_t *err = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
8655           if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
8656             return svn_error_trace(err);
8657           svn_error_clear(err);
8658           SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
8659                                                     PATH_REVPROPS_DIR,
8660                                                     pool),
8661                                     new_dir, pool));
8662         }
8663     }
8664
8665   /* Move the finished rev file into place. */
8666   SVN_ERR(svn_fs_fs__path_rev_absolute(&old_rev_filename,
8667                                        cb->fs, old_rev, pool));
8668   rev_filename = path_rev(cb->fs, new_rev, pool);
8669   proto_filename = path_txn_proto_rev(cb->fs, cb->txn->id, pool);
8670   SVN_ERR(move_into_place(proto_filename, rev_filename, old_rev_filename,
8671                           pool));
8672
8673   /* Now that we've moved the prototype revision file out of the way,
8674      we can unlock it (since further attempts to write to the file
8675      will fail as it no longer exists).  We must do this so that we can
8676      remove the transaction directory later. */
8677   SVN_ERR(unlock_proto_rev(cb->fs, cb->txn->id, proto_file_lockcookie, pool));
8678
8679   /* Update commit time to ensure that svn:date revprops remain ordered. */
8680   date.data = svn_time_to_cstring(apr_time_now(), pool);
8681   date.len = strlen(date.data);
8682
8683   SVN_ERR(svn_fs_fs__change_txn_prop(cb->txn, SVN_PROP_REVISION_DATE,
8684                                      &date, pool));
8685
8686   /* Move the revprops file into place. */
8687   SVN_ERR_ASSERT(! is_packed_revprop(cb->fs, new_rev));
8688   revprop_filename = path_txn_props(cb->fs, cb->txn->id, pool);
8689   final_revprop = path_revprops(cb->fs, new_rev, pool);
8690   SVN_ERR(move_into_place(revprop_filename, final_revprop,
8691                           old_rev_filename, pool));
8692
8693   /* Update the 'current' file. */
8694   SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
8695   SVN_ERR(write_final_current(cb->fs, cb->txn->id, new_rev, start_node_id,
8696                               start_copy_id, pool));
8697
8698   /* At this point the new revision is committed and globally visible
8699      so let the caller know it succeeded by giving it the new revision
8700      number, which fulfills svn_fs_commit_txn() contract.  Any errors
8701      after this point do not change the fact that a new revision was
8702      created. */
8703   *cb->new_rev_p = new_rev;
8704
8705   ffd->youngest_rev_cache = new_rev;
8706
8707   /* Remove this transaction directory. */
8708   SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
8709
8710   return SVN_NO_ERROR;
8711 }
8712
8713 /* Add the representations in REPS_TO_CACHE (an array of representation_t *)
8714  * to the rep-cache database of FS. */
8715 static svn_error_t *
8716 write_reps_to_cache(svn_fs_t *fs,
8717                     const apr_array_header_t *reps_to_cache,
8718                     apr_pool_t *scratch_pool)
8719 {
8720   int i;
8721
8722   for (i = 0; i < reps_to_cache->nelts; i++)
8723     {
8724       representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
8725
8726       /* FALSE because we don't care if another parallel commit happened to
8727        * collide with us.  (Non-parallel collisions will not be detected.) */
8728       SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, FALSE, scratch_pool));
8729     }
8730
8731   return SVN_NO_ERROR;
8732 }
8733
8734 svn_error_t *
8735 svn_fs_fs__commit(svn_revnum_t *new_rev_p,
8736                   svn_fs_t *fs,
8737                   svn_fs_txn_t *txn,
8738                   apr_pool_t *pool)
8739 {
8740   struct commit_baton cb;
8741   fs_fs_data_t *ffd = fs->fsap_data;
8742
8743   cb.new_rev_p = new_rev_p;
8744   cb.fs = fs;
8745   cb.txn = txn;
8746
8747   if (ffd->rep_sharing_allowed)
8748     {
8749       cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
8750       cb.reps_hash = apr_hash_make(pool);
8751       cb.reps_pool = pool;
8752     }
8753   else
8754     {
8755       cb.reps_to_cache = NULL;
8756       cb.reps_hash = NULL;
8757       cb.reps_pool = NULL;
8758     }
8759
8760   SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
8761
8762   /* At this point, *NEW_REV_P has been set, so errors below won't affect
8763      the success of the commit.  (See svn_fs_commit_txn().)  */
8764
8765   if (ffd->rep_sharing_allowed)
8766     {
8767       SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
8768
8769       /* Write new entries to the rep-sharing database.
8770        *
8771        * We use an sqlite transaction to speed things up;
8772        * see <http://www.sqlite.org/faq.html#q19>.
8773        */
8774       SVN_SQLITE__WITH_TXN(
8775         write_reps_to_cache(fs, cb.reps_to_cache, pool),
8776         ffd->rep_cache_db);
8777     }
8778
8779   return SVN_NO_ERROR;
8780 }
8781
8782
8783 svn_error_t *
8784 svn_fs_fs__reserve_copy_id(const char **copy_id_p,
8785                            svn_fs_t *fs,
8786                            const char *txn_id,
8787                            apr_pool_t *pool)
8788 {
8789   const char *cur_node_id, *cur_copy_id;
8790   char *copy_id;
8791   apr_size_t len;
8792
8793   /* First read in the current next-ids file. */
8794   SVN_ERR(read_next_ids(&cur_node_id, &cur_copy_id, fs, txn_id, pool));
8795
8796   copy_id = apr_pcalloc(pool, strlen(cur_copy_id) + 2);
8797
8798   len = strlen(cur_copy_id);
8799   svn_fs_fs__next_key(cur_copy_id, &len, copy_id);
8800
8801   SVN_ERR(write_next_ids(fs, txn_id, cur_node_id, copy_id, pool));
8802
8803   *copy_id_p = apr_pstrcat(pool, "_", cur_copy_id, (char *)NULL);
8804
8805   return SVN_NO_ERROR;
8806 }
8807
8808 /* Write out the zeroth revision for filesystem FS. */
8809 static svn_error_t *
8810 write_revision_zero(svn_fs_t *fs)
8811 {
8812   const char *path_revision_zero = path_rev(fs, 0, fs->pool);
8813   apr_hash_t *proplist;
8814   svn_string_t date;
8815
8816   /* Write out a rev file for revision 0. */
8817   SVN_ERR(svn_io_file_create(path_revision_zero,
8818                              "PLAIN\nEND\nENDREP\n"
8819                              "id: 0.0.r0/17\n"
8820                              "type: dir\n"
8821                              "count: 0\n"
8822                              "text: 0 0 4 4 "
8823                              "2d2977d1c96f487abe4a1e202dd03b4e\n"
8824                              "cpath: /\n"
8825                              "\n\n17 107\n", fs->pool));
8826   SVN_ERR(svn_io_set_file_read_only(path_revision_zero, FALSE, fs->pool));
8827
8828   /* Set a date on revision 0. */
8829   date.data = svn_time_to_cstring(apr_time_now(), fs->pool);
8830   date.len = strlen(date.data);
8831   proplist = apr_hash_make(fs->pool);
8832   svn_hash_sets(proplist, SVN_PROP_REVISION_DATE, &date);
8833   return set_revision_proplist(fs, 0, proplist, fs->pool);
8834 }
8835
8836 svn_error_t *
8837 svn_fs_fs__create(svn_fs_t *fs,
8838                   const char *path,
8839                   apr_pool_t *pool)
8840 {
8841   int format = SVN_FS_FS__FORMAT_NUMBER;
8842   fs_fs_data_t *ffd = fs->fsap_data;
8843
8844   fs->path = apr_pstrdup(pool, path);
8845   /* See if compatibility with older versions was explicitly requested. */
8846   if (fs->config)
8847     {
8848       if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
8849         format = 1;
8850       else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
8851         format = 2;
8852       else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
8853         format = 3;
8854       else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE))
8855         format = 4;
8856     }
8857   ffd->format = format;
8858
8859   /* Override the default linear layout if this is a new-enough format. */
8860   if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT)
8861     ffd->max_files_per_dir = SVN_FS_FS_DEFAULT_MAX_FILES_PER_DIR;
8862
8863   /* Create the revision data directories. */
8864   if (ffd->max_files_per_dir)
8865     SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(fs, 0, pool), pool));
8866   else
8867     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_REVS_DIR,
8868                                                         pool),
8869                                         pool));
8870
8871   /* Create the revprops directory. */
8872   if (ffd->max_files_per_dir)
8873     SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(fs, 0, pool),
8874                                         pool));
8875   else
8876     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path,
8877                                                         PATH_REVPROPS_DIR,
8878                                                         pool),
8879                                         pool));
8880
8881   /* Create the transaction directory. */
8882   SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXNS_DIR,
8883                                                       pool),
8884                                       pool));
8885
8886   /* Create the protorevs directory. */
8887   if (format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
8888     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(path, PATH_TXN_PROTOS_DIR,
8889                                                       pool),
8890                                         pool));
8891
8892   /* Create the 'current' file. */
8893   SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(fs, pool),
8894                              (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
8895                               ? "0\n" : "0 1 1\n"),
8896                              pool));
8897   SVN_ERR(svn_io_file_create(path_lock(fs, pool), "", pool));
8898   SVN_ERR(svn_fs_fs__set_uuid(fs, NULL, pool));
8899
8900   SVN_ERR(write_revision_zero(fs));
8901
8902   /* Create the fsfs.conf file if supported.  Older server versions would
8903      simply ignore the file but that might result in a different behavior
8904      than with the later releases.  Also, hotcopy would ignore, i.e. not
8905      copy, a fsfs.conf with old formats. */
8906   if (ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
8907     SVN_ERR(write_config(fs, pool));
8908
8909   SVN_ERR(read_config(ffd, fs->path, pool));
8910
8911   /* Create the min unpacked rev file. */
8912   if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
8913     SVN_ERR(svn_io_file_create(path_min_unpacked_rev(fs, pool), "0\n", pool));
8914
8915   /* Create the txn-current file if the repository supports
8916      the transaction sequence file. */
8917   if (format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
8918     {
8919       SVN_ERR(svn_io_file_create(path_txn_current(fs, pool),
8920                                  "0\n", pool));
8921       SVN_ERR(svn_io_file_create(path_txn_current_lock(fs, pool),
8922                                  "", pool));
8923     }
8924
8925   /* This filesystem is ready.  Stamp it with a format number. */
8926   SVN_ERR(write_format(path_format(fs, pool),
8927                        ffd->format, ffd->max_files_per_dir, FALSE, pool));
8928
8929   ffd->youngest_rev_cache = 0;
8930   return SVN_NO_ERROR;
8931 }
8932
8933 /* Part of the recovery procedure.  Return the largest revision *REV in
8934    filesystem FS.  Use POOL for temporary allocation. */
8935 static svn_error_t *
8936 recover_get_largest_revision(svn_fs_t *fs, svn_revnum_t *rev, apr_pool_t *pool)
8937 {
8938   /* Discovering the largest revision in the filesystem would be an
8939      expensive operation if we did a readdir() or searched linearly,
8940      so we'll do a form of binary search.  left is a revision that we
8941      know exists, right a revision that we know does not exist. */
8942   apr_pool_t *iterpool;
8943   svn_revnum_t left, right = 1;
8944
8945   iterpool = svn_pool_create(pool);
8946   /* Keep doubling right, until we find a revision that doesn't exist. */
8947   while (1)
8948     {
8949       svn_error_t *err;
8950       apr_file_t *file;
8951
8952       err = open_pack_or_rev_file(&file, fs, right, iterpool);
8953       svn_pool_clear(iterpool);
8954
8955       if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8956         {
8957           svn_error_clear(err);
8958           break;
8959         }
8960       else
8961         SVN_ERR(err);
8962
8963       right <<= 1;
8964     }
8965
8966   left = right >> 1;
8967
8968   /* We know that left exists and right doesn't.  Do a normal bsearch to find
8969      the last revision. */
8970   while (left + 1 < right)
8971     {
8972       svn_revnum_t probe = left + ((right - left) / 2);
8973       svn_error_t *err;
8974       apr_file_t *file;
8975
8976       err = open_pack_or_rev_file(&file, fs, probe, iterpool);
8977       svn_pool_clear(iterpool);
8978
8979       if (err && err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
8980         {
8981           svn_error_clear(err);
8982           right = probe;
8983         }
8984       else
8985         {
8986           SVN_ERR(err);
8987           left = probe;
8988         }
8989     }
8990
8991   svn_pool_destroy(iterpool);
8992
8993   /* left is now the largest revision that exists. */
8994   *rev = left;
8995   return SVN_NO_ERROR;
8996 }
8997
8998 /* A baton for reading a fixed amount from an open file.  For
8999    recover_find_max_ids() below. */
9000 struct recover_read_from_file_baton
9001 {
9002   apr_file_t *file;
9003   apr_pool_t *pool;
9004   apr_off_t remaining;
9005 };
9006
9007 /* A stream read handler used by recover_find_max_ids() below.
9008    Read and return at most BATON->REMAINING bytes from the stream,
9009    returning nothing after that to indicate EOF. */
9010 static svn_error_t *
9011 read_handler_recover(void *baton, char *buffer, apr_size_t *len)
9012 {
9013   struct recover_read_from_file_baton *b = baton;
9014   svn_filesize_t bytes_to_read = *len;
9015
9016   if (b->remaining == 0)
9017     {
9018       /* Return a successful read of zero bytes to signal EOF. */
9019       *len = 0;
9020       return SVN_NO_ERROR;
9021     }
9022
9023   if (bytes_to_read > b->remaining)
9024     bytes_to_read = b->remaining;
9025   b->remaining -= bytes_to_read;
9026
9027   return svn_io_file_read_full2(b->file, buffer, (apr_size_t) bytes_to_read,
9028                                 len, NULL, b->pool);
9029 }
9030
9031 /* Part of the recovery procedure.  Read the directory noderev at offset
9032    OFFSET of file REV_FILE (the revision file of revision REV of
9033    filesystem FS), and set MAX_NODE_ID and MAX_COPY_ID to be the node-id
9034    and copy-id of that node, if greater than the current value stored
9035    in either.  Recurse into any child directories that were modified in
9036    this revision.
9037
9038    MAX_NODE_ID and MAX_COPY_ID must be arrays of at least MAX_KEY_SIZE.
9039
9040    Perform temporary allocation in POOL. */
9041 static svn_error_t *
9042 recover_find_max_ids(svn_fs_t *fs, svn_revnum_t rev,
9043                      apr_file_t *rev_file, apr_off_t offset,
9044                      char *max_node_id, char *max_copy_id,
9045                      apr_pool_t *pool)
9046 {
9047   apr_hash_t *headers;
9048   char *value;
9049   representation_t *data_rep;
9050   struct rep_args *ra;
9051   struct recover_read_from_file_baton baton;
9052   svn_stream_t *stream;
9053   apr_hash_t *entries;
9054   apr_hash_index_t *hi;
9055   apr_pool_t *iterpool;
9056
9057   SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9058   SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile2(rev_file, TRUE,
9059                                                                pool),
9060                             pool));
9061
9062   /* Check that this is a directory.  It should be. */
9063   value = svn_hash_gets(headers, HEADER_TYPE);
9064   if (value == NULL || strcmp(value, KIND_DIR) != 0)
9065     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9066                             _("Recovery encountered a non-directory node"));
9067
9068   /* Get the data location.  No data location indicates an empty directory. */
9069   value = svn_hash_gets(headers, HEADER_TEXT);
9070   if (!value)
9071     return SVN_NO_ERROR;
9072   SVN_ERR(read_rep_offsets(&data_rep, value, NULL, FALSE, pool));
9073
9074   /* If the directory's data representation wasn't changed in this revision,
9075      we've already scanned the directory's contents for noderevs, so we don't
9076      need to again.  This will occur if a property is changed on a directory
9077      without changing the directory's contents. */
9078   if (data_rep->revision != rev)
9079     return SVN_NO_ERROR;
9080
9081   /* We could use get_dir_contents(), but this is much cheaper.  It does
9082      rely on directory entries being stored as PLAIN reps, though. */
9083   offset = data_rep->offset;
9084   SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
9085   SVN_ERR(read_rep_line(&ra, rev_file, pool));
9086   if (ra->is_delta)
9087     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9088                             _("Recovery encountered a deltified directory "
9089                               "representation"));
9090
9091   /* Now create a stream that's allowed to read only as much data as is
9092      stored in the representation. */
9093   baton.file = rev_file;
9094   baton.pool = pool;
9095   baton.remaining = data_rep->expanded_size
9096                   ? data_rep->expanded_size
9097                   : data_rep->size;
9098   stream = svn_stream_create(&baton, pool);
9099   svn_stream_set_read(stream, read_handler_recover);
9100
9101   /* Now read the entries from that stream. */
9102   entries = apr_hash_make(pool);
9103   SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
9104   SVN_ERR(svn_stream_close(stream));
9105
9106   /* Now check each of the entries in our directory to find new node and
9107      copy ids, and recurse into new subdirectories. */
9108   iterpool = svn_pool_create(pool);
9109   for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
9110     {
9111       char *str_val;
9112       char *str;
9113       svn_node_kind_t kind;
9114       svn_fs_id_t *id;
9115       const char *node_id, *copy_id;
9116       apr_off_t child_dir_offset;
9117       const svn_string_t *path = svn__apr_hash_index_val(hi);
9118
9119       svn_pool_clear(iterpool);
9120
9121       str_val = apr_pstrdup(iterpool, path->data);
9122
9123       str = svn_cstring_tokenize(" ", &str_val);
9124       if (str == NULL)
9125         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9126                                 _("Directory entry corrupt"));
9127
9128       if (strcmp(str, KIND_FILE) == 0)
9129         kind = svn_node_file;
9130       else if (strcmp(str, KIND_DIR) == 0)
9131         kind = svn_node_dir;
9132       else
9133         {
9134           return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9135                                   _("Directory entry corrupt"));
9136         }
9137
9138       str = svn_cstring_tokenize(" ", &str_val);
9139       if (str == NULL)
9140         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
9141                                 _("Directory entry corrupt"));
9142
9143       id = svn_fs_fs__id_parse(str, strlen(str), iterpool);
9144
9145       if (svn_fs_fs__id_rev(id) != rev)
9146         {
9147           /* If the node wasn't modified in this revision, we've already
9148              checked the node and copy id. */
9149           continue;
9150         }
9151
9152       node_id = svn_fs_fs__id_node_id(id);
9153       copy_id = svn_fs_fs__id_copy_id(id);
9154
9155       if (svn_fs_fs__key_compare(node_id, max_node_id) > 0)
9156         {
9157           SVN_ERR_ASSERT(strlen(node_id) < MAX_KEY_SIZE);
9158           apr_cpystrn(max_node_id, node_id, MAX_KEY_SIZE);
9159         }
9160       if (svn_fs_fs__key_compare(copy_id, max_copy_id) > 0)
9161         {
9162           SVN_ERR_ASSERT(strlen(copy_id) < MAX_KEY_SIZE);
9163           apr_cpystrn(max_copy_id, copy_id, MAX_KEY_SIZE);
9164         }
9165
9166       if (kind == svn_node_file)
9167         continue;
9168
9169       child_dir_offset = svn_fs_fs__id_offset(id);
9170       SVN_ERR(recover_find_max_ids(fs, rev, rev_file, child_dir_offset,
9171                                    max_node_id, max_copy_id, iterpool));
9172     }
9173   svn_pool_destroy(iterpool);
9174
9175   return SVN_NO_ERROR;
9176 }
9177
9178 /* Return TRUE, if for REVISION in FS, we can find the revprop pack file.
9179  * Use POOL for temporary allocations.
9180  * Set *MISSING, if the reason is a missing manifest or pack file.
9181  */
9182 static svn_boolean_t
9183 packed_revprop_available(svn_boolean_t *missing,
9184                          svn_fs_t *fs,
9185                          svn_revnum_t revision,
9186                          apr_pool_t *pool)
9187 {
9188   fs_fs_data_t *ffd = fs->fsap_data;
9189   svn_stringbuf_t *content = NULL;
9190
9191   /* try to read the manifest file */
9192   const char *folder = path_revprops_pack_shard(fs, revision, pool);
9193   const char *manifest_path = svn_dirent_join(folder, PATH_MANIFEST, pool);
9194
9195   svn_error_t *err = try_stringbuf_from_file(&content,
9196                                              missing,
9197                                              manifest_path,
9198                                              FALSE,
9199                                              pool);
9200
9201   /* if the manifest cannot be read, consider the pack files inaccessible
9202    * even if the file itself exists. */
9203   if (err)
9204     {
9205       svn_error_clear(err);
9206       return FALSE;
9207     }
9208
9209   if (*missing)
9210     return FALSE;
9211
9212   /* parse manifest content until we find the entry for REVISION.
9213    * Revision 0 is never packed. */
9214   revision = revision < ffd->max_files_per_dir
9215            ? revision - 1
9216            : revision % ffd->max_files_per_dir;
9217   while (content->data)
9218     {
9219       char *next = strchr(content->data, '\n');
9220       if (next)
9221         {
9222           *next = 0;
9223           ++next;
9224         }
9225
9226       if (revision-- == 0)
9227         {
9228           /* the respective pack file must exist (and be a file) */
9229           svn_node_kind_t kind;
9230           err = svn_io_check_path(svn_dirent_join(folder, content->data,
9231                                                   pool),
9232                                   &kind, pool);
9233           if (err)
9234             {
9235               svn_error_clear(err);
9236               return FALSE;
9237             }
9238
9239           *missing = kind == svn_node_none;
9240           return kind == svn_node_file;
9241         }
9242
9243       content->data = next;
9244     }
9245
9246   return FALSE;
9247 }
9248
9249 /* Baton used for recover_body below. */
9250 struct recover_baton {
9251   svn_fs_t *fs;
9252   svn_cancel_func_t cancel_func;
9253   void *cancel_baton;
9254 };
9255
9256 /* The work-horse for svn_fs_fs__recover, called with the FS
9257    write lock.  This implements the svn_fs_fs__with_write_lock()
9258    'body' callback type.  BATON is a 'struct recover_baton *'. */
9259 static svn_error_t *
9260 recover_body(void *baton, apr_pool_t *pool)
9261 {
9262   struct recover_baton *b = baton;
9263   svn_fs_t *fs = b->fs;
9264   fs_fs_data_t *ffd = fs->fsap_data;
9265   svn_revnum_t max_rev;
9266   char next_node_id_buf[MAX_KEY_SIZE], next_copy_id_buf[MAX_KEY_SIZE];
9267   char *next_node_id = NULL, *next_copy_id = NULL;
9268   svn_revnum_t youngest_rev;
9269   svn_node_kind_t youngest_revprops_kind;
9270
9271   /* Lose potentially corrupted data in temp files */
9272   SVN_ERR(cleanup_revprop_namespace(fs));
9273
9274   /* We need to know the largest revision in the filesystem. */
9275   SVN_ERR(recover_get_largest_revision(fs, &max_rev, pool));
9276
9277   /* Get the expected youngest revision */
9278   SVN_ERR(get_youngest(&youngest_rev, fs->path, pool));
9279
9280   /* Policy note:
9281
9282      Since the revprops file is written after the revs file, the true
9283      maximum available revision is the youngest one for which both are
9284      present.  That's probably the same as the max_rev we just found,
9285      but if it's not, we could, in theory, repeatedly decrement
9286      max_rev until we find a revision that has both a revs and
9287      revprops file, then write db/current with that.
9288
9289      But we choose not to.  If a repository is so corrupt that it's
9290      missing at least one revprops file, we shouldn't assume that the
9291      youngest revision for which both the revs and revprops files are
9292      present is healthy.  In other words, we're willing to recover
9293      from a missing or out-of-date db/current file, because db/current
9294      is truly redundant -- it's basically a cache so we don't have to
9295      find max_rev each time, albeit a cache with unusual semantics,
9296      since it also officially defines when a revision goes live.  But
9297      if we're missing more than the cache, it's time to back out and
9298      let the admin reconstruct things by hand: correctness at that
9299      point may depend on external things like checking a commit email
9300      list, looking in particular working copies, etc.
9301
9302      This policy matches well with a typical naive backup scenario.
9303      Say you're rsyncing your FSFS repository nightly to the same
9304      location.  Once revs and revprops are written, you've got the
9305      maximum rev; if the backup should bomb before db/current is
9306      written, then db/current could stay arbitrarily out-of-date, but
9307      we can still recover.  It's a small window, but we might as well
9308      do what we can. */
9309
9310   /* Even if db/current were missing, it would be created with 0 by
9311      get_youngest(), so this conditional remains valid. */
9312   if (youngest_rev > max_rev)
9313     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9314                              _("Expected current rev to be <= %ld "
9315                                "but found %ld"), max_rev, youngest_rev);
9316
9317   /* We only need to search for maximum IDs for old FS formats which
9318      se global ID counters. */
9319   if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
9320     {
9321       /* Next we need to find the maximum node id and copy id in use across the
9322          filesystem.  Unfortunately, the only way we can get this information
9323          is to scan all the noderevs of all the revisions and keep track as
9324          we go along. */
9325       svn_revnum_t rev;
9326       apr_pool_t *iterpool = svn_pool_create(pool);
9327       char max_node_id[MAX_KEY_SIZE] = "0", max_copy_id[MAX_KEY_SIZE] = "0";
9328       apr_size_t len;
9329
9330       for (rev = 0; rev <= max_rev; rev++)
9331         {
9332           apr_file_t *rev_file;
9333           apr_off_t root_offset;
9334
9335           svn_pool_clear(iterpool);
9336
9337           if (b->cancel_func)
9338             SVN_ERR(b->cancel_func(b->cancel_baton));
9339
9340           SVN_ERR(open_pack_or_rev_file(&rev_file, fs, rev, iterpool));
9341           SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file, fs, rev,
9342                                           iterpool));
9343           SVN_ERR(recover_find_max_ids(fs, rev, rev_file, root_offset,
9344                                        max_node_id, max_copy_id, iterpool));
9345           SVN_ERR(svn_io_file_close(rev_file, iterpool));
9346         }
9347       svn_pool_destroy(iterpool);
9348
9349       /* Now that we finally have the maximum revision, node-id and copy-id, we
9350          can bump the two ids to get the next of each. */
9351       len = strlen(max_node_id);
9352       svn_fs_fs__next_key(max_node_id, &len, next_node_id_buf);
9353       next_node_id = next_node_id_buf;
9354       len = strlen(max_copy_id);
9355       svn_fs_fs__next_key(max_copy_id, &len, next_copy_id_buf);
9356       next_copy_id = next_copy_id_buf;
9357     }
9358
9359   /* Before setting current, verify that there is a revprops file
9360      for the youngest revision.  (Issue #2992) */
9361   SVN_ERR(svn_io_check_path(path_revprops(fs, max_rev, pool),
9362                             &youngest_revprops_kind, pool));
9363   if (youngest_revprops_kind == svn_node_none)
9364     {
9365       svn_boolean_t missing = TRUE;
9366       if (!packed_revprop_available(&missing, fs, max_rev, pool))
9367         {
9368           if (missing)
9369             {
9370               return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9371                                       _("Revision %ld has a revs file but no "
9372                                         "revprops file"),
9373                                       max_rev);
9374             }
9375           else
9376             {
9377               return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9378                                       _("Revision %ld has a revs file but the "
9379                                         "revprops file is inaccessible"),
9380                                       max_rev);
9381             }
9382           }
9383     }
9384   else if (youngest_revprops_kind != svn_node_file)
9385     {
9386       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9387                                _("Revision %ld has a non-file where its "
9388                                  "revprops file should be"),
9389                                max_rev);
9390     }
9391
9392   /* Prune younger-than-(newfound-youngest) revisions from the rep
9393      cache if sharing is enabled taking care not to create the cache
9394      if it does not exist. */
9395   if (ffd->rep_sharing_allowed)
9396     {
9397       svn_boolean_t rep_cache_exists;
9398
9399       SVN_ERR(svn_fs_fs__exists_rep_cache(&rep_cache_exists, fs, pool));
9400       if (rep_cache_exists)
9401         SVN_ERR(svn_fs_fs__del_rep_reference(fs, max_rev, pool));
9402     }
9403
9404   /* Now store the discovered youngest revision, and the next IDs if
9405      relevant, in a new 'current' file. */
9406   return write_current(fs, max_rev, next_node_id, next_copy_id, pool);
9407 }
9408
9409 /* This implements the fs_library_vtable_t.recover() API. */
9410 svn_error_t *
9411 svn_fs_fs__recover(svn_fs_t *fs,
9412                    svn_cancel_func_t cancel_func, void *cancel_baton,
9413                    apr_pool_t *pool)
9414 {
9415   struct recover_baton b;
9416
9417   /* We have no way to take out an exclusive lock in FSFS, so we're
9418      restricted as to the types of recovery we can do.  Luckily,
9419      we just want to recreate the 'current' file, and we can do that just
9420      by blocking other writers. */
9421   b.fs = fs;
9422   b.cancel_func = cancel_func;
9423   b.cancel_baton = cancel_baton;
9424   return svn_fs_fs__with_write_lock(fs, recover_body, &b, pool);
9425 }
9426
9427 svn_error_t *
9428 svn_fs_fs__set_uuid(svn_fs_t *fs,
9429                     const char *uuid,
9430                     apr_pool_t *pool)
9431 {
9432   char *my_uuid;
9433   apr_size_t my_uuid_len;
9434   const char *tmp_path;
9435   const char *uuid_path = path_uuid(fs, pool);
9436
9437   if (! uuid)
9438     uuid = svn_uuid_generate(pool);
9439
9440   /* Make sure we have a copy in FS->POOL, and append a newline. */
9441   my_uuid = apr_pstrcat(fs->pool, uuid, "\n", (char *)NULL);
9442   my_uuid_len = strlen(my_uuid);
9443
9444   SVN_ERR(svn_io_write_unique(&tmp_path,
9445                               svn_dirent_dirname(uuid_path, pool),
9446                               my_uuid, my_uuid_len,
9447                               svn_io_file_del_none, pool));
9448
9449   /* We use the permissions of the 'current' file, because the 'uuid'
9450      file does not exist during repository creation. */
9451   SVN_ERR(move_into_place(tmp_path, uuid_path,
9452                           svn_fs_fs__path_current(fs, pool), pool));
9453
9454   /* Remove the newline we added, and stash the UUID. */
9455   my_uuid[my_uuid_len - 1] = '\0';
9456   fs->uuid = my_uuid;
9457
9458   return SVN_NO_ERROR;
9459 }
9460
9461 /** Node origin lazy cache. */
9462
9463 /* If directory PATH does not exist, create it and give it the same
9464    permissions as FS_path.*/
9465 svn_error_t *
9466 svn_fs_fs__ensure_dir_exists(const char *path,
9467                              const char *fs_path,
9468                              apr_pool_t *pool)
9469 {
9470   svn_error_t *err = svn_io_dir_make(path, APR_OS_DEFAULT, pool);
9471   if (err && APR_STATUS_IS_EEXIST(err->apr_err))
9472     {
9473       svn_error_clear(err);
9474       return SVN_NO_ERROR;
9475     }
9476   SVN_ERR(err);
9477
9478   /* We successfully created a new directory.  Dup the permissions
9479      from FS->path. */
9480   return svn_io_copy_perms(fs_path, path, pool);
9481 }
9482
9483 /* Set *NODE_ORIGINS to a hash mapping 'const char *' node IDs to
9484    'svn_string_t *' node revision IDs.  Use POOL for allocations. */
9485 static svn_error_t *
9486 get_node_origins_from_file(svn_fs_t *fs,
9487                            apr_hash_t **node_origins,
9488                            const char *node_origins_file,
9489                            apr_pool_t *pool)
9490 {
9491   apr_file_t *fd;
9492   svn_error_t *err;
9493   svn_stream_t *stream;
9494
9495   *node_origins = NULL;
9496   err = svn_io_file_open(&fd, node_origins_file,
9497                          APR_READ, APR_OS_DEFAULT, pool);
9498   if (err && APR_STATUS_IS_ENOENT(err->apr_err))
9499     {
9500       svn_error_clear(err);
9501       return SVN_NO_ERROR;
9502     }
9503   SVN_ERR(err);
9504
9505   stream = svn_stream_from_aprfile2(fd, FALSE, pool);
9506   *node_origins = apr_hash_make(pool);
9507   SVN_ERR(svn_hash_read2(*node_origins, stream, SVN_HASH_TERMINATOR, pool));
9508   return svn_stream_close(stream);
9509 }
9510
9511 svn_error_t *
9512 svn_fs_fs__get_node_origin(const svn_fs_id_t **origin_id,
9513                            svn_fs_t *fs,
9514                            const char *node_id,
9515                            apr_pool_t *pool)
9516 {
9517   apr_hash_t *node_origins;
9518
9519   *origin_id = NULL;
9520   SVN_ERR(get_node_origins_from_file(fs, &node_origins,
9521                                      path_node_origin(fs, node_id, pool),
9522                                      pool));
9523   if (node_origins)
9524     {
9525       svn_string_t *origin_id_str =
9526         svn_hash_gets(node_origins, node_id);
9527       if (origin_id_str)
9528         *origin_id = svn_fs_fs__id_parse(origin_id_str->data,
9529                                          origin_id_str->len, pool);
9530     }
9531   return SVN_NO_ERROR;
9532 }
9533
9534
9535 /* Helper for svn_fs_fs__set_node_origin.  Takes a NODE_ID/NODE_REV_ID
9536    pair and adds it to the NODE_ORIGINS_PATH file.  */
9537 static svn_error_t *
9538 set_node_origins_for_file(svn_fs_t *fs,
9539                           const char *node_origins_path,
9540                           const char *node_id,
9541                           svn_string_t *node_rev_id,
9542                           apr_pool_t *pool)
9543 {
9544   const char *path_tmp;
9545   svn_stream_t *stream;
9546   apr_hash_t *origins_hash;
9547   svn_string_t *old_node_rev_id;
9548
9549   SVN_ERR(svn_fs_fs__ensure_dir_exists(svn_dirent_join(fs->path,
9550                                                        PATH_NODE_ORIGINS_DIR,
9551                                                        pool),
9552                                        fs->path, pool));
9553
9554   /* Read the previously existing origins (if any), and merge our
9555      update with it. */
9556   SVN_ERR(get_node_origins_from_file(fs, &origins_hash,
9557                                      node_origins_path, pool));
9558   if (! origins_hash)
9559     origins_hash = apr_hash_make(pool);
9560
9561   old_node_rev_id = svn_hash_gets(origins_hash, node_id);
9562
9563   if (old_node_rev_id && !svn_string_compare(node_rev_id, old_node_rev_id))
9564     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
9565                              _("Node origin for '%s' exists with a different "
9566                                "value (%s) than what we were about to store "
9567                                "(%s)"),
9568                              node_id, old_node_rev_id->data, node_rev_id->data);
9569
9570   svn_hash_sets(origins_hash, node_id, node_rev_id);
9571
9572   /* Sure, there's a race condition here.  Two processes could be
9573      trying to add different cache elements to the same file at the
9574      same time, and the entries added by the first one to write will
9575      be lost.  But this is just a cache of reconstructible data, so
9576      we'll accept this problem in return for not having to deal with
9577      locking overhead. */
9578
9579   /* Create a temporary file, write out our hash, and close the file. */
9580   SVN_ERR(svn_stream_open_unique(&stream, &path_tmp,
9581                                  svn_dirent_dirname(node_origins_path, pool),
9582                                  svn_io_file_del_none, pool, pool));
9583   SVN_ERR(svn_hash_write2(origins_hash, stream, SVN_HASH_TERMINATOR, pool));
9584   SVN_ERR(svn_stream_close(stream));
9585
9586   /* Rename the temp file as the real destination */
9587   return svn_io_file_rename(path_tmp, node_origins_path, pool);
9588 }
9589
9590
9591 svn_error_t *
9592 svn_fs_fs__set_node_origin(svn_fs_t *fs,
9593                            const char *node_id,
9594                            const svn_fs_id_t *node_rev_id,
9595                            apr_pool_t *pool)
9596 {
9597   svn_error_t *err;
9598   const char *filename = path_node_origin(fs, node_id, pool);
9599
9600   err = set_node_origins_for_file(fs, filename,
9601                                   node_id,
9602                                   svn_fs_fs__id_unparse(node_rev_id, pool),
9603                                   pool);
9604   if (err && APR_STATUS_IS_EACCES(err->apr_err))
9605     {
9606       /* It's just a cache; stop trying if I can't write. */
9607       svn_error_clear(err);
9608       err = NULL;
9609     }
9610   return svn_error_trace(err);
9611 }
9612
9613
9614 svn_error_t *
9615 svn_fs_fs__list_transactions(apr_array_header_t **names_p,
9616                              svn_fs_t *fs,
9617                              apr_pool_t *pool)
9618 {
9619   const char *txn_dir;
9620   apr_hash_t *dirents;
9621   apr_hash_index_t *hi;
9622   apr_array_header_t *names;
9623   apr_size_t ext_len = strlen(PATH_EXT_TXN);
9624
9625   names = apr_array_make(pool, 1, sizeof(const char *));
9626
9627   /* Get the transactions directory. */
9628   txn_dir = svn_dirent_join(fs->path, PATH_TXNS_DIR, pool);
9629
9630   /* Now find a listing of this directory. */
9631   SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
9632
9633   /* Loop through all the entries and return anything that ends with '.txn'. */
9634   for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
9635     {
9636       const char *name = svn__apr_hash_index_key(hi);
9637       apr_ssize_t klen = svn__apr_hash_index_klen(hi);
9638       const char *id;
9639
9640       /* The name must end with ".txn" to be considered a transaction. */
9641       if ((apr_size_t) klen <= ext_len
9642           || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
9643         continue;
9644
9645       /* Truncate the ".txn" extension and store the ID. */
9646       id = apr_pstrndup(pool, name, strlen(name) - ext_len);
9647       APR_ARRAY_PUSH(names, const char *) = id;
9648     }
9649
9650   *names_p = names;
9651
9652   return SVN_NO_ERROR;
9653 }
9654
9655 svn_error_t *
9656 svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
9657                     svn_fs_t *fs,
9658                     const char *name,
9659                     apr_pool_t *pool)
9660 {
9661   svn_fs_txn_t *txn;
9662   svn_node_kind_t kind;
9663   transaction_t *local_txn;
9664
9665   /* First check to see if the directory exists. */
9666   SVN_ERR(svn_io_check_path(path_txn_dir(fs, name, pool), &kind, pool));
9667
9668   /* Did we find it? */
9669   if (kind != svn_node_dir)
9670     return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
9671                              _("No such transaction '%s'"),
9672                              name);
9673
9674   txn = apr_pcalloc(pool, sizeof(*txn));
9675
9676   /* Read in the root node of this transaction. */
9677   txn->id = apr_pstrdup(pool, name);
9678   txn->fs = fs;
9679
9680   SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, name, pool));
9681
9682   txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
9683
9684   txn->vtable = &txn_vtable;
9685   *txn_p = txn;
9686
9687   return SVN_NO_ERROR;
9688 }
9689
9690 svn_error_t *
9691 svn_fs_fs__txn_proplist(apr_hash_t **table_p,
9692                         svn_fs_txn_t *txn,
9693                         apr_pool_t *pool)
9694 {
9695   apr_hash_t *proplist = apr_hash_make(pool);
9696   SVN_ERR(get_txn_proplist(proplist, txn->fs, txn->id, pool));
9697   *table_p = proplist;
9698
9699   return SVN_NO_ERROR;
9700 }
9701
9702 svn_error_t *
9703 svn_fs_fs__delete_node_revision(svn_fs_t *fs,
9704                                 const svn_fs_id_t *id,
9705                                 apr_pool_t *pool)
9706 {
9707   node_revision_t *noderev;
9708
9709   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool));
9710
9711   /* Delete any mutable property representation. */
9712   if (noderev->prop_rep && noderev->prop_rep->txn_id)
9713     SVN_ERR(svn_io_remove_file2(path_txn_node_props(fs, id, pool), FALSE,
9714                                 pool));
9715
9716   /* Delete any mutable data representation. */
9717   if (noderev->data_rep && noderev->data_rep->txn_id
9718       && noderev->kind == svn_node_dir)
9719     {
9720       fs_fs_data_t *ffd = fs->fsap_data;
9721       SVN_ERR(svn_io_remove_file2(path_txn_node_children(fs, id, pool), FALSE,
9722                                   pool));
9723
9724       /* remove the corresponding entry from the cache, if such exists */
9725       if (ffd->txn_dir_cache)
9726         {
9727           const char *key = svn_fs_fs__id_unparse(id, pool)->data;
9728           SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
9729         }
9730     }
9731
9732   return svn_io_remove_file2(path_txn_node_rev(fs, id, pool), FALSE, pool);
9733 }
9734
9735
9736 \f
9737 /*** Revisions ***/
9738
9739 svn_error_t *
9740 svn_fs_fs__revision_prop(svn_string_t **value_p,
9741                          svn_fs_t *fs,
9742                          svn_revnum_t rev,
9743                          const char *propname,
9744                          apr_pool_t *pool)
9745 {
9746   apr_hash_t *table;
9747
9748   SVN_ERR(svn_fs__check_fs(fs, TRUE));
9749   SVN_ERR(svn_fs_fs__revision_proplist(&table, fs, rev, pool));
9750
9751   *value_p = svn_hash_gets(table, propname);
9752
9753   return SVN_NO_ERROR;
9754 }
9755
9756
9757 /* Baton used for change_rev_prop_body below. */
9758 struct change_rev_prop_baton {
9759   svn_fs_t *fs;
9760   svn_revnum_t rev;
9761   const char *name;
9762   const svn_string_t *const *old_value_p;
9763   const svn_string_t *value;
9764 };
9765
9766 /* The work-horse for svn_fs_fs__change_rev_prop, called with the FS
9767    write lock.  This implements the svn_fs_fs__with_write_lock()
9768    'body' callback type.  BATON is a 'struct change_rev_prop_baton *'. */
9769 static svn_error_t *
9770 change_rev_prop_body(void *baton, apr_pool_t *pool)
9771 {
9772   struct change_rev_prop_baton *cb = baton;
9773   apr_hash_t *table;
9774
9775   SVN_ERR(svn_fs_fs__revision_proplist(&table, cb->fs, cb->rev, pool));
9776
9777   if (cb->old_value_p)
9778     {
9779       const svn_string_t *wanted_value = *cb->old_value_p;
9780       const svn_string_t *present_value = svn_hash_gets(table, cb->name);
9781       if ((!wanted_value != !present_value)
9782           || (wanted_value && present_value
9783               && !svn_string_compare(wanted_value, present_value)))
9784         {
9785           /* What we expected isn't what we found. */
9786           return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
9787                                    _("revprop '%s' has unexpected value in "
9788                                      "filesystem"),
9789                                    cb->name);
9790         }
9791       /* Fall through. */
9792     }
9793   svn_hash_sets(table, cb->name, cb->value);
9794
9795   return set_revision_proplist(cb->fs, cb->rev, table, pool);
9796 }
9797
9798 svn_error_t *
9799 svn_fs_fs__change_rev_prop(svn_fs_t *fs,
9800                            svn_revnum_t rev,
9801                            const char *name,
9802                            const svn_string_t *const *old_value_p,
9803                            const svn_string_t *value,
9804                            apr_pool_t *pool)
9805 {
9806   struct change_rev_prop_baton cb;
9807
9808   SVN_ERR(svn_fs__check_fs(fs, TRUE));
9809
9810   cb.fs = fs;
9811   cb.rev = rev;
9812   cb.name = name;
9813   cb.old_value_p = old_value_p;
9814   cb.value = value;
9815
9816   return svn_fs_fs__with_write_lock(fs, change_rev_prop_body, &cb, pool);
9817 }
9818
9819
9820 \f
9821 /*** Transactions ***/
9822
9823 svn_error_t *
9824 svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
9825                        const svn_fs_id_t **base_root_id_p,
9826                        svn_fs_t *fs,
9827                        const char *txn_name,
9828                        apr_pool_t *pool)
9829 {
9830   transaction_t *txn;
9831   SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_name, pool));
9832   *root_id_p = txn->root_id;
9833   *base_root_id_p = txn->base_id;
9834   return SVN_NO_ERROR;
9835 }
9836
9837 \f
9838 /* Generic transaction operations.  */
9839
9840 svn_error_t *
9841 svn_fs_fs__txn_prop(svn_string_t **value_p,
9842                     svn_fs_txn_t *txn,
9843                     const char *propname,
9844                     apr_pool_t *pool)
9845 {
9846   apr_hash_t *table;
9847   svn_fs_t *fs = txn->fs;
9848
9849   SVN_ERR(svn_fs__check_fs(fs, TRUE));
9850   SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
9851
9852   *value_p = svn_hash_gets(table, propname);
9853
9854   return SVN_NO_ERROR;
9855 }
9856
9857 svn_error_t *
9858 svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
9859                      svn_fs_t *fs,
9860                      svn_revnum_t rev,
9861                      apr_uint32_t flags,
9862                      apr_pool_t *pool)
9863 {
9864   svn_string_t date;
9865   svn_prop_t prop;
9866   apr_array_header_t *props = apr_array_make(pool, 3, sizeof(svn_prop_t));
9867
9868   SVN_ERR(svn_fs__check_fs(fs, TRUE));
9869
9870   SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
9871
9872   /* Put a datestamp on the newly created txn, so we always know
9873      exactly how old it is.  (This will help sysadmins identify
9874      long-abandoned txns that may need to be manually removed.)  When
9875      a txn is promoted to a revision, this property will be
9876      automatically overwritten with a revision datestamp. */
9877   date.data = svn_time_to_cstring(apr_time_now(), pool);
9878   date.len = strlen(date.data);
9879
9880   prop.name = SVN_PROP_REVISION_DATE;
9881   prop.value = &date;
9882   APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9883
9884   /* Set temporary txn props that represent the requested 'flags'
9885      behaviors. */
9886   if (flags & SVN_FS_TXN_CHECK_OOD)
9887     {
9888       prop.name = SVN_FS__PROP_TXN_CHECK_OOD;
9889       prop.value = svn_string_create("true", pool);
9890       APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9891     }
9892
9893   if (flags & SVN_FS_TXN_CHECK_LOCKS)
9894     {
9895       prop.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
9896       prop.value = svn_string_create("true", pool);
9897       APR_ARRAY_PUSH(props, svn_prop_t) = prop;
9898     }
9899
9900   return svn_fs_fs__change_txn_props(*txn_p, props, pool);
9901 }
9902
9903 \f
9904 /****** Packing FSFS shards *********/
9905
9906 /* Write a file FILENAME in directory FS_PATH, containing a single line
9907  * with the number REVNUM in ASCII decimal.  Move the file into place
9908  * atomically, overwriting any existing file.
9909  *
9910  * Similar to write_current(). */
9911 static svn_error_t *
9912 write_revnum_file(const char *fs_path,
9913                   const char *filename,
9914                   svn_revnum_t revnum,
9915                   apr_pool_t *scratch_pool)
9916 {
9917   const char *final_path, *tmp_path;
9918   svn_stream_t *tmp_stream;
9919
9920   final_path = svn_dirent_join(fs_path, filename, scratch_pool);
9921   SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path, fs_path,
9922                                    svn_io_file_del_none,
9923                                    scratch_pool, scratch_pool));
9924   SVN_ERR(svn_stream_printf(tmp_stream, scratch_pool, "%ld\n", revnum));
9925   SVN_ERR(svn_stream_close(tmp_stream));
9926   SVN_ERR(move_into_place(tmp_path, final_path, final_path, scratch_pool));
9927   return SVN_NO_ERROR;
9928 }
9929
9930 /* Pack the revision SHARD containing exactly MAX_FILES_PER_DIR revisions
9931  * from SHARD_PATH into the PACK_FILE_DIR, using POOL for allocations.
9932  * CANCEL_FUNC and CANCEL_BATON are what you think they are.
9933  *
9934  * If for some reason we detect a partial packing already performed, we
9935  * remove the pack file and start again.
9936  */
9937 static svn_error_t *
9938 pack_rev_shard(const char *pack_file_dir,
9939                const char *shard_path,
9940                apr_int64_t shard,
9941                int max_files_per_dir,
9942                svn_cancel_func_t cancel_func,
9943                void *cancel_baton,
9944                apr_pool_t *pool)
9945 {
9946   const char *pack_file_path, *manifest_file_path;
9947   svn_stream_t *pack_stream, *manifest_stream;
9948   svn_revnum_t start_rev, end_rev, rev;
9949   apr_off_t next_offset;
9950   apr_pool_t *iterpool;
9951
9952   /* Some useful paths. */
9953   pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
9954   manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool);
9955
9956   /* Remove any existing pack file for this shard, since it is incomplete. */
9957   SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
9958                              pool));
9959
9960   /* Create the new directory and pack and manifest files. */
9961   SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool));
9962   SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool,
9963                                     pool));
9964   SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
9965                                    pool, pool));
9966
9967   start_rev = (svn_revnum_t) (shard * max_files_per_dir);
9968   end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
9969   next_offset = 0;
9970   iterpool = svn_pool_create(pool);
9971
9972   /* Iterate over the revisions in this shard, squashing them together. */
9973   for (rev = start_rev; rev <= end_rev; rev++)
9974     {
9975       svn_stream_t *rev_stream;
9976       apr_finfo_t finfo;
9977       const char *path;
9978
9979       svn_pool_clear(iterpool);
9980
9981       /* Get the size of the file. */
9982       path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
9983                              iterpool);
9984       SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
9985
9986       /* Update the manifest. */
9987       SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%" APR_OFF_T_FMT
9988                                 "\n", next_offset));
9989       next_offset += finfo.size;
9990
9991       /* Copy all the bits from the rev file to the end of the pack file. */
9992       SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool));
9993       SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream,
9994                                                              iterpool),
9995                           cancel_func, cancel_baton, iterpool));
9996     }
9997
9998   SVN_ERR(svn_stream_close(manifest_stream));
9999   SVN_ERR(svn_stream_close(pack_stream));
10000   SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
10001   SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool));
10002   SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
10003
10004   svn_pool_destroy(iterpool);
10005
10006   return SVN_NO_ERROR;
10007 }
10008
10009 /* Copy revprop files for revisions [START_REV, END_REV) from SHARD_PATH
10010  * to the pack file at PACK_FILE_NAME in PACK_FILE_DIR.
10011  *
10012  * The file sizes have already been determined and written to SIZES.
10013  * Please note that this function will be executed while the filesystem
10014  * has been locked and that revprops files will therefore not be modified
10015  * while the pack is in progress.
10016  *
10017  * COMPRESSION_LEVEL defines how well the resulting pack file shall be
10018  * compressed or whether is shall be compressed at all.  TOTAL_SIZE is
10019  * a hint on which initial buffer size we should use to hold the pack file
10020  * content.
10021  *
10022  * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations
10023  * are done in SCRATCH_POOL.
10024  */
10025 static svn_error_t *
10026 copy_revprops(const char *pack_file_dir,
10027               const char *pack_filename,
10028               const char *shard_path,
10029               svn_revnum_t start_rev,
10030               svn_revnum_t end_rev,
10031               apr_array_header_t *sizes,
10032               apr_size_t total_size,
10033               int compression_level,
10034               svn_cancel_func_t cancel_func,
10035               void *cancel_baton,
10036               apr_pool_t *scratch_pool)
10037 {
10038   svn_stream_t *pack_stream;
10039   apr_file_t *pack_file;
10040   svn_revnum_t rev;
10041   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10042   svn_stream_t *stream;
10043
10044   /* create empty data buffer and a write stream on top of it */
10045   svn_stringbuf_t *uncompressed
10046     = svn_stringbuf_create_ensure(total_size, scratch_pool);
10047   svn_stringbuf_t *compressed
10048     = svn_stringbuf_create_empty(scratch_pool);
10049   pack_stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
10050
10051   /* write the pack file header */
10052   SVN_ERR(serialize_revprops_header(pack_stream, start_rev, sizes, 0,
10053                                     sizes->nelts, iterpool));
10054
10055   /* Some useful paths. */
10056   SVN_ERR(svn_io_file_open(&pack_file, svn_dirent_join(pack_file_dir,
10057                                                        pack_filename,
10058                                                        scratch_pool),
10059                            APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
10060                            scratch_pool));
10061
10062   /* Iterate over the revisions in this shard, squashing them together. */
10063   for (rev = start_rev; rev <= end_rev; rev++)
10064     {
10065       const char *path;
10066
10067       svn_pool_clear(iterpool);
10068
10069       /* Construct the file name. */
10070       path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10071                              iterpool);
10072
10073       /* Copy all the bits from the non-packed revprop file to the end of
10074        * the pack file. */
10075       SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
10076       SVN_ERR(svn_stream_copy3(stream, pack_stream,
10077                                cancel_func, cancel_baton, iterpool));
10078     }
10079
10080   /* flush stream buffers to content buffer */
10081   SVN_ERR(svn_stream_close(pack_stream));
10082
10083   /* compress the content (or just store it for COMPRESSION_LEVEL 0) */
10084   SVN_ERR(svn__compress(svn_stringbuf__morph_into_string(uncompressed),
10085                         compressed, compression_level));
10086
10087   /* write the pack file content to disk */
10088   stream = svn_stream_from_aprfile2(pack_file, FALSE, scratch_pool);
10089   SVN_ERR(svn_stream_write(stream, compressed->data, &compressed->len));
10090   SVN_ERR(svn_stream_close(stream));
10091
10092   svn_pool_destroy(iterpool);
10093
10094   return SVN_NO_ERROR;
10095 }
10096
10097 /* For the revprop SHARD at SHARD_PATH with exactly MAX_FILES_PER_DIR
10098  * revprop files in it, create a packed shared at PACK_FILE_DIR.
10099  *
10100  * COMPRESSION_LEVEL defines how well the resulting pack file shall be
10101  * compressed or whether is shall be compressed at all.  Individual pack
10102  * file containing more than one revision will be limited to a size of
10103  * MAX_PACK_SIZE bytes before compression.
10104  *
10105  * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10106  * allocations are done in SCRATCH_POOL.
10107  */
10108 static svn_error_t *
10109 pack_revprops_shard(const char *pack_file_dir,
10110                     const char *shard_path,
10111                     apr_int64_t shard,
10112                     int max_files_per_dir,
10113                     apr_off_t max_pack_size,
10114                     int compression_level,
10115                     svn_cancel_func_t cancel_func,
10116                     void *cancel_baton,
10117                     apr_pool_t *scratch_pool)
10118 {
10119   const char *manifest_file_path, *pack_filename = NULL;
10120   svn_stream_t *manifest_stream;
10121   svn_revnum_t start_rev, end_rev, rev;
10122   apr_off_t total_size;
10123   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10124   apr_array_header_t *sizes;
10125
10126   /* Some useful paths. */
10127   manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
10128                                        scratch_pool);
10129
10130   /* Remove any existing pack file for this shard, since it is incomplete. */
10131   SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
10132                              scratch_pool));
10133
10134   /* Create the new directory and manifest file stream. */
10135   SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, scratch_pool));
10136   SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
10137                                    scratch_pool, scratch_pool));
10138
10139   /* revisions to handle. Special case: revision 0 */
10140   start_rev = (svn_revnum_t) (shard * max_files_per_dir);
10141   end_rev = (svn_revnum_t) ((shard + 1) * (max_files_per_dir) - 1);
10142   if (start_rev == 0)
10143     ++start_rev;
10144
10145   /* initialize the revprop size info */
10146   sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
10147   total_size = 2 * SVN_INT64_BUFFER_SIZE;
10148
10149   /* Iterate over the revisions in this shard, determine their size and
10150    * squashing them together into pack files. */
10151   for (rev = start_rev; rev <= end_rev; rev++)
10152     {
10153       apr_finfo_t finfo;
10154       const char *path;
10155
10156       svn_pool_clear(iterpool);
10157
10158       /* Get the size of the file. */
10159       path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
10160                              iterpool);
10161       SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
10162
10163       /* if we already have started a pack file and this revprop cannot be
10164        * appended to it, write the previous pack file. */
10165       if (sizes->nelts != 0 &&
10166           total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
10167         {
10168           SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10169                                 start_rev, rev-1, sizes, (apr_size_t)total_size,
10170                                 compression_level, cancel_func, cancel_baton,
10171                                 iterpool));
10172
10173           /* next pack file starts empty again */
10174           apr_array_clear(sizes);
10175           total_size = 2 * SVN_INT64_BUFFER_SIZE;
10176           start_rev = rev;
10177         }
10178
10179       /* Update the manifest. Allocate a file name for the current pack
10180        * file if it is a new one */
10181       if (sizes->nelts == 0)
10182         pack_filename = apr_psprintf(scratch_pool, "%ld.0", rev);
10183
10184       SVN_ERR(svn_stream_printf(manifest_stream, iterpool, "%s\n",
10185                                 pack_filename));
10186
10187       /* add to list of files to put into the current pack file */
10188       APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
10189       total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
10190     }
10191
10192   /* write the last pack file */
10193   if (sizes->nelts != 0)
10194     SVN_ERR(copy_revprops(pack_file_dir, pack_filename, shard_path,
10195                           start_rev, rev-1, sizes, (apr_size_t)total_size,
10196                           compression_level, cancel_func, cancel_baton,
10197                           iterpool));
10198
10199   /* flush the manifest file and update permissions */
10200   SVN_ERR(svn_stream_close(manifest_stream));
10201   SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
10202
10203   svn_pool_destroy(iterpool);
10204
10205   return SVN_NO_ERROR;
10206 }
10207
10208 /* Delete the non-packed revprop SHARD at SHARD_PATH with exactly
10209  * MAX_FILES_PER_DIR revprop files in it.  If this is shard 0, keep the
10210  * revprop file for revision 0.
10211  *
10212  * CANCEL_FUNC and CANCEL_BATON are used in the usual way.  Temporary
10213  * allocations are done in SCRATCH_POOL.
10214  */
10215 static svn_error_t *
10216 delete_revprops_shard(const char *shard_path,
10217                       apr_int64_t shard,
10218                       int max_files_per_dir,
10219                       svn_cancel_func_t cancel_func,
10220                       void *cancel_baton,
10221                       apr_pool_t *scratch_pool)
10222 {
10223   if (shard == 0)
10224     {
10225       apr_pool_t *iterpool = svn_pool_create(scratch_pool);
10226       int i;
10227
10228       /* delete all files except the one for revision 0 */
10229       for (i = 1; i < max_files_per_dir; ++i)
10230         {
10231           const char *path = svn_dirent_join(shard_path,
10232                                        apr_psprintf(iterpool, "%d", i),
10233                                        iterpool);
10234           if (cancel_func)
10235             SVN_ERR((*cancel_func)(cancel_baton));
10236
10237           SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
10238           svn_pool_clear(iterpool);
10239         }
10240
10241       svn_pool_destroy(iterpool);
10242     }
10243   else
10244     SVN_ERR(svn_io_remove_dir2(shard_path, TRUE,
10245                                cancel_func, cancel_baton, scratch_pool));
10246
10247   return SVN_NO_ERROR;
10248 }
10249
10250 /* In the file system at FS_PATH, pack the SHARD in REVS_DIR and
10251  * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL
10252  * for allocations.  REVPROPS_DIR will be NULL if revprop packing is not
10253  * supported.  COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that
10254  * case.
10255  *
10256  * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly
10257  * NOTIFY_FUNC and NOTIFY_BATON.
10258  *
10259  * If for some reason we detect a partial packing already performed, we
10260  * remove the pack file and start again.
10261  */
10262 static svn_error_t *
10263 pack_shard(const char *revs_dir,
10264            const char *revsprops_dir,
10265            const char *fs_path,
10266            apr_int64_t shard,
10267            int max_files_per_dir,
10268            apr_off_t max_pack_size,
10269            int compression_level,
10270            svn_fs_pack_notify_t notify_func,
10271            void *notify_baton,
10272            svn_cancel_func_t cancel_func,
10273            void *cancel_baton,
10274            apr_pool_t *pool)
10275 {
10276   const char *rev_shard_path, *rev_pack_file_dir;
10277   const char *revprops_shard_path, *revprops_pack_file_dir;
10278
10279   /* Notify caller we're starting to pack this shard. */
10280   if (notify_func)
10281     SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start,
10282                         pool));
10283
10284   /* Some useful paths. */
10285   rev_pack_file_dir = svn_dirent_join(revs_dir,
10286                   apr_psprintf(pool,
10287                                "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10288                                shard),
10289                   pool);
10290   rev_shard_path = svn_dirent_join(revs_dir,
10291                            apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10292                            pool);
10293
10294   /* pack the revision content */
10295   SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path,
10296                          shard, max_files_per_dir,
10297                          cancel_func, cancel_baton, pool));
10298
10299   /* if enabled, pack the revprops in an equivalent way */
10300   if (revsprops_dir)
10301     {
10302       revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
10303                    apr_psprintf(pool,
10304                                 "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
10305                                 shard),
10306                    pool);
10307       revprops_shard_path = svn_dirent_join(revsprops_dir,
10308                            apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
10309                            pool);
10310
10311       SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
10312                                   shard, max_files_per_dir,
10313                                   (int)(0.9 * max_pack_size),
10314                                   compression_level,
10315                                   cancel_func, cancel_baton, pool));
10316     }
10317
10318   /* Update the min-unpacked-rev file to reflect our newly packed shard.
10319    * (This doesn't update ffd->min_unpacked_rev.  That will be updated by
10320    * update_min_unpacked_rev() when necessary.) */
10321   SVN_ERR(write_revnum_file(fs_path, PATH_MIN_UNPACKED_REV,
10322                             (svn_revnum_t)((shard + 1) * max_files_per_dir),
10323                             pool));
10324
10325   /* Finally, remove the existing shard directories. */
10326   SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE,
10327                              cancel_func, cancel_baton, pool));
10328   if (revsprops_dir)
10329     SVN_ERR(delete_revprops_shard(revprops_shard_path,
10330                                   shard, max_files_per_dir,
10331                                   cancel_func, cancel_baton, pool));
10332
10333   /* Notify caller we're starting to pack this shard. */
10334   if (notify_func)
10335     SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end,
10336                         pool));
10337
10338   return SVN_NO_ERROR;
10339 }
10340
10341 struct pack_baton
10342 {
10343   svn_fs_t *fs;
10344   svn_fs_pack_notify_t notify_func;
10345   void *notify_baton;
10346   svn_cancel_func_t cancel_func;
10347   void *cancel_baton;
10348 };
10349
10350
10351 /* The work-horse for svn_fs_fs__pack, called with the FS write lock.
10352    This implements the svn_fs_fs__with_write_lock() 'body' callback
10353    type.  BATON is a 'struct pack_baton *'.
10354
10355    WARNING: if you add a call to this function, please note:
10356      The code currently assumes that any piece of code running with
10357      the write-lock set can rely on the ffd->min_unpacked_rev and
10358      ffd->min_unpacked_revprop caches to be up-to-date (and, by
10359      extension, on not having to use a retry when calling
10360      svn_fs_fs__path_rev_absolute() and friends).  If you add a call
10361      to this function, consider whether you have to call
10362      update_min_unpacked_rev().
10363      See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith
10364  */
10365 static svn_error_t *
10366 pack_body(void *baton,
10367           apr_pool_t *pool)
10368 {
10369   struct pack_baton *pb = baton;
10370   fs_fs_data_t ffd = {0};
10371   apr_int64_t completed_shards;
10372   apr_int64_t i;
10373   svn_revnum_t youngest;
10374   apr_pool_t *iterpool;
10375   const char *rev_data_path;
10376   const char *revprops_data_path = NULL;
10377
10378   /* read repository settings */
10379   SVN_ERR(read_format(&ffd.format, &ffd.max_files_per_dir,
10380                       path_format(pb->fs, pool), pool));
10381   SVN_ERR(check_format(ffd.format));
10382   SVN_ERR(read_config(&ffd, pb->fs->path, pool));
10383
10384   /* If the repository isn't a new enough format, we don't support packing.
10385      Return a friendly error to that effect. */
10386   if (ffd.format < SVN_FS_FS__MIN_PACKED_FORMAT)
10387     return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
10388       _("FSFS format (%d) too old to pack; please upgrade the filesystem."),
10389       ffd.format);
10390
10391   /* If we aren't using sharding, we can't do any packing, so quit. */
10392   if (!ffd.max_files_per_dir)
10393     return SVN_NO_ERROR;
10394
10395   SVN_ERR(read_min_unpacked_rev(&ffd.min_unpacked_rev,
10396                                 path_min_unpacked_rev(pb->fs, pool),
10397                                 pool));
10398
10399   SVN_ERR(get_youngest(&youngest, pb->fs->path, pool));
10400   completed_shards = (youngest + 1) / ffd.max_files_per_dir;
10401
10402   /* See if we've already completed all possible shards thus far. */
10403   if (ffd.min_unpacked_rev == (completed_shards * ffd.max_files_per_dir))
10404     return SVN_NO_ERROR;
10405
10406   rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
10407   if (ffd.format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
10408     revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
10409                                          pool);
10410
10411   iterpool = svn_pool_create(pool);
10412   for (i = ffd.min_unpacked_rev / ffd.max_files_per_dir;
10413        i < completed_shards;
10414        i++)
10415     {
10416       svn_pool_clear(iterpool);
10417
10418       if (pb->cancel_func)
10419         SVN_ERR(pb->cancel_func(pb->cancel_baton));
10420
10421       SVN_ERR(pack_shard(rev_data_path, revprops_data_path,
10422                          pb->fs->path, i, ffd.max_files_per_dir,
10423                          ffd.revprop_pack_size,
10424                          ffd.compress_packed_revprops
10425                            ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
10426                            : SVN_DELTA_COMPRESSION_LEVEL_NONE,
10427                          pb->notify_func, pb->notify_baton,
10428                          pb->cancel_func, pb->cancel_baton, iterpool));
10429     }
10430
10431   svn_pool_destroy(iterpool);
10432   return SVN_NO_ERROR;
10433 }
10434
10435 svn_error_t *
10436 svn_fs_fs__pack(svn_fs_t *fs,
10437                 svn_fs_pack_notify_t notify_func,
10438                 void *notify_baton,
10439                 svn_cancel_func_t cancel_func,
10440                 void *cancel_baton,
10441                 apr_pool_t *pool)
10442 {
10443   struct pack_baton pb = { 0 };
10444   pb.fs = fs;
10445   pb.notify_func = notify_func;
10446   pb.notify_baton = notify_baton;
10447   pb.cancel_func = cancel_func;
10448   pb.cancel_baton = cancel_baton;
10449   return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool);
10450 }
10451
10452 \f
10453 /** Verifying. **/
10454
10455 /* Baton type expected by verify_walker().  The purpose is to reuse open
10456  * rev / pack file handles between calls.  Its contents need to be cleaned
10457  * periodically to limit resource usage.
10458  */
10459 typedef struct verify_walker_baton_t
10460 {
10461   /* number of calls to verify_walker() since the last clean */
10462   int iteration_count;
10463
10464   /* number of files opened since the last clean */
10465   int file_count;
10466
10467   /* progress notification callback to invoke periodically (may be NULL) */
10468   svn_fs_progress_notify_func_t notify_func;
10469
10470   /* baton to use with NOTIFY_FUNC */
10471   void *notify_baton;
10472
10473   /* remember the last revision for which we called notify_func */
10474   svn_revnum_t last_notified_revision;
10475
10476   /* current file handle (or NULL) */
10477   apr_file_t *file_hint;
10478
10479   /* corresponding revision (or SVN_INVALID_REVNUM) */
10480   svn_revnum_t rev_hint;
10481
10482   /* pool to use for the file handles etc. */
10483   apr_pool_t *pool;
10484 } verify_walker_baton_t;
10485
10486 /* Used by svn_fs_fs__verify().
10487    Implements svn_fs_fs__walk_rep_reference().walker.  */
10488 static svn_error_t *
10489 verify_walker(representation_t *rep,
10490               void *baton,
10491               svn_fs_t *fs,
10492               apr_pool_t *scratch_pool)
10493 {
10494   struct rep_state *rs;
10495   struct rep_args *rep_args;
10496
10497   if (baton)
10498     {
10499       verify_walker_baton_t *walker_baton = baton;
10500       apr_file_t * previous_file;
10501
10502       /* notify and free resources periodically */
10503       if (   walker_baton->iteration_count > 1000
10504           || walker_baton->file_count > 16)
10505         {
10506           if (   walker_baton->notify_func
10507               && rep->revision != walker_baton->last_notified_revision)
10508             {
10509               walker_baton->notify_func(rep->revision,
10510                                         walker_baton->notify_baton,
10511                                         scratch_pool);
10512               walker_baton->last_notified_revision = rep->revision;
10513             }
10514
10515           svn_pool_clear(walker_baton->pool);
10516
10517           walker_baton->iteration_count = 0;
10518           walker_baton->file_count = 0;
10519           walker_baton->file_hint = NULL;
10520           walker_baton->rev_hint = SVN_INVALID_REVNUM;
10521         }
10522
10523       /* access the repo data */
10524       previous_file = walker_baton->file_hint;
10525       SVN_ERR(create_rep_state(&rs, &rep_args, &walker_baton->file_hint,
10526                                &walker_baton->rev_hint, rep, fs,
10527                                walker_baton->pool));
10528
10529       /* update resource usage counters */
10530       walker_baton->iteration_count++;
10531       if (previous_file != walker_baton->file_hint)
10532         walker_baton->file_count++;
10533     }
10534   else
10535     {
10536       /* ### Should this be using read_rep_line() directly? */
10537       SVN_ERR(create_rep_state(&rs, &rep_args, NULL, NULL, rep, fs,
10538                                scratch_pool));
10539     }
10540
10541   return SVN_NO_ERROR;
10542 }
10543
10544 svn_error_t *
10545 svn_fs_fs__verify(svn_fs_t *fs,
10546                   svn_revnum_t start,
10547                   svn_revnum_t end,
10548                   svn_fs_progress_notify_func_t notify_func,
10549                   void *notify_baton,
10550                   svn_cancel_func_t cancel_func,
10551                   void *cancel_baton,
10552                   apr_pool_t *pool)
10553 {
10554   fs_fs_data_t *ffd = fs->fsap_data;
10555   svn_boolean_t exists;
10556   svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
10557
10558   if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
10559     return SVN_NO_ERROR;
10560
10561   /* Input validation. */
10562   if (! SVN_IS_VALID_REVNUM(start))
10563     start = 0;
10564   if (! SVN_IS_VALID_REVNUM(end))
10565     end = youngest;
10566   SVN_ERR(ensure_revision_exists(fs, start, pool));
10567   SVN_ERR(ensure_revision_exists(fs, end, pool));
10568
10569   /* rep-cache verification. */
10570   SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
10571   if (exists)
10572     {
10573       /* provide a baton to allow the reuse of open file handles between
10574          iterations (saves 2/3 of OS level file operations). */
10575       verify_walker_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
10576       baton->rev_hint = SVN_INVALID_REVNUM;
10577       baton->pool = svn_pool_create(pool);
10578       baton->last_notified_revision = SVN_INVALID_REVNUM;
10579       baton->notify_func = notify_func;
10580       baton->notify_baton = notify_baton;
10581
10582       /* tell the user that we are now ready to do *something* */
10583       if (notify_func)
10584         notify_func(SVN_INVALID_REVNUM, notify_baton, baton->pool);
10585
10586       /* Do not attempt to walk the rep-cache database if its file does
10587          not exist,  since doing so would create it --- which may confuse
10588          the administrator.   Don't take any lock. */
10589       SVN_ERR(svn_fs_fs__walk_rep_reference(fs, start, end,
10590                                             verify_walker, baton,
10591                                             cancel_func, cancel_baton,
10592                                             pool));
10593
10594       /* walker resource cleanup */
10595       svn_pool_destroy(baton->pool);
10596     }
10597
10598   return SVN_NO_ERROR;
10599 }
10600
10601 \f
10602 /** Hotcopy. **/
10603
10604 /* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
10605  * the destination and do not differ in terms of kind, size, and mtime. */
10606 static svn_error_t *
10607 hotcopy_io_dir_file_copy(const char *src_path,
10608                          const char *dst_path,
10609                          const char *file,
10610                          apr_pool_t *scratch_pool)
10611 {
10612   const svn_io_dirent2_t *src_dirent;
10613   const svn_io_dirent2_t *dst_dirent;
10614   const char *src_target;
10615   const char *dst_target;
10616
10617   /* Does the destination already exist? If not, we must copy it. */
10618   dst_target = svn_dirent_join(dst_path, file, scratch_pool);
10619   SVN_ERR(svn_io_stat_dirent2(&dst_dirent, dst_target, FALSE, TRUE,
10620                               scratch_pool, scratch_pool));
10621   if (dst_dirent->kind != svn_node_none)
10622     {
10623       /* If the destination's stat information indicates that the file
10624        * is equal to the source, don't bother copying the file again. */
10625       src_target = svn_dirent_join(src_path, file, scratch_pool);
10626       SVN_ERR(svn_io_stat_dirent2(&src_dirent, src_target, FALSE, FALSE,
10627                                   scratch_pool, scratch_pool));
10628       if (src_dirent->kind == dst_dirent->kind &&
10629           src_dirent->special == dst_dirent->special &&
10630           src_dirent->filesize == dst_dirent->filesize &&
10631           src_dirent->mtime <= dst_dirent->mtime)
10632         return SVN_NO_ERROR;
10633     }
10634
10635   return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
10636                                               scratch_pool));
10637 }
10638
10639 /* Set *NAME_P to the UTF-8 representation of directory entry NAME.
10640  * NAME is in the internal encoding used by APR; PARENT is in
10641  * UTF-8 and in internal (not local) style.
10642  *
10643  * Use PARENT only for generating an error string if the conversion
10644  * fails because NAME could not be represented in UTF-8.  In that
10645  * case, return a two-level error in which the outer error's message
10646  * mentions PARENT, but the inner error's message does not mention
10647  * NAME (except possibly in hex) since NAME may not be printable.
10648  * Such a compound error at least allows the user to go looking in the
10649  * right directory for the problem.
10650  *
10651  * If there is any other error, just return that error directly.
10652  *
10653  * If there is any error, the effect on *NAME_P is undefined.
10654  *
10655  * *NAME_P and NAME may refer to the same storage.
10656  */
10657 static svn_error_t *
10658 entry_name_to_utf8(const char **name_p,
10659                    const char *name,
10660                    const char *parent,
10661                    apr_pool_t *pool)
10662 {
10663   svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
10664   if (err && err->apr_err == APR_EINVAL)
10665     {
10666       return svn_error_createf(err->apr_err, err,
10667                                _("Error converting entry "
10668                                  "in directory '%s' to UTF-8"),
10669                                svn_dirent_local_style(parent, pool));
10670     }
10671   return err;
10672 }
10673
10674 /* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
10675  * exist in the destination and do not differ from the source in terms of
10676  * kind, size, and mtime. */
10677 static svn_error_t *
10678 hotcopy_io_copy_dir_recursively(const char *src,
10679                                 const char *dst_parent,
10680                                 const char *dst_basename,
10681                                 svn_boolean_t copy_perms,
10682                                 svn_cancel_func_t cancel_func,
10683                                 void *cancel_baton,
10684                                 apr_pool_t *pool)
10685 {
10686   svn_node_kind_t kind;
10687   apr_status_t status;
10688   const char *dst_path;
10689   apr_dir_t *this_dir;
10690   apr_finfo_t this_entry;
10691   apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
10692
10693   /* Make a subpool for recursion */
10694   apr_pool_t *subpool = svn_pool_create(pool);
10695
10696   /* The 'dst_path' is simply dst_parent/dst_basename */
10697   dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
10698
10699   /* Sanity checks:  SRC and DST_PARENT are directories, and
10700      DST_BASENAME doesn't already exist in DST_PARENT. */
10701   SVN_ERR(svn_io_check_path(src, &kind, subpool));
10702   if (kind != svn_node_dir)
10703     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10704                              _("Source '%s' is not a directory"),
10705                              svn_dirent_local_style(src, pool));
10706
10707   SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
10708   if (kind != svn_node_dir)
10709     return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
10710                              _("Destination '%s' is not a directory"),
10711                              svn_dirent_local_style(dst_parent, pool));
10712
10713   SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
10714
10715   /* Create the new directory. */
10716   /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
10717   SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
10718
10719   /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
10720   SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
10721
10722   for (status = apr_dir_read(&this_entry, flags, this_dir);
10723        status == APR_SUCCESS;
10724        status = apr_dir_read(&this_entry, flags, this_dir))
10725     {
10726       if ((this_entry.name[0] == '.')
10727           && ((this_entry.name[1] == '\0')
10728               || ((this_entry.name[1] == '.')
10729                   && (this_entry.name[2] == '\0'))))
10730         {
10731           continue;
10732         }
10733       else
10734         {
10735           const char *entryname_utf8;
10736
10737           if (cancel_func)
10738             SVN_ERR(cancel_func(cancel_baton));
10739
10740           SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
10741                                      src, subpool));
10742           if (this_entry.filetype == APR_REG) /* regular file */
10743             {
10744               SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8,
10745                                                subpool));
10746             }
10747           else if (this_entry.filetype == APR_LNK) /* symlink */
10748             {
10749               const char *src_target = svn_dirent_join(src, entryname_utf8,
10750                                                        subpool);
10751               const char *dst_target = svn_dirent_join(dst_path,
10752                                                        entryname_utf8,
10753                                                        subpool);
10754               SVN_ERR(svn_io_copy_link(src_target, dst_target,
10755                                        subpool));
10756             }
10757           else if (this_entry.filetype == APR_DIR) /* recurse */
10758             {
10759               const char *src_target;
10760
10761               /* Prevent infinite recursion by filtering off our
10762                  newly created destination path. */
10763               if (strcmp(src, dst_parent) == 0
10764                   && strcmp(entryname_utf8, dst_basename) == 0)
10765                 continue;
10766
10767               src_target = svn_dirent_join(src, entryname_utf8, subpool);
10768               SVN_ERR(hotcopy_io_copy_dir_recursively(src_target,
10769                                                       dst_path,
10770                                                       entryname_utf8,
10771                                                       copy_perms,
10772                                                       cancel_func,
10773                                                       cancel_baton,
10774                                                       subpool));
10775             }
10776           /* ### support other APR node types someday?? */
10777
10778         }
10779     }
10780
10781   if (! (APR_STATUS_IS_ENOENT(status)))
10782     return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
10783                               svn_dirent_local_style(src, pool));
10784
10785   status = apr_dir_close(this_dir);
10786   if (status)
10787     return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
10788                               svn_dirent_local_style(src, pool));
10789
10790   /* Free any memory used by recursion */
10791   svn_pool_destroy(subpool);
10792
10793   return SVN_NO_ERROR;
10794 }
10795
10796 /* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
10797  * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
10798  * Use SCRATCH_POOL for temporary allocations. */
10799 static svn_error_t *
10800 hotcopy_copy_shard_file(const char *src_subdir,
10801                         const char *dst_subdir,
10802                         svn_revnum_t rev,
10803                         int max_files_per_dir,
10804                         apr_pool_t *scratch_pool)
10805 {
10806   const char *src_subdir_shard = src_subdir,
10807              *dst_subdir_shard = dst_subdir;
10808
10809   if (max_files_per_dir)
10810     {
10811       const char *shard = apr_psprintf(scratch_pool, "%ld",
10812                                        rev / max_files_per_dir);
10813       src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
10814       dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10815
10816       if (rev % max_files_per_dir == 0)
10817         {
10818           SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
10819           SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
10820                                     scratch_pool));
10821         }
10822     }
10823
10824   SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
10825                                    apr_psprintf(scratch_pool, "%ld", rev),
10826                                    scratch_pool));
10827   return SVN_NO_ERROR;
10828 }
10829
10830
10831 /* Copy a packed shard containing revision REV, and which contains
10832  * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
10833  * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
10834  * Do not re-copy data which already exists in DST_FS.
10835  * Use SCRATCH_POOL for temporary allocations. */
10836 static svn_error_t *
10837 hotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev,
10838                           svn_fs_t *src_fs,
10839                           svn_fs_t *dst_fs,
10840                           svn_revnum_t rev,
10841                           int max_files_per_dir,
10842                           apr_pool_t *scratch_pool)
10843 {
10844   const char *src_subdir;
10845   const char *dst_subdir;
10846   const char *packed_shard;
10847   const char *src_subdir_packed_shard;
10848   svn_revnum_t revprop_rev;
10849   apr_pool_t *iterpool;
10850   fs_fs_data_t *src_ffd = src_fs->fsap_data;
10851
10852   /* Copy the packed shard. */
10853   src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
10854   dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
10855   packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10856                               rev / max_files_per_dir);
10857   src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10858                                             scratch_pool);
10859   SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10860                                           dst_subdir, packed_shard,
10861                                           TRUE /* copy_perms */,
10862                                           NULL /* cancel_func */, NULL,
10863                                           scratch_pool));
10864
10865   /* Copy revprops belonging to revisions in this pack. */
10866   src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10867   dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
10868
10869   if (   src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
10870       || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
10871     {
10872       /* copy unpacked revprops rev by rev */
10873       iterpool = svn_pool_create(scratch_pool);
10874       for (revprop_rev = rev;
10875            revprop_rev < rev + max_files_per_dir;
10876            revprop_rev++)
10877         {
10878           svn_pool_clear(iterpool);
10879
10880           SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10881                                           revprop_rev, max_files_per_dir,
10882                                           iterpool));
10883         }
10884       svn_pool_destroy(iterpool);
10885     }
10886   else
10887     {
10888       /* revprop for revision 0 will never be packed */
10889       if (rev == 0)
10890         SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
10891                                         0, max_files_per_dir,
10892                                         scratch_pool));
10893
10894       /* packed revprops folder */
10895       packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
10896                                   rev / max_files_per_dir);
10897       src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
10898                                                 scratch_pool);
10899       SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
10900                                               dst_subdir, packed_shard,
10901                                               TRUE /* copy_perms */,
10902                                               NULL /* cancel_func */, NULL,
10903                                               scratch_pool));
10904     }
10905
10906   /* If necessary, update the min-unpacked rev file in the hotcopy. */
10907   if (*dst_min_unpacked_rev < rev + max_files_per_dir)
10908     {
10909       *dst_min_unpacked_rev = rev + max_files_per_dir;
10910       SVN_ERR(write_revnum_file(dst_fs->path, PATH_MIN_UNPACKED_REV,
10911                                 *dst_min_unpacked_rev,
10912                                 scratch_pool));
10913     }
10914
10915   return SVN_NO_ERROR;
10916 }
10917
10918 /* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current'
10919  * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST.
10920  * Use SCRATCH_POOL for temporary allocations. */
10921 static svn_error_t *
10922 hotcopy_update_current(svn_revnum_t *dst_youngest,
10923                        svn_fs_t *dst_fs,
10924                        svn_revnum_t new_youngest,
10925                        apr_pool_t *scratch_pool)
10926 {
10927   char next_node_id[MAX_KEY_SIZE] = "0";
10928   char next_copy_id[MAX_KEY_SIZE] = "0";
10929   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
10930
10931   if (*dst_youngest >= new_youngest)
10932     return SVN_NO_ERROR;
10933
10934   /* If necessary, get new current next_node and next_copy IDs. */
10935   if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
10936     {
10937       apr_off_t root_offset;
10938       apr_file_t *rev_file;
10939       char max_node_id[MAX_KEY_SIZE] = "0";
10940       char max_copy_id[MAX_KEY_SIZE] = "0";
10941       apr_size_t len;
10942
10943       if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
10944         SVN_ERR(update_min_unpacked_rev(dst_fs, scratch_pool));
10945
10946       SVN_ERR(open_pack_or_rev_file(&rev_file, dst_fs, new_youngest,
10947                                     scratch_pool));
10948       SVN_ERR(get_root_changes_offset(&root_offset, NULL, rev_file,
10949                                       dst_fs, new_youngest, scratch_pool));
10950       SVN_ERR(recover_find_max_ids(dst_fs, new_youngest, rev_file,
10951                                    root_offset, max_node_id, max_copy_id,
10952                                    scratch_pool));
10953       SVN_ERR(svn_io_file_close(rev_file, scratch_pool));
10954
10955       /* We store the _next_ ids. */
10956       len = strlen(max_node_id);
10957       svn_fs_fs__next_key(max_node_id, &len, next_node_id);
10958       len = strlen(max_copy_id);
10959       svn_fs_fs__next_key(max_copy_id, &len, next_copy_id);
10960     }
10961
10962   /* Update 'current'. */
10963   SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id,
10964                         scratch_pool));
10965
10966   *dst_youngest = new_youngest;
10967
10968   return SVN_NO_ERROR;
10969 }
10970
10971
10972 /* Remove revision or revprop files between START_REV (inclusive) and
10973  * END_REV (non-inclusive) from folder DST_SUBDIR in DST_FS.  Assume
10974  * sharding as per MAX_FILES_PER_DIR.
10975  * Use SCRATCH_POOL for temporary allocations. */
10976 static svn_error_t *
10977 hotcopy_remove_files(svn_fs_t *dst_fs,
10978                      const char *dst_subdir,
10979                      svn_revnum_t start_rev,
10980                      svn_revnum_t end_rev,
10981                      int max_files_per_dir,
10982                      apr_pool_t *scratch_pool)
10983 {
10984   const char *shard;
10985   const char *dst_subdir_shard;
10986   svn_revnum_t rev;
10987   apr_pool_t *iterpool;
10988
10989   /* Pre-compute paths for initial shard. */
10990   shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
10991   dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
10992
10993   iterpool = svn_pool_create(scratch_pool);
10994   for (rev = start_rev; rev < end_rev; rev++)
10995     {
10996       const char *path;
10997       svn_pool_clear(iterpool);
10998
10999       /* If necessary, update paths for shard. */
11000       if (rev != start_rev && rev % max_files_per_dir == 0)
11001         {
11002           shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
11003           dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
11004         }
11005
11006       /* remove files for REV */
11007       path = svn_dirent_join(dst_subdir_shard,
11008                              apr_psprintf(iterpool, "%ld", rev),
11009                              iterpool);
11010
11011       /* Make the rev file writable and remove it. */
11012       SVN_ERR(svn_io_set_file_read_write(path, TRUE, iterpool));
11013       SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
11014     }
11015
11016   svn_pool_destroy(iterpool);
11017
11018   return SVN_NO_ERROR;
11019 }
11020
11021 /* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
11022  * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
11023  * Use SCRATCH_POOL for temporary allocations. */
11024 static svn_error_t *
11025 hotcopy_remove_rev_files(svn_fs_t *dst_fs,
11026                          svn_revnum_t start_rev,
11027                          svn_revnum_t end_rev,
11028                          int max_files_per_dir,
11029                          apr_pool_t *scratch_pool)
11030 {
11031   SVN_ERR_ASSERT(start_rev <= end_rev);
11032   SVN_ERR(hotcopy_remove_files(dst_fs,
11033                                svn_dirent_join(dst_fs->path,
11034                                                PATH_REVS_DIR,
11035                                                scratch_pool),
11036                                start_rev, end_rev,
11037                                max_files_per_dir, scratch_pool));
11038
11039   return SVN_NO_ERROR;
11040 }
11041
11042 /* Remove revision properties between START_REV (inclusive) and END_REV
11043  * (non-inclusive) from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
11044  * Use SCRATCH_POOL for temporary allocations.  Revision 0 revprops will
11045  * not be deleted. */
11046 static svn_error_t *
11047 hotcopy_remove_revprop_files(svn_fs_t *dst_fs,
11048                              svn_revnum_t start_rev,
11049                              svn_revnum_t end_rev,
11050                              int max_files_per_dir,
11051                              apr_pool_t *scratch_pool)
11052 {
11053   SVN_ERR_ASSERT(start_rev <= end_rev);
11054
11055   /* don't delete rev 0 props */
11056   SVN_ERR(hotcopy_remove_files(dst_fs,
11057                                svn_dirent_join(dst_fs->path,
11058                                                PATH_REVPROPS_DIR,
11059                                                scratch_pool),
11060                                start_rev ? start_rev : 1, end_rev,
11061                                max_files_per_dir, scratch_pool));
11062
11063   return SVN_NO_ERROR;
11064 }
11065
11066 /* Verify that DST_FS is a suitable destination for an incremental
11067  * hotcopy from SRC_FS. */
11068 static svn_error_t *
11069 hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
11070                                         svn_fs_t *dst_fs,
11071                                         apr_pool_t *pool)
11072 {
11073   fs_fs_data_t *src_ffd = src_fs->fsap_data;
11074   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11075
11076   /* We only support incremental hotcopy between the same format. */
11077   if (src_ffd->format != dst_ffd->format)
11078     return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11079       _("The FSFS format (%d) of the hotcopy source does not match the "
11080         "FSFS format (%d) of the hotcopy destination; please upgrade "
11081         "both repositories to the same format"),
11082       src_ffd->format, dst_ffd->format);
11083
11084   /* Make sure the UUID of source and destination match up.
11085    * We don't want to copy over a different repository. */
11086   if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
11087     return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
11088                             _("The UUID of the hotcopy source does "
11089                               "not match the UUID of the hotcopy "
11090                               "destination"));
11091
11092   /* Also require same shard size. */
11093   if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
11094     return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11095                             _("The sharding layout configuration "
11096                               "of the hotcopy source does not match "
11097                               "the sharding layout configuration of "
11098                               "the hotcopy destination"));
11099   return SVN_NO_ERROR;
11100 }
11101
11102 /* Remove folder PATH.  Ignore errors due to the sub-tree not being empty.
11103  * CANCEL_FUNC and CANCEL_BATON do the usual thing.
11104  * Use POOL for temporary allocations.
11105  */
11106 static svn_error_t *
11107 remove_folder(const char *path,
11108               svn_cancel_func_t cancel_func,
11109               void *cancel_baton,
11110               apr_pool_t *pool)
11111 {
11112   svn_error_t *err = svn_io_remove_dir2(path, TRUE,
11113                                         cancel_func, cancel_baton, pool);
11114
11115   if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
11116     {
11117       svn_error_clear(err);
11118       err = SVN_NO_ERROR;
11119     }
11120
11121   return svn_error_trace(err);
11122 }
11123
11124 /* Baton for hotcopy_body(). */
11125 struct hotcopy_body_baton {
11126   svn_fs_t *src_fs;
11127   svn_fs_t *dst_fs;
11128   svn_boolean_t incremental;
11129   svn_cancel_func_t cancel_func;
11130   void *cancel_baton;
11131 } hotcopy_body_baton;
11132
11133 /* Perform a hotcopy, either normal or incremental.
11134  *
11135  * Normal hotcopy assumes that the destination exists as an empty
11136  * directory. It behaves like an incremental hotcopy except that
11137  * none of the copied files already exist in the destination.
11138  *
11139  * An incremental hotcopy copies only changed or new files to the destination,
11140  * and removes files from the destination no longer present in the source.
11141  * While the incremental hotcopy is running, readers should still be able
11142  * to access the destintation repository without error and should not see
11143  * revisions currently in progress of being copied. Readers are able to see
11144  * new fully copied revisions even if the entire incremental hotcopy procedure
11145  * has not yet completed.
11146  *
11147  * Writers are blocked out completely during the entire incremental hotcopy
11148  * process to ensure consistency. This function assumes that the repository
11149  * write-lock is held.
11150  */
11151 static svn_error_t *
11152 hotcopy_body(void *baton, apr_pool_t *pool)
11153 {
11154   struct hotcopy_body_baton *hbb = baton;
11155   svn_fs_t *src_fs = hbb->src_fs;
11156   fs_fs_data_t *src_ffd = src_fs->fsap_data;
11157   svn_fs_t *dst_fs = hbb->dst_fs;
11158   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11159   int max_files_per_dir = src_ffd->max_files_per_dir;
11160   svn_boolean_t incremental = hbb->incremental;
11161   svn_cancel_func_t cancel_func = hbb->cancel_func;
11162   void* cancel_baton = hbb->cancel_baton;
11163   svn_revnum_t src_youngest;
11164   svn_revnum_t dst_youngest;
11165   svn_revnum_t rev;
11166   svn_revnum_t src_min_unpacked_rev;
11167   svn_revnum_t dst_min_unpacked_rev;
11168   const char *src_subdir;
11169   const char *dst_subdir;
11170   const char *revprop_src_subdir;
11171   const char *revprop_dst_subdir;
11172   apr_pool_t *iterpool;
11173   svn_node_kind_t kind;
11174
11175   /* Try to copy the config.
11176    *
11177    * ### We try copying the config file before doing anything else,
11178    * ### because higher layers will abort the hotcopy if we throw
11179    * ### an error from this function, and that renders the hotcopy
11180    * ### unusable anyway. */
11181   if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
11182     {
11183       svn_error_t *err;
11184
11185       err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
11186                                  pool);
11187       if (err)
11188         {
11189           if (APR_STATUS_IS_ENOENT(err->apr_err))
11190             {
11191               /* 1.6.0 to 1.6.11 did not copy the configuration file during
11192                * hotcopy. So if we're hotcopying a repository which has been
11193                * created as a hotcopy itself, it's possible that fsfs.conf
11194                * does not exist. Ask the user to re-create it.
11195                *
11196                * ### It would be nice to make this a non-fatal error,
11197                * ### but this function does not get an svn_fs_t object
11198                * ### so we have no way of just printing a warning via
11199                * ### the fs->warning() callback. */
11200
11201               const char *msg;
11202               const char *src_abspath;
11203               const char *dst_abspath;
11204               const char *config_relpath;
11205               svn_error_t *err2;
11206
11207               config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
11208               err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
11209               if (err2)
11210                 return svn_error_trace(svn_error_compose_create(err, err2));
11211               err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
11212               if (err2)
11213                 return svn_error_trace(svn_error_compose_create(err, err2));
11214
11215               /* ### hack: strip off the 'db/' directory from paths so
11216                * ### they make sense to the user */
11217               src_abspath = svn_dirent_dirname(src_abspath, pool);
11218               dst_abspath = svn_dirent_dirname(dst_abspath, pool);
11219
11220               msg = apr_psprintf(pool,
11221                                  _("Failed to create hotcopy at '%s'. "
11222                                    "The file '%s' is missing from the source "
11223                                    "repository. Please create this file, for "
11224                                    "instance by running 'svnadmin upgrade %s'"),
11225                                  dst_abspath, config_relpath, src_abspath);
11226               return svn_error_quick_wrap(err, msg);
11227             }
11228           else
11229             return svn_error_trace(err);
11230         }
11231     }
11232
11233   if (cancel_func)
11234     SVN_ERR(cancel_func(cancel_baton));
11235
11236   /* Find the youngest revision in the source and destination.
11237    * We only support hotcopies from sources with an equal or greater amount
11238    * of revisions than the destination.
11239    * This also catches the case where users accidentally swap the
11240    * source and destination arguments. */
11241   SVN_ERR(get_youngest(&src_youngest, src_fs->path, pool));
11242   if (incremental)
11243     {
11244       SVN_ERR(get_youngest(&dst_youngest, dst_fs->path, pool));
11245       if (src_youngest < dst_youngest)
11246         return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11247                  _("The hotcopy destination already contains more revisions "
11248                    "(%lu) than the hotcopy source contains (%lu); are source "
11249                    "and destination swapped?"),
11250                   dst_youngest, src_youngest);
11251     }
11252   else
11253     dst_youngest = 0;
11254
11255   if (cancel_func)
11256     SVN_ERR(cancel_func(cancel_baton));
11257
11258   /* Copy the min unpacked rev, and read its value. */
11259   if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11260     {
11261       const char *min_unpacked_rev_path;
11262
11263       min_unpacked_rev_path = svn_dirent_join(src_fs->path,
11264                                               PATH_MIN_UNPACKED_REV,
11265                                               pool);
11266       SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev,
11267                                     min_unpacked_rev_path,
11268                                     pool));
11269
11270       min_unpacked_rev_path = svn_dirent_join(dst_fs->path,
11271                                               PATH_MIN_UNPACKED_REV,
11272                                               pool);
11273       SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev,
11274                                     min_unpacked_rev_path,
11275                                     pool));
11276
11277       /* We only support packs coming from the hotcopy source.
11278        * The destination should not be packed independently from
11279        * the source. This also catches the case where users accidentally
11280        * swap the source and destination arguments. */
11281       if (src_min_unpacked_rev < dst_min_unpacked_rev)
11282         return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
11283                                  _("The hotcopy destination already contains "
11284                                    "more packed revisions (%lu) than the "
11285                                    "hotcopy source contains (%lu)"),
11286                                    dst_min_unpacked_rev - 1,
11287                                    src_min_unpacked_rev - 1);
11288
11289       SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11290                                    PATH_MIN_UNPACKED_REV, pool));
11291     }
11292   else
11293     {
11294       src_min_unpacked_rev = 0;
11295       dst_min_unpacked_rev = 0;
11296     }
11297
11298   if (cancel_func)
11299     SVN_ERR(cancel_func(cancel_baton));
11300
11301   /*
11302    * Copy the necessary rev files.
11303    */
11304
11305   src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
11306   dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
11307   SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
11308
11309   iterpool = svn_pool_create(pool);
11310   /* First, copy packed shards. */
11311   for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
11312     {
11313       svn_pool_clear(iterpool);
11314
11315       if (cancel_func)
11316         SVN_ERR(cancel_func(cancel_baton));
11317
11318       /* Copy the packed shard. */
11319       SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11320                                         src_fs, dst_fs,
11321                                         rev, max_files_per_dir,
11322                                         iterpool));
11323
11324       /* If necessary, update 'current' to the most recent packed rev,
11325        * so readers can see new revisions which arrived in this pack. */
11326       SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs,
11327                                      rev + max_files_per_dir - 1,
11328                                      iterpool));
11329
11330       /* Remove revision files which are now packed. */
11331       if (incremental)
11332         {
11333           SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev,
11334                                            rev + max_files_per_dir,
11335                                            max_files_per_dir, iterpool));
11336           if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11337             SVN_ERR(hotcopy_remove_revprop_files(dst_fs, rev,
11338                                                  rev + max_files_per_dir,
11339                                                  max_files_per_dir,
11340                                                  iterpool));
11341         }
11342
11343       /* Now that all revisions have moved into the pack, the original
11344        * rev dir can be removed. */
11345       SVN_ERR(remove_folder(path_rev_shard(dst_fs, rev, iterpool),
11346                             cancel_func, cancel_baton, iterpool));
11347       if (rev > 0 && dst_ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
11348         SVN_ERR(remove_folder(path_revprops_shard(dst_fs, rev, iterpool),
11349                               cancel_func, cancel_baton, iterpool));
11350     }
11351
11352   if (cancel_func)
11353     SVN_ERR(cancel_func(cancel_baton));
11354
11355   /* Now, copy pairs of non-packed revisions and revprop files.
11356    * If necessary, update 'current' after copying all files from a shard. */
11357   SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
11358   SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
11359   revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
11360   revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
11361   SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool));
11362   for (; rev <= src_youngest; rev++)
11363     {
11364       svn_error_t *err;
11365
11366       svn_pool_clear(iterpool);
11367
11368       if (cancel_func)
11369         SVN_ERR(cancel_func(cancel_baton));
11370
11371       /* Copy the rev file. */
11372       err = hotcopy_copy_shard_file(src_subdir, dst_subdir,
11373                                     rev, max_files_per_dir,
11374                                     iterpool);
11375       if (err)
11376         {
11377           if (APR_STATUS_IS_ENOENT(err->apr_err) &&
11378               src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11379             {
11380               svn_error_clear(err);
11381
11382               /* The source rev file does not exist. This can happen if the
11383                * source repository is being packed concurrently with this
11384                * hotcopy operation.
11385                *
11386                * If the new revision is now packed, and the youngest revision
11387                * we're interested in is not inside this pack, try to copy the
11388                * pack instead.
11389                *
11390                * If the youngest revision ended up being packed, don't try
11391                * to be smart and work around this. Just abort the hotcopy. */
11392               SVN_ERR(update_min_unpacked_rev(src_fs, pool));
11393               if (is_packed_rev(src_fs, rev))
11394                 {
11395                   if (is_packed_rev(src_fs, src_youngest))
11396                     return svn_error_createf(
11397                              SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11398                              _("The assumed HEAD revision (%lu) of the "
11399                                "hotcopy source has been packed while the "
11400                                "hotcopy was in progress; please restart "
11401                                "the hotcopy operation"),
11402                              src_youngest);
11403
11404                   SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
11405                                                     src_fs, dst_fs,
11406                                                     rev, max_files_per_dir,
11407                                                     iterpool));
11408                   rev = dst_min_unpacked_rev;
11409                   continue;
11410                 }
11411               else
11412                 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
11413                                          _("Revision %lu disappeared from the "
11414                                            "hotcopy source while hotcopy was "
11415                                            "in progress"), rev);
11416             }
11417           else
11418             return svn_error_trace(err);
11419         }
11420
11421       /* Copy the revprop file. */
11422       SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir,
11423                                       revprop_dst_subdir,
11424                                       rev, max_files_per_dir,
11425                                       iterpool));
11426
11427       /* After completing a full shard, update 'current'. */
11428       if (max_files_per_dir && rev % max_files_per_dir == 0)
11429         SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool));
11430     }
11431   svn_pool_destroy(iterpool);
11432
11433   if (cancel_func)
11434     SVN_ERR(cancel_func(cancel_baton));
11435
11436   /* We assume that all revisions were copied now, i.e. we didn't exit the
11437    * above loop early. 'rev' was last incremented during exit of the loop. */
11438   SVN_ERR_ASSERT(rev == src_youngest + 1);
11439
11440   /* All revisions were copied. Update 'current'. */
11441   SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool));
11442
11443   /* Replace the locks tree.
11444    * This is racy in case readers are currently trying to list locks in
11445    * the destination. However, we need to get rid of stale locks.
11446    * This is the simplest way of doing this, so we accept this small race. */
11447   dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
11448   SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
11449                              pool));
11450   src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
11451   SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11452   if (kind == svn_node_dir)
11453     SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
11454                                         PATH_LOCKS_DIR, TRUE,
11455                                         cancel_func, cancel_baton, pool));
11456
11457   /* Now copy the node-origins cache tree. */
11458   src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
11459   SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11460   if (kind == svn_node_dir)
11461     SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path,
11462                                             PATH_NODE_ORIGINS_DIR, TRUE,
11463                                             cancel_func, cancel_baton, pool));
11464
11465   /*
11466    * NB: Data copied below is only read by writers, not readers.
11467    *     Writers are still locked out at this point.
11468    */
11469
11470   if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
11471     {
11472       /* Copy the rep cache and then remove entries for revisions
11473        * younger than the destination's youngest revision. */
11474       src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
11475       dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
11476       SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
11477       if (kind == svn_node_file)
11478         {
11479           SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
11480           SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool));
11481         }
11482     }
11483
11484   /* Copy the txn-current file. */
11485   if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11486     SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
11487                                  PATH_TXN_CURRENT, pool));
11488
11489   /* If a revprop generation file exists in the source filesystem,
11490    * reset it to zero (since this is on a different path, it will not
11491    * overlap with data already in cache).  Also, clean up stale files
11492    * used for the named atomics implementation. */
11493   SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
11494                             &kind, pool));
11495   if (kind == svn_node_file)
11496     SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool));
11497
11498   SVN_ERR(cleanup_revprop_namespace(dst_fs));
11499
11500   /* Hotcopied FS is complete. Stamp it with a format file. */
11501   SVN_ERR(write_format(svn_dirent_join(dst_fs->path, PATH_FORMAT, pool),
11502                        dst_ffd->format, max_files_per_dir, TRUE, pool));
11503
11504   return SVN_NO_ERROR;
11505 }
11506
11507
11508 /* Set up shared data between SRC_FS and DST_FS. */
11509 static void
11510 hotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs)
11511 {
11512   fs_fs_data_t *src_ffd = src_fs->fsap_data;
11513   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11514
11515   /* The common pool and mutexes are shared between src and dst filesystems.
11516    * During hotcopy we only grab the mutexes for the destination, so there
11517    * is no risk of dead-lock. We don't write to the src filesystem. Shared
11518    * data for the src_fs has already been initialised in fs_hotcopy(). */
11519   dst_ffd->shared = src_ffd->shared;
11520 }
11521
11522 /* Create an empty filesystem at DST_FS at DST_PATH with the same
11523  * configuration as SRC_FS (uuid, format, and other parameters).
11524  * After creation DST_FS has no revisions, not even revision zero. */
11525 static svn_error_t *
11526 hotcopy_create_empty_dest(svn_fs_t *src_fs,
11527                           svn_fs_t *dst_fs,
11528                           const char *dst_path,
11529                           apr_pool_t *pool)
11530 {
11531   fs_fs_data_t *src_ffd = src_fs->fsap_data;
11532   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
11533
11534   dst_fs->path = apr_pstrdup(pool, dst_path);
11535
11536   dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir;
11537   dst_ffd->config = src_ffd->config;
11538   dst_ffd->format = src_ffd->format;
11539
11540   /* Create the revision data directories. */
11541   if (dst_ffd->max_files_per_dir)
11542     SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool),
11543                                         pool));
11544   else
11545     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11546                                                         PATH_REVS_DIR, pool),
11547                                         pool));
11548
11549   /* Create the revprops directory. */
11550   if (src_ffd->max_files_per_dir)
11551     SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool),
11552                                         pool));
11553   else
11554     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11555                                                         PATH_REVPROPS_DIR,
11556                                                         pool),
11557                                         pool));
11558
11559   /* Create the transaction directory. */
11560   SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR,
11561                                                       pool),
11562                                       pool));
11563
11564   /* Create the protorevs directory. */
11565   if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
11566     SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
11567                                                         PATH_TXN_PROTOS_DIR,
11568                                                         pool),
11569                                         pool));
11570
11571   /* Create the 'current' file. */
11572   SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool),
11573                              (dst_ffd->format >=
11574                                 SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
11575                                 ? "0\n" : "0 1 1\n"),
11576                              pool));
11577
11578   /* Create lock file and UUID. */
11579   SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool));
11580   SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool));
11581
11582   /* Create the min unpacked rev file. */
11583   if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
11584     SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool),
11585                                                      "0\n", pool));
11586   /* Create the txn-current file if the repository supports
11587      the transaction sequence file. */
11588   if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
11589     {
11590       SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool),
11591                                  "0\n", pool));
11592       SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool),
11593                                  "", pool));
11594     }
11595
11596   dst_ffd->youngest_rev_cache = 0;
11597
11598   hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11599   SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11600
11601   return SVN_NO_ERROR;
11602 }
11603
11604 svn_error_t *
11605 svn_fs_fs__hotcopy(svn_fs_t *src_fs,
11606                    svn_fs_t *dst_fs,
11607                    const char *src_path,
11608                    const char *dst_path,
11609                    svn_boolean_t incremental,
11610                    svn_cancel_func_t cancel_func,
11611                    void *cancel_baton,
11612                    apr_pool_t *pool)
11613 {
11614   struct hotcopy_body_baton hbb;
11615
11616   if (cancel_func)
11617     SVN_ERR(cancel_func(cancel_baton));
11618
11619   SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
11620
11621   if (incremental)
11622     {
11623       const char *dst_format_abspath;
11624       svn_node_kind_t dst_format_kind;
11625
11626       /* Check destination format to be sure we know how to incrementally
11627        * hotcopy to the destination FS. */
11628       dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
11629       SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
11630       if (dst_format_kind == svn_node_none)
11631         {
11632           /* Destination doesn't exist yet. Perform a normal hotcopy to a
11633            * empty destination using the same configuration as the source. */
11634           SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11635         }
11636       else
11637         {
11638           /* Check the existing repository. */
11639           SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
11640           SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
11641                                                           pool));
11642           hotcopy_setup_shared_fs_data(src_fs, dst_fs);
11643           SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
11644         }
11645     }
11646   else
11647     {
11648       /* Start out with an empty destination using the same configuration
11649        * as the source. */
11650       SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
11651     }
11652
11653   if (cancel_func)
11654     SVN_ERR(cancel_func(cancel_baton));
11655
11656   hbb.src_fs = src_fs;
11657   hbb.dst_fs = dst_fs;
11658   hbb.incremental = incremental;
11659   hbb.cancel_func = cancel_func;
11660   hbb.cancel_baton = cancel_baton;
11661   SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool));
11662
11663   return SVN_NO_ERROR;
11664 }