]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_fs_fs/transaction.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_fs_fs / transaction.c
1 /* transaction.c --- transaction-related functions of FSFS
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 "transaction.h"
24
25 #include <assert.h>
26 #include <apr_sha1.h>
27
28 #include "svn_hash.h"
29 #include "svn_props.h"
30 #include "svn_sorts.h"
31 #include "svn_time.h"
32 #include "svn_dirent_uri.h"
33
34 #include "fs_fs.h"
35 #include "index.h"
36 #include "tree.h"
37 #include "util.h"
38 #include "id.h"
39 #include "low_level.h"
40 #include "temp_serializer.h"
41 #include "cached_data.h"
42 #include "lock.h"
43 #include "rep-cache.h"
44
45 #include "private/svn_fs_util.h"
46 #include "private/svn_fspath.h"
47 #include "private/svn_sorts_private.h"
48 #include "private/svn_subr_private.h"
49 #include "private/svn_string_private.h"
50 #include "../libsvn_fs/fs-loader.h"
51
52 #include "svn_private_config.h"
53
54 /* Return the name of the sha1->rep mapping file in transaction TXN_ID
55  * within FS for the given SHA1 checksum.  Use POOL for allocations.
56  */
57 static APR_INLINE const char *
58 path_txn_sha1(svn_fs_t *fs,
59               const svn_fs_fs__id_part_t *txn_id,
60               const unsigned char *sha1,
61               apr_pool_t *pool)
62 {
63   svn_checksum_t checksum;
64   checksum.digest = sha1;
65   checksum.kind = svn_checksum_sha1;
66
67   return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
68                          svn_checksum_to_cstring(&checksum, pool),
69                          pool);
70 }
71
72 static APR_INLINE const char *
73 path_txn_changes(svn_fs_t *fs,
74                  const svn_fs_fs__id_part_t *txn_id,
75                  apr_pool_t *pool)
76 {
77   return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
78                          PATH_CHANGES, pool);
79 }
80
81 static APR_INLINE const char *
82 path_txn_props(svn_fs_t *fs,
83                const svn_fs_fs__id_part_t *txn_id,
84                apr_pool_t *pool)
85 {
86   return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
87                          PATH_TXN_PROPS, pool);
88 }
89
90 static APR_INLINE const char *
91 path_txn_props_final(svn_fs_t *fs,
92                      const svn_fs_fs__id_part_t *txn_id,
93                      apr_pool_t *pool)
94 {
95   return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
96                          PATH_TXN_PROPS_FINAL, pool);
97 }
98
99 static APR_INLINE const char *
100 path_txn_next_ids(svn_fs_t *fs,
101                   const svn_fs_fs__id_part_t *txn_id,
102                   apr_pool_t *pool)
103 {
104   return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
105                          PATH_NEXT_IDS, pool);
106 }
107
108
109 /* The vtable associated with an open transaction object. */
110 static txn_vtable_t txn_vtable = {
111   svn_fs_fs__commit_txn,
112   svn_fs_fs__abort_txn,
113   svn_fs_fs__txn_prop,
114   svn_fs_fs__txn_proplist,
115   svn_fs_fs__change_txn_prop,
116   svn_fs_fs__txn_root,
117   svn_fs_fs__change_txn_props
118 };
119
120 /* FSFS-specific data being attached to svn_fs_txn_t.
121  */
122 typedef struct fs_txn_data_t
123 {
124   /* Strongly typed representation of the TXN's ID member. */
125   svn_fs_fs__id_part_t txn_id;
126 } fs_txn_data_t;
127
128 const svn_fs_fs__id_part_t *
129 svn_fs_fs__txn_get_id(svn_fs_txn_t *txn)
130 {
131   fs_txn_data_t *ftd = txn->fsap_data;
132   return &ftd->txn_id;
133 }
134
135 /* Functions for working with shared transaction data. */
136
137 /* Return the transaction object for transaction TXN_ID from the
138    transaction list of filesystem FS (which must already be locked via the
139    txn_list_lock mutex).  If the transaction does not exist in the list,
140    then create a new transaction object and return it (if CREATE_NEW is
141    true) or return NULL (otherwise). */
142 static fs_fs_shared_txn_data_t *
143 get_shared_txn(svn_fs_t *fs,
144                const svn_fs_fs__id_part_t *txn_id,
145                svn_boolean_t create_new)
146 {
147   fs_fs_data_t *ffd = fs->fsap_data;
148   fs_fs_shared_data_t *ffsd = ffd->shared;
149   fs_fs_shared_txn_data_t *txn;
150
151   for (txn = ffsd->txns; txn; txn = txn->next)
152     if (svn_fs_fs__id_part_eq(&txn->txn_id, txn_id))
153       break;
154
155   if (txn || !create_new)
156     return txn;
157
158   /* Use the transaction object from the (single-object) freelist,
159      if one is available, or otherwise create a new object. */
160   if (ffsd->free_txn)
161     {
162       txn = ffsd->free_txn;
163       ffsd->free_txn = NULL;
164     }
165   else
166     {
167       apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
168       txn = apr_palloc(subpool, sizeof(*txn));
169       txn->pool = subpool;
170     }
171
172   txn->txn_id = *txn_id;
173   txn->being_written = FALSE;
174
175   /* Link this transaction into the head of the list.  We will typically
176      be dealing with only one active transaction at a time, so it makes
177      sense for searches through the transaction list to look at the
178      newest transactions first.  */
179   txn->next = ffsd->txns;
180   ffsd->txns = txn;
181
182   return txn;
183 }
184
185 /* Free the transaction object for transaction TXN_ID, and remove it
186    from the transaction list of filesystem FS (which must already be
187    locked via the txn_list_lock mutex).  Do nothing if the transaction
188    does not exist. */
189 static void
190 free_shared_txn(svn_fs_t *fs, const svn_fs_fs__id_part_t *txn_id)
191 {
192   fs_fs_data_t *ffd = fs->fsap_data;
193   fs_fs_shared_data_t *ffsd = ffd->shared;
194   fs_fs_shared_txn_data_t *txn, *prev = NULL;
195
196   for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
197     if (svn_fs_fs__id_part_eq(&txn->txn_id, txn_id))
198       break;
199
200   if (!txn)
201     return;
202
203   if (prev)
204     prev->next = txn->next;
205   else
206     ffsd->txns = txn->next;
207
208   /* As we typically will be dealing with one transaction after another,
209      we will maintain a single-object free list so that we can hopefully
210      keep reusing the same transaction object. */
211   if (!ffsd->free_txn)
212     ffsd->free_txn = txn;
213   else
214     svn_pool_destroy(txn->pool);
215 }
216
217
218 /* Obtain a lock on the transaction list of filesystem FS, call BODY
219    with FS, BATON, and POOL, and then unlock the transaction list.
220    Return what BODY returned. */
221 static svn_error_t *
222 with_txnlist_lock(svn_fs_t *fs,
223                   svn_error_t *(*body)(svn_fs_t *fs,
224                                        const void *baton,
225                                        apr_pool_t *pool),
226                   const void *baton,
227                   apr_pool_t *pool)
228 {
229   fs_fs_data_t *ffd = fs->fsap_data;
230   fs_fs_shared_data_t *ffsd = ffd->shared;
231
232   SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
233                        body(fs, baton, pool));
234
235   return SVN_NO_ERROR;
236 }
237
238
239 /* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
240    which see. */
241 struct unlock_proto_rev_baton
242 {
243   svn_fs_fs__id_part_t txn_id;
244   void *lockcookie;
245 };
246
247 /* Callback used in the implementation of unlock_proto_rev(). */
248 static svn_error_t *
249 unlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
250 {
251   const struct unlock_proto_rev_baton *b = baton;
252   apr_file_t *lockfile = b->lockcookie;
253   fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, &b->txn_id, FALSE);
254   apr_status_t apr_err;
255
256   if (!txn)
257     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
258                              _("Can't unlock unknown transaction '%s'"),
259                              svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
260   if (!txn->being_written)
261     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
262                              _("Can't unlock nonlocked transaction '%s'"),
263                              svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
264
265   apr_err = apr_file_unlock(lockfile);
266   if (apr_err)
267     return svn_error_wrap_apr
268       (apr_err,
269        _("Can't unlock prototype revision lockfile for transaction '%s'"),
270        svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
271   apr_err = apr_file_close(lockfile);
272   if (apr_err)
273     return svn_error_wrap_apr
274       (apr_err,
275        _("Can't close prototype revision lockfile for transaction '%s'"),
276        svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
277
278   txn->being_written = FALSE;
279
280   return SVN_NO_ERROR;
281 }
282
283 /* Unlock the prototype revision file for transaction TXN_ID in filesystem
284    FS using cookie LOCKCOOKIE.  The original prototype revision file must
285    have been closed _before_ calling this function.
286
287    Perform temporary allocations in POOL. */
288 static svn_error_t *
289 unlock_proto_rev(svn_fs_t *fs,
290                  const svn_fs_fs__id_part_t *txn_id,
291                  void *lockcookie,
292                  apr_pool_t *pool)
293 {
294   struct unlock_proto_rev_baton b;
295
296   b.txn_id = *txn_id;
297   b.lockcookie = lockcookie;
298   return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
299 }
300
301 /* A structure used by get_writable_proto_rev() and
302    get_writable_proto_rev_body(), which see. */
303 struct get_writable_proto_rev_baton
304 {
305   void **lockcookie;
306   svn_fs_fs__id_part_t txn_id;
307 };
308
309 /* Callback used in the implementation of get_writable_proto_rev(). */
310 static svn_error_t *
311 get_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
312 {
313   const struct get_writable_proto_rev_baton *b = baton;
314   void **lockcookie = b->lockcookie;
315   fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, &b->txn_id, TRUE);
316
317   /* First, ensure that no thread in this process (including this one)
318      is currently writing to this transaction's proto-rev file. */
319   if (txn->being_written)
320     return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
321                              _("Cannot write to the prototype revision file "
322                                "of transaction '%s' because a previous "
323                                "representation is currently being written by "
324                                "this process"),
325                              svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
326
327
328   /* We know that no thread in this process is writing to the proto-rev
329      file, and by extension, that no thread in this process is holding a
330      lock on the prototype revision lock file.  It is therefore safe
331      for us to attempt to lock this file, to see if any other process
332      is holding a lock. */
333
334   {
335     apr_file_t *lockfile;
336     apr_status_t apr_err;
337     const char *lockfile_path
338       = svn_fs_fs__path_txn_proto_rev_lock(fs, &b->txn_id, pool);
339
340     /* Open the proto-rev lockfile, creating it if necessary, as it may
341        not exist if the transaction dates from before the lockfiles were
342        introduced.
343
344        ### We'd also like to use something like svn_io_file_lock2(), but
345            that forces us to create a subpool just to be able to unlock
346            the file, which seems a waste. */
347     SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
348                              APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
349
350     apr_err = apr_file_lock(lockfile,
351                             APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
352     if (apr_err)
353       {
354         svn_error_clear(svn_io_file_close(lockfile, pool));
355
356         if (APR_STATUS_IS_EAGAIN(apr_err))
357           return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
358                                    _("Cannot write to the prototype revision "
359                                      "file of transaction '%s' because a "
360                                      "previous representation is currently "
361                                      "being written by another process"),
362                                    svn_fs_fs__id_txn_unparse(&b->txn_id,
363                                                              pool));
364
365         return svn_error_wrap_apr(apr_err,
366                                   _("Can't get exclusive lock on file '%s'"),
367                                   svn_dirent_local_style(lockfile_path, pool));
368       }
369
370     *lockcookie = lockfile;
371   }
372
373   /* We've successfully locked the transaction; mark it as such. */
374   txn->being_written = TRUE;
375
376   return SVN_NO_ERROR;
377 }
378
379 /* Make sure the length ACTUAL_LENGTH of the proto-revision file PROTO_REV
380    of transaction TXN_ID in filesystem FS matches the proto-index file.
381    Trim any crash / failure related extra data from the proto-rev file.
382
383    If the prototype revision file is too short, we can't do much but bail out.
384
385    Perform all allocations in POOL. */
386 static svn_error_t *
387 auto_truncate_proto_rev(svn_fs_t *fs,
388                         apr_file_t *proto_rev,
389                         apr_off_t actual_length,
390                         const svn_fs_fs__id_part_t *txn_id,
391                         apr_pool_t *pool)
392 {
393   /* Only relevant for newer FSFS formats. */
394   if (svn_fs_fs__use_log_addressing(fs))
395     {
396       /* Determine file range covered by the proto-index so far.  Note that
397          we always append to both file, i.e. the last index entry also
398          corresponds to the last addition in the rev file. */
399       const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool);
400       apr_file_t *file;
401       apr_off_t indexed_length;
402
403       SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool));
404       SVN_ERR(svn_fs_fs__p2l_proto_index_next_offset(&indexed_length, file,
405                                                      pool));
406       SVN_ERR(svn_io_file_close(file, pool));
407
408       /* Handle mismatches. */
409       if (indexed_length < actual_length)
410         SVN_ERR(svn_io_file_trunc(proto_rev, indexed_length, pool));
411       else if (indexed_length > actual_length)
412         return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
413                                  NULL,
414                                  _("p2l proto index offset %s beyond proto"
415                                    "rev file size %s for TXN %s"),
416                                    apr_off_t_toa(pool, indexed_length),
417                                    apr_off_t_toa(pool, actual_length),
418                                    svn_fs_fs__id_txn_unparse(txn_id, pool));
419     }
420
421   return SVN_NO_ERROR;
422 }
423
424 /* Get a handle to the prototype revision file for transaction TXN_ID in
425    filesystem FS, and lock it for writing.  Return FILE, a file handle
426    positioned at the end of the file, and LOCKCOOKIE, a cookie that
427    should be passed to unlock_proto_rev() to unlock the file once FILE
428    has been closed.
429
430    If the prototype revision file is already locked, return error
431    SVN_ERR_FS_REP_BEING_WRITTEN.
432
433    Perform all allocations in POOL. */
434 static svn_error_t *
435 get_writable_proto_rev(apr_file_t **file,
436                        void **lockcookie,
437                        svn_fs_t *fs,
438                        const svn_fs_fs__id_part_t *txn_id,
439                        apr_pool_t *pool)
440 {
441   struct get_writable_proto_rev_baton b;
442   svn_error_t *err;
443   apr_off_t end_offset = 0;
444
445   b.lockcookie = lockcookie;
446   b.txn_id = *txn_id;
447
448   SVN_ERR(with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool));
449
450   /* Now open the prototype revision file and seek to the end. */
451   err = svn_io_file_open(file,
452                          svn_fs_fs__path_txn_proto_rev(fs, txn_id, pool),
453                          APR_READ | APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT,
454                          pool);
455
456   /* You might expect that we could dispense with the following seek
457      and achieve the same thing by opening the file using APR_APPEND.
458      Unfortunately, APR's buffered file implementation unconditionally
459      places its initial file pointer at the start of the file (even for
460      files opened with APR_APPEND), so we need this seek to reconcile
461      the APR file pointer to the OS file pointer (since we need to be
462      able to read the current file position later). */
463   if (!err)
464     err = svn_io_file_seek(*file, APR_END, &end_offset, pool);
465
466   /* We don't want unused sections (such as leftovers from failed delta
467      stream) in our file.  If we use log addressing, we would need an
468      index entry for the unused section and that section would need to
469      be all NUL by convention.  So, detect and fix those cases by truncating
470      the protorev file. */
471   if (!err)
472     err = auto_truncate_proto_rev(fs, *file, end_offset, txn_id, pool);
473
474   if (err)
475     {
476       err = svn_error_compose_create(
477               err,
478               unlock_proto_rev(fs, txn_id, *lockcookie, pool));
479
480       *lockcookie = NULL;
481     }
482
483   return svn_error_trace(err);
484 }
485
486 /* Callback used in the implementation of purge_shared_txn(). */
487 static svn_error_t *
488 purge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
489 {
490   const svn_fs_fs__id_part_t *txn_id = baton;
491
492   free_shared_txn(fs, txn_id);
493   svn_fs_fs__reset_txn_caches(fs);
494
495   return SVN_NO_ERROR;
496 }
497
498 /* Purge the shared data for transaction TXN_ID in filesystem FS.
499    Perform all allocations in POOL. */
500 static svn_error_t *
501 purge_shared_txn(svn_fs_t *fs,
502                  const svn_fs_fs__id_part_t *txn_id,
503                  apr_pool_t *pool)
504 {
505   return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
506 }
507 \f
508
509 svn_error_t *
510 svn_fs_fs__put_node_revision(svn_fs_t *fs,
511                              const svn_fs_id_t *id,
512                              node_revision_t *noderev,
513                              svn_boolean_t fresh_txn_root,
514                              apr_pool_t *pool)
515 {
516   fs_fs_data_t *ffd = fs->fsap_data;
517   apr_file_t *noderev_file;
518
519   noderev->is_fresh_txn_root = fresh_txn_root;
520
521   if (! svn_fs_fs__id_is_txn(id))
522     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
523                              _("Attempted to write to non-transaction '%s'"),
524                              svn_fs_fs__id_unparse(id, pool)->data);
525
526   SVN_ERR(svn_io_file_open(&noderev_file,
527                            svn_fs_fs__path_txn_node_rev(fs, id, pool),
528                            APR_WRITE | APR_CREATE | APR_TRUNCATE
529                            | APR_BUFFERED, APR_OS_DEFAULT, pool));
530
531   SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
532                                                             pool),
533                                    noderev, ffd->format,
534                                    svn_fs_fs__fs_supports_mergeinfo(fs),
535                                    pool));
536
537   SVN_ERR(svn_io_file_close(noderev_file, pool));
538
539   return SVN_NO_ERROR;
540 }
541
542 /* For the in-transaction NODEREV within FS, write the sha1->rep mapping
543  * file in the respective transaction, if rep sharing has been enabled etc.
544  * Use SCATCH_POOL for temporary allocations.
545  */
546 static svn_error_t *
547 store_sha1_rep_mapping(svn_fs_t *fs,
548                        node_revision_t *noderev,
549                        apr_pool_t *scratch_pool)
550 {
551   fs_fs_data_t *ffd = fs->fsap_data;
552
553   /* if rep sharing has been enabled and the noderev has a data rep and
554    * its SHA-1 is known, store the rep struct under its SHA1. */
555   if (   ffd->rep_sharing_allowed
556       && noderev->data_rep
557       && noderev->data_rep->has_sha1)
558     {
559       apr_file_t *rep_file;
560       const char *file_name = path_txn_sha1(fs,
561                                             &noderev->data_rep->txn_id,
562                                             noderev->data_rep->sha1_digest,
563                                             scratch_pool);
564       svn_stringbuf_t *rep_string
565         = svn_fs_fs__unparse_representation(noderev->data_rep,
566                                             ffd->format,
567                                             (noderev->kind == svn_node_dir),
568                                             scratch_pool, scratch_pool);
569       SVN_ERR(svn_io_file_open(&rep_file, file_name,
570                                APR_WRITE | APR_CREATE | APR_TRUNCATE
571                                | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
572
573       SVN_ERR(svn_io_file_write_full(rep_file, rep_string->data,
574                                      rep_string->len, NULL, scratch_pool));
575
576       SVN_ERR(svn_io_file_close(rep_file, scratch_pool));
577     }
578
579   return SVN_NO_ERROR;
580 }
581
582 static svn_error_t *
583 unparse_dir_entry(svn_fs_dirent_t *dirent,
584                   svn_stream_t *stream,
585                   apr_pool_t *pool)
586 {
587   const char *val
588     = apr_psprintf(pool, "%s %s",
589                    (dirent->kind == svn_node_file) ? SVN_FS_FS__KIND_FILE
590                                                    : SVN_FS_FS__KIND_DIR,
591                    svn_fs_fs__id_unparse(dirent->id, pool)->data);
592
593   SVN_ERR(svn_stream_printf(stream, pool, "K %" APR_SIZE_T_FMT "\n%s\n"
594                             "V %" APR_SIZE_T_FMT "\n%s\n",
595                             strlen(dirent->name), dirent->name,
596                             strlen(val), val));
597   return SVN_NO_ERROR;
598 }
599
600 /* Write the directory given as array of dirent structs in ENTRIES to STREAM.
601    Perform temporary allocations in POOL. */
602 static svn_error_t *
603 unparse_dir_entries(apr_array_header_t *entries,
604                     svn_stream_t *stream,
605                     apr_pool_t *pool)
606 {
607   apr_pool_t *iterpool = svn_pool_create(pool);
608   int i;
609   for (i = 0; i < entries->nelts; ++i)
610     {
611       svn_fs_dirent_t *dirent;
612
613       svn_pool_clear(iterpool);
614       dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
615       SVN_ERR(unparse_dir_entry(dirent, stream, iterpool));
616     }
617
618   SVN_ERR(svn_stream_printf(stream, pool, "%s\n", SVN_HASH_TERMINATOR));
619
620   svn_pool_destroy(iterpool);
621   return SVN_NO_ERROR;
622 }
623
624 /* Return a deep copy of SOURCE and allocate it in RESULT_POOL.
625  */
626 static svn_fs_path_change2_t *
627 path_change_dup(const svn_fs_path_change2_t *source,
628                 apr_pool_t *result_pool)
629 {
630   svn_fs_path_change2_t *result = apr_pmemdup(result_pool, source,
631                                               sizeof(*source));
632   result->node_rev_id = svn_fs_fs__id_copy(source->node_rev_id, result_pool);
633   if (source->copyfrom_path)
634     result->copyfrom_path = apr_pstrdup(result_pool, source->copyfrom_path);
635
636   return result;
637 }
638
639 /* Merge the internal-use-only CHANGE into a hash of public-FS
640    svn_fs_path_change2_t CHANGED_PATHS, collapsing multiple changes into a
641    single summarical (is that real word?) change per path.  DELETIONS is
642    also a path->svn_fs_path_change2_t hash and contains all the deletions
643    that got turned into a replacement. */
644 static svn_error_t *
645 fold_change(apr_hash_t *changed_paths,
646             apr_hash_t *deletions,
647             const change_t *change)
648 {
649   apr_pool_t *pool = apr_hash_pool_get(changed_paths);
650   svn_fs_path_change2_t *old_change, *new_change;
651   const svn_string_t *path = &change->path;
652   const svn_fs_path_change2_t *info = &change->info;
653
654   if ((old_change = apr_hash_get(changed_paths, path->data, path->len)))
655     {
656       /* This path already exists in the hash, so we have to merge
657          this change into the already existing one. */
658
659       /* Sanity check:  only allow NULL node revision ID in the
660          `reset' case. */
661       if ((! info->node_rev_id)
662            && (info->change_kind != svn_fs_path_change_reset))
663         return svn_error_create
664           (SVN_ERR_FS_CORRUPT, NULL,
665            _("Missing required node revision ID"));
666
667       /* Sanity check: we should be talking about the same node
668          revision ID as our last change except where the last change
669          was a deletion. */
670       if (info->node_rev_id
671           && (! svn_fs_fs__id_eq(old_change->node_rev_id, info->node_rev_id))
672           && (old_change->change_kind != svn_fs_path_change_delete))
673         return svn_error_create
674           (SVN_ERR_FS_CORRUPT, NULL,
675            _("Invalid change ordering: new node revision ID "
676              "without delete"));
677
678       /* Sanity check: an add, replacement, or reset must be the first
679          thing to follow a deletion. */
680       if ((old_change->change_kind == svn_fs_path_change_delete)
681           && (! ((info->change_kind == svn_fs_path_change_replace)
682                  || (info->change_kind == svn_fs_path_change_reset)
683                  || (info->change_kind == svn_fs_path_change_add))))
684         return svn_error_create
685           (SVN_ERR_FS_CORRUPT, NULL,
686            _("Invalid change ordering: non-add change on deleted path"));
687
688       /* Sanity check: an add can't follow anything except
689          a delete or reset.  */
690       if ((info->change_kind == svn_fs_path_change_add)
691           && (old_change->change_kind != svn_fs_path_change_delete)
692           && (old_change->change_kind != svn_fs_path_change_reset))
693         return svn_error_create
694           (SVN_ERR_FS_CORRUPT, NULL,
695            _("Invalid change ordering: add change on preexisting path"));
696
697       /* Now, merge that change in. */
698       switch (info->change_kind)
699         {
700         case svn_fs_path_change_reset:
701           /* A reset here will simply remove the path change from the
702              hash. */
703           apr_hash_set(changed_paths, path->data, path->len, NULL);
704           break;
705
706         case svn_fs_path_change_delete:
707           if (old_change->change_kind == svn_fs_path_change_add)
708             {
709               /* If the path was introduced in this transaction via an
710                  add, and we are deleting it, just remove the path
711                  altogether.  (The caller will delete any child paths.) */
712               apr_hash_set(changed_paths, path->data, path->len, NULL);
713             }
714           else if (old_change->change_kind == svn_fs_path_change_replace)
715             {
716               /* A deleting a 'replace' restore the original deletion. */
717               new_change = apr_hash_get(deletions, path->data, path->len);
718               SVN_ERR_ASSERT(new_change);
719               apr_hash_set(changed_paths, path->data, path->len, new_change);
720             }
721           else
722             {
723               /* A deletion overrules a previous change (modify). */
724               new_change = path_change_dup(info, pool);
725               apr_hash_set(changed_paths, path->data, path->len, new_change);
726             }
727           break;
728
729         case svn_fs_path_change_add:
730         case svn_fs_path_change_replace:
731           /* An add at this point must be following a previous delete,
732              so treat it just like a replace.  Remember the original
733              deletion such that we are able to delete this path again
734              (the replacement may have changed node kind and id). */
735           new_change = path_change_dup(info, pool);
736           new_change->change_kind = svn_fs_path_change_replace;
737
738           apr_hash_set(changed_paths, path->data, path->len, new_change);
739
740           /* Remember the original change.
741            * Make sure to allocate the hash key in a durable pool. */
742           apr_hash_set(deletions,
743                        apr_pstrmemdup(apr_hash_pool_get(deletions),
744                                       path->data, path->len),
745                        path->len, old_change);
746           break;
747
748         case svn_fs_path_change_modify:
749         default:
750           /* If the new change modifies some attribute of the node, set
751              the corresponding flag, whether it already was set or not.
752              Note: We do not reset a flag to FALSE if a change is undone. */
753           if (info->text_mod)
754             old_change->text_mod = TRUE;
755           if (info->prop_mod)
756             old_change->prop_mod = TRUE;
757           if (info->mergeinfo_mod == svn_tristate_true)
758             old_change->mergeinfo_mod = svn_tristate_true;
759           break;
760         }
761     }
762   else
763     {
764       /* Add this path.  The API makes no guarantees that this (new) key
765          will not be retained.  Thus, we copy the key into the target pool
766          to ensure a proper lifetime.  */
767       apr_hash_set(changed_paths,
768                    apr_pstrmemdup(pool, path->data, path->len), path->len,
769                    path_change_dup(info, pool));
770     }
771
772   return SVN_NO_ERROR;
773 }
774
775 /* Baton type to be used with process_changes(). */
776 typedef struct process_changes_baton_t
777 {
778   /* Folded list of path changes. */
779   apr_hash_t *changed_paths;
780
781   /* Path changes that are deletions and have been turned into
782      replacements.  If those replacements get deleted again, this
783      container contains the record that we have to revert to. */
784   apr_hash_t *deletions;
785 } process_changes_baton_t;
786
787 /* An implementation of svn_fs_fs__change_receiver_t.
788    Examine all the changed path entries in CHANGES and store them in
789    *CHANGED_PATHS.  Folding is done to remove redundant or unnecessary
790    data. Do all allocations in POOL. */
791 static svn_error_t *
792 process_changes(void *baton_p,
793                 change_t *change,
794                 apr_pool_t *scratch_pool)
795 {
796   process_changes_baton_t *baton = baton_p;
797
798   SVN_ERR(fold_change(baton->changed_paths, baton->deletions, change));
799
800   /* Now, if our change was a deletion or replacement, we have to
801      blow away any changes thus far on paths that are (or, were)
802      children of this path.
803      ### i won't bother with another iteration pool here -- at
804      most we talking about a few extra dups of paths into what
805      is already a temporary subpool.
806   */
807
808   if ((change->info.change_kind == svn_fs_path_change_delete)
809        || (change->info.change_kind == svn_fs_path_change_replace))
810     {
811       apr_hash_index_t *hi;
812
813       /* a potential child path must contain at least 2 more chars
814          (the path separator plus at least one char for the name).
815          Also, we should not assume that all paths have been normalized
816          i.e. some might have trailing path separators.
817       */
818       apr_ssize_t path_len = change->path.len;
819       apr_ssize_t min_child_len = path_len == 0
820                                 ? 1
821                                 : change->path.data[path_len-1] == '/'
822                                     ? path_len + 1
823                                     : path_len + 2;
824
825       /* CAUTION: This is the inner loop of an O(n^2) algorithm.
826          The number of changes to process may be >> 1000.
827          Therefore, keep the inner loop as tight as possible.
828       */
829       for (hi = apr_hash_first(scratch_pool, baton->changed_paths);
830            hi;
831            hi = apr_hash_next(hi))
832         {
833           /* KEY is the path. */
834           const void *path;
835           apr_ssize_t klen;
836           svn_fs_path_change2_t *old_change;
837           apr_hash_this(hi, &path, &klen, (void**)&old_change);
838
839           /* If we come across a child of our path, remove it.
840              Call svn_fspath__skip_ancestor only if there is a chance that
841              this is actually a sub-path.
842            */
843           if (klen >= min_child_len)
844             {
845               const char *child;
846
847               child = svn_fspath__skip_ancestor(change->path.data, path);
848               if (child && child[0] != '\0')
849                 {
850                   apr_hash_set(baton->changed_paths, path, klen, NULL);
851                 }
852             }
853         }
854     }
855
856   return SVN_NO_ERROR;
857 }
858
859 svn_error_t *
860 svn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
861                              svn_fs_t *fs,
862                              const svn_fs_fs__id_part_t *txn_id,
863                              apr_pool_t *pool)
864 {
865   apr_file_t *file;
866   apr_hash_t *changed_paths = apr_hash_make(pool);
867   apr_pool_t *scratch_pool = svn_pool_create(pool);
868   process_changes_baton_t baton;
869
870   baton.changed_paths = changed_paths;
871   baton.deletions = apr_hash_make(scratch_pool);
872
873   SVN_ERR(svn_io_file_open(&file,
874                            path_txn_changes(fs, txn_id, scratch_pool),
875                            APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
876                            scratch_pool));
877
878   SVN_ERR(svn_fs_fs__read_changes_incrementally(
879                                   svn_stream_from_aprfile2(file, TRUE,
880                                                            scratch_pool),
881                                   process_changes, &baton,
882                                   scratch_pool));
883   svn_pool_destroy(scratch_pool);
884
885   *changed_paths_p = changed_paths;
886
887   return SVN_NO_ERROR;
888 }
889
890
891 svn_error_t *
892 svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
893                          svn_fs_t *fs,
894                          svn_revnum_t rev,
895                          apr_pool_t *pool)
896 {
897   apr_hash_t *changed_paths;
898   apr_array_header_t *changes;
899   int i;
900
901   SVN_ERR(svn_fs_fs__get_changes(&changes, fs, rev, pool));
902
903   changed_paths = svn_hash__make(pool);
904   for (i = 0; i < changes->nelts; ++i)
905     {
906       change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
907       apr_hash_set(changed_paths, change->path.data, change->path.len,
908                    &change->info);
909     }
910
911   *changed_paths_p = changed_paths;
912
913   return SVN_NO_ERROR;
914 }
915
916 /* Copy a revision node-rev SRC into the current transaction TXN_ID in
917    the filesystem FS.  This is only used to create the root of a transaction.
918    Allocations are from POOL.  */
919 static svn_error_t *
920 create_new_txn_noderev_from_rev(svn_fs_t *fs,
921                                 const svn_fs_fs__id_part_t *txn_id,
922                                 svn_fs_id_t *src,
923                                 apr_pool_t *pool)
924 {
925   node_revision_t *noderev;
926   const svn_fs_fs__id_part_t *node_id, *copy_id;
927
928   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool, pool));
929
930   if (svn_fs_fs__id_is_txn(noderev->id))
931     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
932                             _("Copying from transactions not allowed"));
933
934   noderev->predecessor_id = noderev->id;
935   noderev->predecessor_count++;
936   noderev->copyfrom_path = NULL;
937   noderev->copyfrom_rev = SVN_INVALID_REVNUM;
938
939   /* For the transaction root, the copyroot never changes. */
940
941   node_id = svn_fs_fs__id_node_id(noderev->id);
942   copy_id = svn_fs_fs__id_copy_id(noderev->id);
943   noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
944
945   return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
946 }
947
948 /* A structure used by get_and_increment_txn_key_body(). */
949 struct get_and_increment_txn_key_baton {
950   svn_fs_t *fs;
951   apr_uint64_t txn_number;
952   apr_pool_t *pool;
953 };
954
955 /* Callback used in the implementation of create_txn_dir().  This gets
956    the current base 36 value in PATH_TXN_CURRENT and increments it.
957    It returns the original value by the baton. */
958 static svn_error_t *
959 get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
960 {
961   struct get_and_increment_txn_key_baton *cb = baton;
962   const char *txn_current_filename
963     = svn_fs_fs__path_txn_current(cb->fs, pool);
964   char new_id_str[SVN_INT64_BUFFER_SIZE + 1]; /* add space for a newline */
965   apr_size_t line_length;
966
967   svn_stringbuf_t *buf;
968   SVN_ERR(svn_fs_fs__read_content(&buf, txn_current_filename, cb->pool));
969
970   /* assign the current txn counter value to our result */
971   cb->txn_number = svn__base36toui64(NULL, buf->data);
972
973   /* remove trailing newlines */
974   line_length = svn__ui64tobase36(new_id_str, cb->txn_number+1);
975   new_id_str[line_length] = '\n';
976
977   /* Increment the key and add a trailing \n to the string so the
978      txn-current file has a newline in it. */
979   SVN_ERR(svn_io_write_atomic(txn_current_filename, new_id_str,
980                               line_length + 1,
981                               txn_current_filename /* copy_perms path */,
982                               pool));
983
984   return SVN_NO_ERROR;
985 }
986
987 /* Create a unique directory for a transaction in FS based on revision REV.
988    Return the ID for this transaction in *ID_P and *TXN_ID.  Use a sequence
989    value in the transaction ID to prevent reuse of transaction IDs. */
990 static svn_error_t *
991 create_txn_dir(const char **id_p,
992                svn_fs_fs__id_part_t *txn_id,
993                svn_fs_t *fs,
994                svn_revnum_t rev,
995                apr_pool_t *pool)
996 {
997   struct get_and_increment_txn_key_baton cb;
998   const char *txn_dir;
999
1000   /* Get the current transaction sequence value, which is a base-36
1001      number, from the txn-current file, and write an
1002      incremented value back out to the file.  Place the revision
1003      number the transaction is based off into the transaction id. */
1004   cb.pool = pool;
1005   cb.fs = fs;
1006   SVN_ERR(svn_fs_fs__with_txn_current_lock(fs,
1007                                            get_and_increment_txn_key_body,
1008                                            &cb,
1009                                            pool));
1010   txn_id->revision = rev;
1011   txn_id->number = cb.txn_number;
1012
1013   *id_p = svn_fs_fs__id_txn_unparse(txn_id, pool);
1014   txn_dir = svn_fs_fs__path_txn_dir(fs, txn_id, pool);
1015
1016   return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
1017 }
1018
1019 /* Create a unique directory for a transaction in FS based on revision
1020    REV.  Return the ID for this transaction in *ID_P and *TXN_ID.  This
1021    implementation is used in svn 1.4 and earlier repositories and is
1022    kept in 1.5 and greater to support the --pre-1.4-compatible and
1023    --pre-1.5-compatible repository creation options.  Reused
1024    transaction IDs are possible with this implementation. */
1025 static svn_error_t *
1026 create_txn_dir_pre_1_5(const char **id_p,
1027                        svn_fs_fs__id_part_t *txn_id,
1028                        svn_fs_t *fs,
1029                        svn_revnum_t rev,
1030                        apr_pool_t *pool)
1031 {
1032   unsigned int i;
1033   apr_pool_t *subpool;
1034   const char *unique_path, *prefix;
1035
1036   /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
1037   prefix = svn_dirent_join(svn_fs_fs__path_txns_dir(fs, pool),
1038                            apr_psprintf(pool, "%ld", rev), pool);
1039
1040   subpool = svn_pool_create(pool);
1041   for (i = 1; i <= 99999; i++)
1042     {
1043       svn_error_t *err;
1044
1045       svn_pool_clear(subpool);
1046       unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
1047       err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
1048       if (! err)
1049         {
1050           /* We succeeded.  Return the basename minus the ".txn" extension. */
1051           const char *name = svn_dirent_basename(unique_path, subpool);
1052           *id_p = apr_pstrndup(pool, name,
1053                                strlen(name) - strlen(PATH_EXT_TXN));
1054           SVN_ERR(svn_fs_fs__id_txn_parse(txn_id, *id_p));
1055           svn_pool_destroy(subpool);
1056           return SVN_NO_ERROR;
1057         }
1058       if (! APR_STATUS_IS_EEXIST(err->apr_err))
1059         return svn_error_trace(err);
1060       svn_error_clear(err);
1061     }
1062
1063   return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
1064                            NULL,
1065                            _("Unable to create transaction directory "
1066                              "in '%s' for revision %ld"),
1067                            svn_dirent_local_style(fs->path, pool),
1068                            rev);
1069 }
1070
1071 svn_error_t *
1072 svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
1073                       svn_fs_t *fs,
1074                       svn_revnum_t rev,
1075                       apr_pool_t *pool)
1076 {
1077   fs_fs_data_t *ffd = fs->fsap_data;
1078   svn_fs_txn_t *txn;
1079   fs_txn_data_t *ftd;
1080   svn_fs_id_t *root_id;
1081
1082   txn = apr_pcalloc(pool, sizeof(*txn));
1083   ftd = apr_pcalloc(pool, sizeof(*ftd));
1084
1085   /* Get the txn_id. */
1086   if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1087     SVN_ERR(create_txn_dir(&txn->id, &ftd->txn_id, fs, rev, pool));
1088   else
1089     SVN_ERR(create_txn_dir_pre_1_5(&txn->id, &ftd->txn_id, fs, rev, pool));
1090
1091   txn->fs = fs;
1092   txn->base_rev = rev;
1093
1094   txn->vtable = &txn_vtable;
1095   txn->fsap_data = ftd;
1096   *txn_p = txn;
1097
1098   /* Create a new root node for this transaction. */
1099   SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool, pool));
1100   SVN_ERR(create_new_txn_noderev_from_rev(fs, &ftd->txn_id, root_id, pool));
1101
1102   /* Create an empty rev file. */
1103   SVN_ERR(svn_io_file_create_empty(
1104                     svn_fs_fs__path_txn_proto_rev(fs, &ftd->txn_id, pool),
1105                     pool));
1106
1107   /* Create an empty rev-lock file. */
1108   SVN_ERR(svn_io_file_create_empty(
1109                svn_fs_fs__path_txn_proto_rev_lock(fs, &ftd->txn_id, pool),
1110                pool));
1111
1112   /* Create an empty changes file. */
1113   SVN_ERR(svn_io_file_create_empty(path_txn_changes(fs, &ftd->txn_id, pool),
1114                                    pool));
1115
1116   /* Create the next-ids file. */
1117   return svn_io_file_create(path_txn_next_ids(fs, &ftd->txn_id, pool),
1118                             "0 0\n", pool);
1119 }
1120
1121 /* Store the property list for transaction TXN_ID in PROPLIST.
1122    Perform temporary allocations in POOL. */
1123 static svn_error_t *
1124 get_txn_proplist(apr_hash_t *proplist,
1125                  svn_fs_t *fs,
1126                  const svn_fs_fs__id_part_t *txn_id,
1127                  apr_pool_t *pool)
1128 {
1129   svn_stream_t *stream;
1130   svn_error_t *err;
1131
1132   /* Check for issue #3696. (When we find and fix the cause, we can change
1133    * this to an assertion.) */
1134   if (!txn_id || !svn_fs_fs__id_txn_used(txn_id))
1135     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1136                             _("Internal error: a null transaction id was "
1137                               "passed to get_txn_proplist()"));
1138
1139   /* Open the transaction properties file. */
1140   SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
1141                                    pool, pool));
1142
1143   /* Read in the property list. */
1144   err = svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool);
1145   if (err)
1146     {
1147       svn_error_clear(svn_stream_close(stream));  
1148       return svn_error_quick_wrapf(err,
1149                _("malformed property list in transaction '%s'"),
1150                path_txn_props(fs, txn_id, pool));
1151     }
1152
1153   return svn_stream_close(stream);
1154 }
1155
1156 /* Save the property list PROPS as the revprops for transaction TXN_ID
1157    in FS.  Perform temporary allocations in POOL. */
1158 static svn_error_t *
1159 set_txn_proplist(svn_fs_t *fs,
1160                  const svn_fs_fs__id_part_t *txn_id,
1161                  apr_hash_t *props,
1162                  svn_boolean_t final,
1163                  apr_pool_t *pool)
1164 {
1165   svn_stringbuf_t *buf;
1166   svn_stream_t *stream;
1167
1168   /* Write out the new file contents to BUF. */
1169   buf = svn_stringbuf_create_ensure(1024, pool);
1170   stream = svn_stream_from_stringbuf(buf, pool);
1171   SVN_ERR(svn_hash_write2(props, stream, SVN_HASH_TERMINATOR, pool));
1172   SVN_ERR(svn_stream_close(stream));
1173
1174   /* Open the transaction properties file and write new contents to it. */
1175   SVN_ERR(svn_io_write_atomic((final
1176                                ? path_txn_props_final(fs, txn_id, pool)
1177                                : path_txn_props(fs, txn_id, pool)),
1178                               buf->data, buf->len,
1179                               NULL /* copy_perms_path */, pool));
1180   return SVN_NO_ERROR;
1181 }
1182
1183
1184 svn_error_t *
1185 svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
1186                            const char *name,
1187                            const svn_string_t *value,
1188                            apr_pool_t *pool)
1189 {
1190   apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
1191   svn_prop_t prop;
1192
1193   prop.name = name;
1194   prop.value = value;
1195   APR_ARRAY_PUSH(props, svn_prop_t) = prop;
1196
1197   return svn_fs_fs__change_txn_props(txn, props, pool);
1198 }
1199
1200 svn_error_t *
1201 svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
1202                             const apr_array_header_t *props,
1203                             apr_pool_t *pool)
1204 {
1205   fs_txn_data_t *ftd = txn->fsap_data;
1206   apr_hash_t *txn_prop = apr_hash_make(pool);
1207   int i;
1208   svn_error_t *err;
1209
1210   err = get_txn_proplist(txn_prop, txn->fs, &ftd->txn_id, pool);
1211   /* Here - and here only - we need to deal with the possibility that the
1212      transaction property file doesn't yet exist.  The rest of the
1213      implementation assumes that the file exists, but we're called to set the
1214      initial transaction properties as the transaction is being created. */
1215   if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
1216     svn_error_clear(err);
1217   else if (err)
1218     return svn_error_trace(err);
1219
1220   for (i = 0; i < props->nelts; i++)
1221     {
1222       svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
1223
1224       if (svn_hash_gets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE)
1225           && !strcmp(prop->name, SVN_PROP_REVISION_DATE))
1226         svn_hash_sets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE,
1227                       svn_string_create("1", pool));
1228
1229       svn_hash_sets(txn_prop, prop->name, prop->value);
1230     }
1231
1232   /* Create a new version of the file and write out the new props. */
1233   /* Open the transaction properties file. */
1234   SVN_ERR(set_txn_proplist(txn->fs, &ftd->txn_id, txn_prop, FALSE, pool));
1235
1236   return SVN_NO_ERROR;
1237 }
1238
1239 svn_error_t *
1240 svn_fs_fs__get_txn(transaction_t **txn_p,
1241                    svn_fs_t *fs,
1242                    const svn_fs_fs__id_part_t *txn_id,
1243                    apr_pool_t *pool)
1244 {
1245   transaction_t *txn;
1246   node_revision_t *noderev;
1247   svn_fs_id_t *root_id;
1248
1249   txn = apr_pcalloc(pool, sizeof(*txn));
1250   root_id = svn_fs_fs__id_txn_create_root(txn_id, pool);
1251
1252   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool, pool));
1253
1254   txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
1255   txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
1256   txn->copies = NULL;
1257
1258   *txn_p = txn;
1259
1260   return SVN_NO_ERROR;
1261 }
1262
1263 /* Write out the currently available next node_id NODE_ID and copy_id
1264    COPY_ID for transaction TXN_ID in filesystem FS.  The next node-id is
1265    used both for creating new unique nodes for the given transaction, as
1266    well as uniquifying representations.  Perform temporary allocations in
1267    POOL. */
1268 static svn_error_t *
1269 write_next_ids(svn_fs_t *fs,
1270                const svn_fs_fs__id_part_t *txn_id,
1271                apr_uint64_t node_id,
1272                apr_uint64_t copy_id,
1273                apr_pool_t *pool)
1274 {
1275   apr_file_t *file;
1276   char buffer[2 * SVN_INT64_BUFFER_SIZE + 2];
1277   char *p = buffer;
1278
1279   p += svn__ui64tobase36(p, node_id);
1280   *(p++) = ' ';
1281   p += svn__ui64tobase36(p, copy_id);
1282   *(p++) = '\n';
1283   *(p++) = '\0';
1284
1285   SVN_ERR(svn_io_file_open(&file,
1286                            path_txn_next_ids(fs, txn_id, pool),
1287                            APR_WRITE | APR_TRUNCATE,
1288                            APR_OS_DEFAULT, pool));
1289   SVN_ERR(svn_io_file_write_full(file, buffer, p - buffer, NULL, pool));
1290   return svn_io_file_close(file, pool);
1291 }
1292
1293 /* Find out what the next unique node-id and copy-id are for
1294    transaction TXN_ID in filesystem FS.  Store the results in *NODE_ID
1295    and *COPY_ID.  The next node-id is used both for creating new unique
1296    nodes for the given transaction, as well as uniquifying representations.
1297    Perform all allocations in POOL. */
1298 static svn_error_t *
1299 read_next_ids(apr_uint64_t *node_id,
1300               apr_uint64_t *copy_id,
1301               svn_fs_t *fs,
1302               const svn_fs_fs__id_part_t *txn_id,
1303               apr_pool_t *pool)
1304 {
1305   svn_stringbuf_t *buf;
1306   const char *str;
1307   SVN_ERR(svn_fs_fs__read_content(&buf,
1308                                   path_txn_next_ids(fs, txn_id, pool),
1309                                   pool));
1310
1311   /* Parse this into two separate strings. */
1312
1313   str = buf->data;
1314   *node_id = svn__base36toui64(&str, str);
1315   if (*str != ' ')
1316     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1317                             _("next-id file corrupt"));
1318
1319   ++str;
1320   *copy_id = svn__base36toui64(&str, str);
1321   if (*str != '\n')
1322     return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1323                             _("next-id file corrupt"));
1324
1325   return SVN_NO_ERROR;
1326 }
1327
1328 /* Get a new and unique to this transaction node-id for transaction
1329    TXN_ID in filesystem FS.  Store the new node-id in *NODE_ID_P.
1330    Node-ids are guaranteed to be unique to this transction, but may
1331    not necessarily be sequential.  Perform all allocations in POOL. */
1332 static svn_error_t *
1333 get_new_txn_node_id(svn_fs_fs__id_part_t *node_id_p,
1334                     svn_fs_t *fs,
1335                     const svn_fs_fs__id_part_t *txn_id,
1336                     apr_pool_t *pool)
1337 {
1338   apr_uint64_t node_id, copy_id;
1339
1340   /* First read in the current next-ids file. */
1341   SVN_ERR(read_next_ids(&node_id, &copy_id, fs, txn_id, pool));
1342
1343   node_id_p->revision = SVN_INVALID_REVNUM;
1344   node_id_p->number = node_id;
1345
1346   SVN_ERR(write_next_ids(fs, txn_id, ++node_id, copy_id, pool));
1347
1348   return SVN_NO_ERROR;
1349 }
1350
1351 svn_error_t *
1352 svn_fs_fs__reserve_copy_id(svn_fs_fs__id_part_t *copy_id_p,
1353                            svn_fs_t *fs,
1354                            const svn_fs_fs__id_part_t *txn_id,
1355                            apr_pool_t *pool)
1356 {
1357   apr_uint64_t node_id, copy_id;
1358
1359   /* First read in the current next-ids file. */
1360   SVN_ERR(read_next_ids(&node_id, &copy_id, fs, txn_id, pool));
1361
1362   /* this is an in-txn ID now */
1363   copy_id_p->revision = SVN_INVALID_REVNUM;
1364   copy_id_p->number = copy_id;
1365
1366   /* Update the ID counter file */
1367   SVN_ERR(write_next_ids(fs, txn_id, node_id, ++copy_id, pool));
1368
1369   return SVN_NO_ERROR;
1370 }
1371
1372 svn_error_t *
1373 svn_fs_fs__create_node(const svn_fs_id_t **id_p,
1374                        svn_fs_t *fs,
1375                        node_revision_t *noderev,
1376                        const svn_fs_fs__id_part_t *copy_id,
1377                        const svn_fs_fs__id_part_t *txn_id,
1378                        apr_pool_t *pool)
1379 {
1380   svn_fs_fs__id_part_t node_id;
1381   const svn_fs_id_t *id;
1382
1383   /* Get a new node-id for this node. */
1384   SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
1385
1386   id = svn_fs_fs__id_txn_create(&node_id, copy_id, txn_id, pool);
1387
1388   noderev->id = id;
1389
1390   SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
1391
1392   *id_p = id;
1393
1394   return SVN_NO_ERROR;
1395 }
1396
1397 svn_error_t *
1398 svn_fs_fs__purge_txn(svn_fs_t *fs,
1399                      const char *txn_id_str,
1400                      apr_pool_t *pool)
1401 {
1402   fs_fs_data_t *ffd = fs->fsap_data;
1403   svn_fs_fs__id_part_t txn_id;
1404   SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, txn_id_str));
1405
1406   /* Remove the shared transaction object associated with this transaction. */
1407   SVN_ERR(purge_shared_txn(fs, &txn_id, pool));
1408   /* Remove the directory associated with this transaction. */
1409   SVN_ERR(svn_io_remove_dir2(svn_fs_fs__path_txn_dir(fs, &txn_id, pool),
1410                              FALSE, NULL, NULL, pool));
1411   if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1412     {
1413       /* Delete protorev and its lock, which aren't in the txn
1414          directory.  It's OK if they don't exist (for example, if this
1415          is post-commit and the proto-rev has been moved into
1416          place). */
1417       SVN_ERR(svn_io_remove_file2(
1418                   svn_fs_fs__path_txn_proto_rev(fs, &txn_id, pool),
1419                   TRUE, pool));
1420       SVN_ERR(svn_io_remove_file2(
1421                   svn_fs_fs__path_txn_proto_rev_lock(fs, &txn_id, pool),
1422                   TRUE, pool));
1423     }
1424   return SVN_NO_ERROR;
1425 }
1426
1427
1428 svn_error_t *
1429 svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
1430                      apr_pool_t *pool)
1431 {
1432   SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
1433
1434   /* Now, purge the transaction. */
1435   SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
1436             apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
1437                          txn->id));
1438
1439   return SVN_NO_ERROR;
1440 }
1441
1442 /* Assign the UNIQUIFIER member of REP based on the current state of TXN_ID
1443  * in FS.  Allocate the uniquifier in POOL.
1444  */
1445 static svn_error_t *
1446 set_uniquifier(svn_fs_t *fs,
1447                representation_t *rep,
1448                apr_pool_t *pool)
1449 {
1450   svn_fs_fs__id_part_t temp;
1451   fs_fs_data_t *ffd = fs->fsap_data;
1452
1453   if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
1454     {
1455       SVN_ERR(get_new_txn_node_id(&temp, fs, &rep->txn_id, pool));
1456       rep->uniquifier.noderev_txn_id = rep->txn_id;
1457       rep->uniquifier.number = temp.number;
1458     }
1459
1460   return SVN_NO_ERROR;
1461 }
1462
1463 /* Return TRUE if the TXN_ID member of REP is in use.
1464  */
1465 static svn_boolean_t
1466 is_txn_rep(const representation_t *rep)
1467 {
1468   return svn_fs_fs__id_txn_used(&rep->txn_id);
1469 }
1470
1471 /* Mark the TXN_ID member of REP as "unused".
1472  */
1473 static void
1474 reset_txn_in_rep(representation_t *rep)
1475 {
1476   svn_fs_fs__id_txn_reset(&rep->txn_id);
1477 }
1478
1479 svn_error_t *
1480 svn_fs_fs__set_entry(svn_fs_t *fs,
1481                      const svn_fs_fs__id_part_t *txn_id,
1482                      node_revision_t *parent_noderev,
1483                      const char *name,
1484                      const svn_fs_id_t *id,
1485                      svn_node_kind_t kind,
1486                      apr_pool_t *pool)
1487 {
1488   representation_t *rep = parent_noderev->data_rep;
1489   const char *filename
1490     = svn_fs_fs__path_txn_node_children(fs, parent_noderev->id, pool);
1491   apr_file_t *file;
1492   svn_stream_t *out;
1493   fs_fs_data_t *ffd = fs->fsap_data;
1494   apr_pool_t *subpool = svn_pool_create(pool);
1495
1496   if (!rep || !is_txn_rep(rep))
1497     {
1498       apr_array_header_t *entries;
1499
1500       /* Before we can modify the directory, we need to dump its old
1501          contents into a mutable representation file. */
1502       SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
1503                                           subpool, subpool));
1504       SVN_ERR(svn_io_file_open(&file, filename,
1505                                APR_WRITE | APR_CREATE | APR_BUFFERED,
1506                                APR_OS_DEFAULT, pool));
1507       out = svn_stream_from_aprfile2(file, TRUE, pool);
1508       SVN_ERR(unparse_dir_entries(entries, out, subpool));
1509
1510       svn_pool_clear(subpool);
1511
1512       /* Mark the node-rev's data rep as mutable. */
1513       rep = apr_pcalloc(pool, sizeof(*rep));
1514       rep->revision = SVN_INVALID_REVNUM;
1515       rep->txn_id = *txn_id;
1516       SVN_ERR(set_uniquifier(fs, rep, pool));
1517       parent_noderev->data_rep = rep;
1518       SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
1519                                            parent_noderev, FALSE, pool));
1520     }
1521   else
1522     {
1523       /* The directory rep is already mutable, so just open it for append. */
1524       SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
1525                                APR_OS_DEFAULT, pool));
1526       out = svn_stream_from_aprfile2(file, TRUE, pool);
1527     }
1528
1529   /* if we have a directory cache for this transaction, update it */
1530   if (ffd->txn_dir_cache)
1531     {
1532       /* build parameters: (name, new entry) pair */
1533       const char *key =
1534           svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
1535       replace_baton_t baton;
1536
1537       baton.name = name;
1538       baton.new_entry = NULL;
1539
1540       if (id)
1541         {
1542           baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
1543           baton.new_entry->name = name;
1544           baton.new_entry->kind = kind;
1545           baton.new_entry->id = id;
1546         }
1547
1548       /* actually update the cached directory (if cached) */
1549       SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
1550                                      svn_fs_fs__replace_dir_entry, &baton,
1551                                      subpool));
1552     }
1553   svn_pool_clear(subpool);
1554
1555   /* Append an incremental hash entry for the entry change. */
1556   if (id)
1557     {
1558       svn_fs_dirent_t entry;
1559       entry.name = name;
1560       entry.id = id;
1561       entry.kind = kind;
1562
1563       SVN_ERR(unparse_dir_entry(&entry, out, subpool));
1564     }
1565   else
1566     {
1567       SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
1568                                 strlen(name), name));
1569     }
1570
1571   SVN_ERR(svn_io_file_close(file, subpool));
1572   svn_pool_destroy(subpool);
1573   return SVN_NO_ERROR;
1574 }
1575
1576 svn_error_t *
1577 svn_fs_fs__add_change(svn_fs_t *fs,
1578                       const svn_fs_fs__id_part_t *txn_id,
1579                       const char *path,
1580                       const svn_fs_id_t *id,
1581                       svn_fs_path_change_kind_t change_kind,
1582                       svn_boolean_t text_mod,
1583                       svn_boolean_t prop_mod,
1584                       svn_boolean_t mergeinfo_mod,
1585                       svn_node_kind_t node_kind,
1586                       svn_revnum_t copyfrom_rev,
1587                       const char *copyfrom_path,
1588                       apr_pool_t *pool)
1589 {
1590   apr_file_t *file;
1591   svn_fs_path_change2_t *change;
1592   apr_hash_t *changes = apr_hash_make(pool);
1593
1594   /* Not using APR_BUFFERED to append change in one atomic write operation. */
1595   SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
1596                            APR_APPEND | APR_WRITE | APR_CREATE,
1597                            APR_OS_DEFAULT, pool));
1598
1599   change = svn_fs__path_change_create_internal(id, change_kind, pool);
1600   change->text_mod = text_mod;
1601   change->prop_mod = prop_mod;
1602   change->mergeinfo_mod = mergeinfo_mod
1603                         ? svn_tristate_true
1604                         : svn_tristate_false;
1605   change->node_kind = node_kind;
1606   change->copyfrom_known = TRUE;
1607   change->copyfrom_rev = copyfrom_rev;
1608   if (copyfrom_path)
1609     change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
1610
1611   svn_hash_sets(changes, path, change);
1612   SVN_ERR(svn_fs_fs__write_changes(svn_stream_from_aprfile2(file, TRUE, pool),
1613                                    fs, changes, FALSE, pool));
1614
1615   return svn_io_file_close(file, pool);
1616 }
1617
1618 /* If the transaction TXN_ID in FS uses logical addressing, store the
1619  * (ITEM_INDEX, OFFSET) pair in the txn's log-to-phys proto index file.
1620  * Use POOL for allocations.
1621  */
1622 static svn_error_t *
1623 store_l2p_index_entry(svn_fs_t *fs,
1624                       const svn_fs_fs__id_part_t *txn_id,
1625                       apr_off_t offset,
1626                       apr_uint64_t item_index,
1627                       apr_pool_t *pool)
1628 {
1629   if (svn_fs_fs__use_log_addressing(fs))
1630     {
1631       const char *path = svn_fs_fs__path_l2p_proto_index(fs, txn_id, pool);
1632       apr_file_t *file;
1633       SVN_ERR(svn_fs_fs__l2p_proto_index_open(&file, path, pool));
1634       SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(file, offset,
1635                                                    item_index, pool));
1636       SVN_ERR(svn_io_file_close(file, pool));
1637     }
1638
1639   return SVN_NO_ERROR;
1640 }
1641
1642 /* If the transaction TXN_ID in FS uses logical addressing, store ENTRY
1643  * in the phys-to-log proto index file of transaction TXN_ID.
1644  * Use POOL for allocations.
1645  */
1646 static svn_error_t *
1647 store_p2l_index_entry(svn_fs_t *fs,
1648                       const svn_fs_fs__id_part_t *txn_id,
1649                       svn_fs_fs__p2l_entry_t *entry,
1650                       apr_pool_t *pool)
1651 {
1652   if (svn_fs_fs__use_log_addressing(fs))
1653     {
1654       const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool);
1655       apr_file_t *file;
1656       SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool));
1657       SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(file, entry, pool));
1658       SVN_ERR(svn_io_file_close(file, pool));
1659     }
1660
1661   return SVN_NO_ERROR;
1662 }
1663
1664 /* Allocate an item index for the given MY_OFFSET in the transaction TXN_ID
1665  * of file system FS and return it in *ITEM_INDEX.  For old formats, it
1666  * will simply return the offset as item index; in new formats, it will
1667  * increment the txn's item index counter file and store the mapping in
1668  * the proto index file.  Use POOL for allocations.
1669  */
1670 static svn_error_t *
1671 allocate_item_index(apr_uint64_t *item_index,
1672                     svn_fs_t *fs,
1673                     const svn_fs_fs__id_part_t *txn_id,
1674                     apr_off_t my_offset,
1675                     apr_pool_t *pool)
1676 {
1677   if (svn_fs_fs__use_log_addressing(fs))
1678     {
1679       apr_file_t *file;
1680       char buffer[SVN_INT64_BUFFER_SIZE] = { 0 };
1681       svn_boolean_t eof = FALSE;
1682       apr_size_t to_write;
1683       apr_size_t read;
1684       apr_off_t offset = 0;
1685
1686       /* read number, increment it and write it back to disk */
1687       SVN_ERR(svn_io_file_open(&file,
1688                          svn_fs_fs__path_txn_item_index(fs, txn_id, pool),
1689                          APR_READ | APR_WRITE | APR_CREATE | APR_BUFFERED,
1690                          APR_OS_DEFAULT, pool));
1691       SVN_ERR(svn_io_file_read_full2(file, buffer, sizeof(buffer)-1,
1692                                      &read, &eof, pool));
1693       if (read)
1694         SVN_ERR(svn_cstring_atoui64(item_index, buffer));
1695       else
1696         *item_index = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
1697
1698       to_write = svn__ui64toa(buffer, *item_index + 1);
1699       SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));
1700       SVN_ERR(svn_io_file_write_full(file, buffer, to_write, NULL, pool));
1701       SVN_ERR(svn_io_file_close(file, pool));
1702
1703       /* write log-to-phys index */
1704       SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset, *item_index, pool));
1705     }
1706   else
1707     {
1708       *item_index = (apr_uint64_t)my_offset;
1709     }
1710
1711   return SVN_NO_ERROR;
1712 }
1713
1714 /* Baton used by fnv1a_write_handler to calculate the FNV checksum
1715  * before passing the data on to the INNER_STREAM.
1716  */
1717 typedef struct fnv1a_stream_baton_t
1718 {
1719   svn_stream_t *inner_stream;
1720   svn_checksum_ctx_t *context;
1721 } fnv1a_stream_baton_t;
1722
1723 /* Implement svn_write_fn_t.
1724  * Update checksum and pass data on to inner stream.
1725  */
1726 static svn_error_t *
1727 fnv1a_write_handler(void *baton,
1728                     const char *data,
1729                     apr_size_t *len)
1730 {
1731   fnv1a_stream_baton_t *b = baton;
1732
1733   SVN_ERR(svn_checksum_update(b->context, data, *len));
1734   SVN_ERR(svn_stream_write(b->inner_stream, data, len));
1735
1736   return SVN_NO_ERROR;
1737 }
1738
1739 /* Return a stream that calculates a FNV checksum in *CONTEXT
1740  * over all data written to the stream and passes that data on
1741  * to INNER_STREAM.  Allocate objects in POOL.
1742  */
1743 static svn_stream_t *
1744 fnv1a_wrap_stream(svn_checksum_ctx_t **context,
1745                   svn_stream_t *inner_stream,
1746                   apr_pool_t *pool)
1747 {
1748   svn_stream_t *outer_stream;
1749
1750   fnv1a_stream_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
1751   baton->inner_stream = inner_stream;
1752   baton->context = svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, pool);
1753   *context = baton->context;
1754
1755   outer_stream = svn_stream_create(baton, pool);
1756   svn_stream_set_write(outer_stream, fnv1a_write_handler);
1757
1758   return outer_stream;
1759 }
1760
1761 /* Set *DIGEST to the FNV checksum calculated in CONTEXT.
1762  * Use SCRATCH_POOL for temporary allocations.
1763  */
1764 static svn_error_t *
1765 fnv1a_checksum_finalize(apr_uint32_t *digest,
1766                         svn_checksum_ctx_t *context,
1767                         apr_pool_t *scratch_pool)
1768 {
1769   svn_checksum_t *checksum;
1770
1771   SVN_ERR(svn_checksum_final(&checksum, context, scratch_pool));
1772   SVN_ERR_ASSERT(checksum->kind == svn_checksum_fnv1a_32x4);
1773   *digest = ntohl(*(const apr_uint32_t *)(checksum->digest));
1774
1775   return SVN_NO_ERROR;
1776 }
1777
1778 /* This baton is used by the representation writing streams.  It keeps
1779    track of the checksum information as well as the total size of the
1780    representation so far. */
1781 struct rep_write_baton
1782 {
1783   /* The FS we are writing to. */
1784   svn_fs_t *fs;
1785
1786   /* Actual file to which we are writing. */
1787   svn_stream_t *rep_stream;
1788
1789   /* A stream from the delta combiner.  Data written here gets
1790      deltified, then eventually written to rep_stream. */
1791   svn_stream_t *delta_stream;
1792
1793   /* Where is this representation header stored. */
1794   apr_off_t rep_offset;
1795
1796   /* Start of the actual data. */
1797   apr_off_t delta_start;
1798
1799   /* How many bytes have been written to this rep already. */
1800   svn_filesize_t rep_size;
1801
1802   /* The node revision for which we're writing out info. */
1803   node_revision_t *noderev;
1804
1805   /* Actual output file. */
1806   apr_file_t *file;
1807   /* Lock 'cookie' used to unlock the output file once we've finished
1808      writing to it. */
1809   void *lockcookie;
1810
1811   svn_checksum_ctx_t *md5_checksum_ctx;
1812   svn_checksum_ctx_t *sha1_checksum_ctx;
1813
1814   /* calculate a modified FNV-1a checksum of the on-disk representation */
1815   svn_checksum_ctx_t *fnv1a_checksum_ctx;
1816
1817   /* Local / scratch pool, available for temporary allocations. */
1818   apr_pool_t *scratch_pool;
1819
1820   /* Outer / result pool. */
1821   apr_pool_t *result_pool;
1822 };
1823
1824 /* Handler for the write method of the representation writable stream.
1825    BATON is a rep_write_baton, DATA is the data to write, and *LEN is
1826    the length of this data. */
1827 static svn_error_t *
1828 rep_write_contents(void *baton,
1829                    const char *data,
1830                    apr_size_t *len)
1831 {
1832   struct rep_write_baton *b = baton;
1833
1834   SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
1835   SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
1836   b->rep_size += *len;
1837
1838   /* If we are writing a delta, use that stream. */
1839   if (b->delta_stream)
1840     return svn_stream_write(b->delta_stream, data, len);
1841   else
1842     return svn_stream_write(b->rep_stream, data, len);
1843 }
1844
1845 /* Set *SPANNED to the number of shards touched when walking WALK steps on
1846  * NODEREV's predecessor chain in FS.  Use POOL for temporary allocations.
1847  */
1848 static svn_error_t *
1849 shards_spanned(int *spanned,
1850                svn_fs_t *fs,
1851                node_revision_t *noderev,
1852                int walk,
1853                apr_pool_t *pool)
1854 {
1855   fs_fs_data_t *ffd = fs->fsap_data;
1856   int shard_size = ffd->max_files_per_dir ? ffd->max_files_per_dir : 1;
1857   apr_pool_t *iterpool;
1858
1859   int count = walk ? 1 : 0; /* The start of a walk already touches a shard. */
1860   svn_revnum_t shard, last_shard = ffd->youngest_rev_cache / shard_size;
1861   iterpool = svn_pool_create(pool);
1862   while (walk-- && noderev->predecessor_count)
1863     {
1864       svn_pool_clear(iterpool);
1865       SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs,
1866                                            noderev->predecessor_id, pool,
1867                                            iterpool));
1868       shard = svn_fs_fs__id_rev(noderev->id) / shard_size;
1869       if (shard != last_shard)
1870         {
1871           ++count;
1872           last_shard = shard;
1873         }
1874     }
1875   svn_pool_destroy(iterpool);
1876
1877   *spanned = count;
1878   return SVN_NO_ERROR;
1879 }
1880
1881 /* Given a node-revision NODEREV in filesystem FS, return the
1882    representation in *REP to use as the base for a text representation
1883    delta if PROPS is FALSE.  If PROPS has been set, a suitable props
1884    base representation will be returned.  Perform temporary allocations
1885    in *POOL. */
1886 static svn_error_t *
1887 choose_delta_base(representation_t **rep,
1888                   svn_fs_t *fs,
1889                   node_revision_t *noderev,
1890                   svn_boolean_t props,
1891                   apr_pool_t *pool)
1892 {
1893   /* The zero-based index (counting from the "oldest" end), along NODEREVs line
1894    * predecessors, of the node-rev we will use as delta base. */
1895   int count;
1896   /* The length of the linear part of a delta chain.  (Delta chains use
1897    * skip-delta bits for the high-order bits and are linear in the low-order
1898    * bits.) */
1899   int walk;
1900   node_revision_t *base;
1901   fs_fs_data_t *ffd = fs->fsap_data;
1902   apr_pool_t *iterpool;
1903
1904   /* If we have no predecessors, or that one is empty, then use the empty
1905    * stream as a base. */
1906   if (! noderev->predecessor_count)
1907     {
1908       *rep = NULL;
1909       return SVN_NO_ERROR;
1910     }
1911
1912   /* Flip the rightmost '1' bit of the predecessor count to determine
1913      which file rev (counting from 0) we want to use.  (To see why
1914      count & (count - 1) unsets the rightmost set bit, think about how
1915      you decrement a binary number.) */
1916   count = noderev->predecessor_count;
1917   count = count & (count - 1);
1918
1919   /* Finding the delta base over a very long distance can become extremely
1920      expensive for very deep histories, possibly causing client timeouts etc.
1921      OTOH, this is a rare operation and its gains are minimal. Lets simply
1922      start deltification anew close every other 1000 changes or so.  */
1923   walk = noderev->predecessor_count - count;
1924   if (walk > (int)ffd->max_deltification_walk)
1925     {
1926       *rep = NULL;
1927       return SVN_NO_ERROR;
1928     }
1929
1930   /* We use skip delta for limiting the number of delta operations
1931      along very long node histories.  Close to HEAD however, we create
1932      a linear history to minimize delta size.  */
1933   if (walk < (int)ffd->max_linear_deltification)
1934     {
1935       int shards;
1936       SVN_ERR(shards_spanned(&shards, fs, noderev, walk, pool));
1937
1938       /* We also don't want the linear deltification to span more shards
1939          than if deltas we used in a simple skip-delta scheme. */
1940       if ((1 << (--shards)) <= walk)
1941         count = noderev->predecessor_count - 1;
1942     }
1943
1944   /* Walk back a number of predecessors equal to the difference
1945      between count and the original predecessor count.  (For example,
1946      if noderev has ten predecessors and we want the eighth file rev,
1947      walk back two predecessors.) */
1948   base = noderev;
1949   iterpool = svn_pool_create(pool);
1950   while ((count++) < noderev->predecessor_count)
1951     {
1952       svn_pool_clear(iterpool);
1953       SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
1954                                            base->predecessor_id, pool,
1955                                            iterpool));
1956     }
1957   svn_pool_destroy(iterpool);
1958
1959   /* return a suitable base representation */
1960   *rep = props ? base->prop_rep : base->data_rep;
1961
1962   /* if we encountered a shared rep, its parent chain may be different
1963    * from the node-rev parent chain. */
1964   if (*rep)
1965     {
1966       int chain_length = 0;
1967       int shard_count = 0;
1968
1969       /* Very short rep bases are simply not worth it as we are unlikely
1970        * to re-coup the deltification space overhead of 20+ bytes. */
1971       svn_filesize_t rep_size = (*rep)->expanded_size
1972                               ? (*rep)->expanded_size
1973                               : (*rep)->size;
1974       if (rep_size < 64)
1975         {
1976           *rep = NULL;
1977           return SVN_NO_ERROR;
1978         }
1979
1980       /* Check whether the length of the deltification chain is acceptable.
1981        * Otherwise, shared reps may form a non-skipping delta chain in
1982        * extreme cases. */
1983       SVN_ERR(svn_fs_fs__rep_chain_length(&chain_length, &shard_count,
1984                                           *rep, fs, pool));
1985
1986       /* Some reasonable limit, depending on how acceptable longer linear
1987        * chains are in this repo.  Also, allow for some minimal chain. */
1988       if (chain_length >= 2 * (int)ffd->max_linear_deltification + 2)
1989         *rep = NULL;
1990       else
1991         /* To make it worth opening additional shards / pack files, we
1992          * require that the reps have a certain minimal size.  To deltify
1993          * against a rep in different shard, the lower limit is 512 bytes
1994          * and doubles with every extra shard to visit along the delta
1995          * chain. */
1996         if (   shard_count > 1
1997             && ((svn_filesize_t)128 << shard_count) >= rep_size)
1998           *rep = NULL;
1999     }
2000
2001   return SVN_NO_ERROR;
2002 }
2003
2004 /* Something went wrong and the pool for the rep write is being
2005    cleared before we've finished writing the rep.  So we need
2006    to remove the rep from the protorevfile and we need to unlock
2007    the protorevfile. */
2008 static apr_status_t
2009 rep_write_cleanup(void *data)
2010 {
2011   struct rep_write_baton *b = data;
2012   svn_error_t *err;
2013
2014   /* Truncate and close the protorevfile. */
2015   err = svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool);
2016   err = svn_error_compose_create(err, svn_io_file_close(b->file,
2017                                                         b->scratch_pool));
2018
2019   /* Remove our lock regardless of any preceding errors so that the
2020      being_written flag is always removed and stays consistent with the
2021      file lock which will be removed no matter what since the pool is
2022      going away. */
2023   err = svn_error_compose_create(err,
2024                                  unlock_proto_rev(b->fs,
2025                                      svn_fs_fs__id_txn_id(b->noderev->id),
2026                                      b->lockcookie, b->scratch_pool));
2027   if (err)
2028     {
2029       apr_status_t rc = err->apr_err;
2030       svn_error_clear(err);
2031       return rc;
2032     }
2033
2034   return APR_SUCCESS;
2035 }
2036
2037 /* Get a rep_write_baton and store it in *WB_P for the representation
2038    indicated by NODEREV in filesystem FS.  Perform allocations in
2039    POOL.  Only appropriate for file contents, not for props or
2040    directory contents. */
2041 static svn_error_t *
2042 rep_write_get_baton(struct rep_write_baton **wb_p,
2043                     svn_fs_t *fs,
2044                     node_revision_t *noderev,
2045                     apr_pool_t *pool)
2046 {
2047   struct rep_write_baton *b;
2048   apr_file_t *file;
2049   representation_t *base_rep;
2050   svn_stream_t *source;
2051   svn_txdelta_window_handler_t wh;
2052   void *whb;
2053   fs_fs_data_t *ffd = fs->fsap_data;
2054   int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
2055   svn_fs_fs__rep_header_t header = { 0 };
2056
2057   b = apr_pcalloc(pool, sizeof(*b));
2058
2059   b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
2060   b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
2061
2062   b->fs = fs;
2063   b->result_pool = pool;
2064   b->scratch_pool = svn_pool_create(pool);
2065   b->rep_size = 0;
2066   b->noderev = noderev;
2067
2068   /* Open the prototype rev file and seek to its end. */
2069   SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
2070                                  fs, svn_fs_fs__id_txn_id(noderev->id),
2071                                  b->scratch_pool));
2072
2073   b->file = file;
2074   b->rep_stream = fnv1a_wrap_stream(&b->fnv1a_checksum_ctx,
2075                                     svn_stream_from_aprfile2(file, TRUE,
2076                                                              b->scratch_pool),
2077                                     b->scratch_pool);
2078
2079   SVN_ERR(svn_fs_fs__get_file_offset(&b->rep_offset, file, b->scratch_pool));
2080
2081   /* Get the base for this delta. */
2082   SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->scratch_pool));
2083   SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, TRUE,
2084                                   b->scratch_pool));
2085
2086   /* Write out the rep header. */
2087   if (base_rep)
2088     {
2089       header.base_revision = base_rep->revision;
2090       header.base_item_index = base_rep->item_index;
2091       header.base_length = base_rep->size;
2092       header.type = svn_fs_fs__rep_delta;
2093     }
2094   else
2095     {
2096       header.type = svn_fs_fs__rep_self_delta;
2097     }
2098   SVN_ERR(svn_fs_fs__write_rep_header(&header, b->rep_stream,
2099                                       b->scratch_pool));
2100
2101   /* Now determine the offset of the actual svndiff data. */
2102   SVN_ERR(svn_fs_fs__get_file_offset(&b->delta_start, file,
2103                                      b->scratch_pool));
2104
2105   /* Cleanup in case something goes wrong. */
2106   apr_pool_cleanup_register(b->scratch_pool, b, rep_write_cleanup,
2107                             apr_pool_cleanup_null);
2108
2109   /* Prepare to write the svndiff data. */
2110   svn_txdelta_to_svndiff3(&wh,
2111                           &whb,
2112                           b->rep_stream,
2113                           diff_version,
2114                           ffd->delta_compression_level,
2115                           pool);
2116
2117   b->delta_stream = svn_txdelta_target_push(wh, whb, source,
2118                                             b->scratch_pool);
2119
2120   *wb_p = b;
2121
2122   return SVN_NO_ERROR;
2123 }
2124
2125 /* For REP->SHA1_CHECKSUM, try to find an already existing representation
2126    in FS and return it in *OUT_REP.  If no such representation exists or
2127    if rep sharing has been disabled for FS, NULL will be returned.  Since
2128    there may be new duplicate representations within the same uncommitted
2129    revision, those can be passed in REPS_HASH (maps a sha1 digest onto
2130    representation_t*), otherwise pass in NULL for REPS_HASH.
2131    Use RESULT_POOL for *OLD_REP  allocations and SCRATCH_POOL for temporaries.
2132    The lifetime of *OLD_REP is limited by both, RESULT_POOL and REP lifetime.
2133  */
2134 static svn_error_t *
2135 get_shared_rep(representation_t **old_rep,
2136                svn_fs_t *fs,
2137                representation_t *rep,
2138                apr_hash_t *reps_hash,
2139                apr_pool_t *result_pool,
2140                apr_pool_t *scratch_pool)
2141 {
2142   svn_error_t *err;
2143   fs_fs_data_t *ffd = fs->fsap_data;
2144
2145   /* Return NULL, if rep sharing has been disabled. */
2146   *old_rep = NULL;
2147   if (!ffd->rep_sharing_allowed)
2148     return SVN_NO_ERROR;
2149
2150   /* Check and see if we already have a representation somewhere that's
2151      identical to the one we just wrote out.  Start with the hash lookup
2152      because it is cheepest. */
2153   if (reps_hash)
2154     *old_rep = apr_hash_get(reps_hash,
2155                             rep->sha1_digest,
2156                             APR_SHA1_DIGESTSIZE);
2157
2158   /* If we haven't found anything yet, try harder and consult our DB. */
2159   if (*old_rep == NULL)
2160     {
2161       svn_checksum_t checksum;
2162       checksum.digest = rep->sha1_digest;
2163       checksum.kind = svn_checksum_sha1;
2164       err = svn_fs_fs__get_rep_reference(old_rep, fs, &checksum, result_pool);
2165       /* ### Other error codes that we shouldn't mask out? */
2166       if (err == SVN_NO_ERROR)
2167         {
2168           if (*old_rep)
2169             SVN_ERR(svn_fs_fs__check_rep(*old_rep, fs, NULL, scratch_pool));
2170         }
2171       else if (err->apr_err == SVN_ERR_FS_CORRUPT
2172                || SVN_ERROR_IN_CATEGORY(err->apr_err,
2173                                         SVN_ERR_MALFUNC_CATEGORY_START))
2174         {
2175           /* Fatal error; don't mask it.
2176
2177              In particular, this block is triggered when the rep-cache refers
2178              to revisions in the future.  We signal that as a corruption situation
2179              since, once those revisions are less than youngest (because of more
2180              commits), the rep-cache would be invalid.
2181            */
2182           SVN_ERR(err);
2183         }
2184       else
2185         {
2186           /* Something's wrong with the rep-sharing index.  We can continue
2187              without rep-sharing, but warn.
2188            */
2189           (fs->warning)(fs->warning_baton, err);
2190           svn_error_clear(err);
2191           *old_rep = NULL;
2192         }
2193     }
2194
2195   /* look for intra-revision matches (usually data reps but not limited
2196      to them in case props happen to look like some data rep)
2197    */
2198   if (*old_rep == NULL && is_txn_rep(rep))
2199     {
2200       svn_node_kind_t kind;
2201       const char *file_name
2202         = path_txn_sha1(fs, &rep->txn_id, rep->sha1_digest, scratch_pool);
2203
2204       /* in our txn, is there a rep file named with the wanted SHA1?
2205          If so, read it and use that rep.
2206        */
2207       SVN_ERR(svn_io_check_path(file_name, &kind, scratch_pool));
2208       if (kind == svn_node_file)
2209         {
2210           svn_stringbuf_t *rep_string;
2211           SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name,
2212                                            scratch_pool));
2213           SVN_ERR(svn_fs_fs__parse_representation(old_rep, rep_string,
2214                                                   result_pool, scratch_pool));
2215         }
2216     }
2217
2218   if (!*old_rep)
2219     return SVN_NO_ERROR;
2220
2221   /* We don't want 0-length PLAIN representations to replace non-0-length
2222      ones (see issue #4554).  Take into account that EXPANDED_SIZE may be
2223      0 in which case we have to check the on-disk SIZE.  Also, this doubles
2224      as a simple guard against general rep-cache induced corruption. */
2225   if (   ((*old_rep)->expanded_size != rep->expanded_size)
2226       || ((rep->expanded_size == 0) && ((*old_rep)->size != rep->size)))
2227     {
2228       *old_rep = NULL;
2229     }
2230   else
2231     {
2232       /* Add information that is missing in the cached data.
2233          Use the old rep for this content. */
2234       memcpy((*old_rep)->md5_digest, rep->md5_digest, sizeof(rep->md5_digest));
2235       (*old_rep)->uniquifier = rep->uniquifier;
2236     }
2237
2238   return SVN_NO_ERROR;
2239 }
2240
2241 /* Copy the hash sum calculation results from MD5_CTX, SHA1_CTX into REP.
2242  * Use POOL for allocations.
2243  */
2244 static svn_error_t *
2245 digests_final(representation_t *rep,
2246               const svn_checksum_ctx_t *md5_ctx,
2247               const svn_checksum_ctx_t *sha1_ctx,
2248               apr_pool_t *pool)
2249 {
2250   svn_checksum_t *checksum;
2251
2252   SVN_ERR(svn_checksum_final(&checksum, md5_ctx, pool));
2253   memcpy(rep->md5_digest, checksum->digest, svn_checksum_size(checksum));
2254   SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, pool));
2255   rep->has_sha1 = checksum != NULL;
2256   if (rep->has_sha1)
2257     memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
2258
2259   return SVN_NO_ERROR;
2260 }
2261
2262 /* Close handler for the representation write stream.  BATON is a
2263    rep_write_baton.  Writes out a new node-rev that correctly
2264    references the representation we just finished writing. */
2265 static svn_error_t *
2266 rep_write_contents_close(void *baton)
2267 {
2268   struct rep_write_baton *b = baton;
2269   representation_t *rep;
2270   representation_t *old_rep;
2271   apr_off_t offset;
2272
2273   rep = apr_pcalloc(b->result_pool, sizeof(*rep));
2274
2275   /* Close our delta stream so the last bits of svndiff are written
2276      out. */
2277   if (b->delta_stream)
2278     SVN_ERR(svn_stream_close(b->delta_stream));
2279
2280   /* Determine the length of the svndiff data. */
2281   SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
2282   rep->size = offset - b->delta_start;
2283
2284   /* Fill in the rest of the representation field. */
2285   rep->expanded_size = b->rep_size;
2286   rep->txn_id = *svn_fs_fs__id_txn_id(b->noderev->id);
2287   SVN_ERR(set_uniquifier(b->fs, rep, b->scratch_pool));
2288   rep->revision = SVN_INVALID_REVNUM;
2289
2290   /* Finalize the checksum. */
2291   SVN_ERR(digests_final(rep, b->md5_checksum_ctx, b->sha1_checksum_ctx,
2292                         b->result_pool));
2293
2294   /* Check and see if we already have a representation somewhere that's
2295      identical to the one we just wrote out. */
2296   SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->result_pool,
2297                          b->scratch_pool));
2298
2299   if (old_rep)
2300     {
2301       /* We need to erase from the protorev the data we just wrote. */
2302       SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool));
2303
2304       /* Use the old rep for this content. */
2305       b->noderev->data_rep = old_rep;
2306     }
2307   else
2308     {
2309       /* Write out our cosmetic end marker. */
2310       SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
2311       SVN_ERR(allocate_item_index(&rep->item_index, b->fs, &rep->txn_id,
2312                                   b->rep_offset, b->scratch_pool));
2313
2314       b->noderev->data_rep = rep;
2315     }
2316
2317   /* Remove cleanup callback. */
2318   apr_pool_cleanup_kill(b->scratch_pool, b, rep_write_cleanup);
2319
2320   /* Write out the new node-rev information. */
2321   SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev,
2322                                        FALSE, b->scratch_pool));
2323   if (!old_rep)
2324     {
2325       svn_fs_fs__p2l_entry_t entry;
2326
2327       entry.offset = b->rep_offset;
2328       SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
2329       entry.size = offset - b->rep_offset;
2330       entry.type = SVN_FS_FS__ITEM_TYPE_FILE_REP;
2331       entry.item.revision = SVN_INVALID_REVNUM;
2332       entry.item.number = rep->item_index;
2333       SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2334                                       b->fnv1a_checksum_ctx,
2335                                       b->scratch_pool));
2336
2337       SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->scratch_pool));
2338       SVN_ERR(store_p2l_index_entry(b->fs, &rep->txn_id, &entry,
2339                                     b->scratch_pool));
2340     }
2341
2342   SVN_ERR(svn_io_file_close(b->file, b->scratch_pool));
2343   SVN_ERR(unlock_proto_rev(b->fs, &rep->txn_id, b->lockcookie,
2344                            b->scratch_pool));
2345   svn_pool_destroy(b->scratch_pool);
2346
2347   return SVN_NO_ERROR;
2348 }
2349
2350 /* Store a writable stream in *CONTENTS_P that will receive all data
2351    written and store it as the file data representation referenced by
2352    NODEREV in filesystem FS.  Perform temporary allocations in
2353    POOL.  Only appropriate for file data, not props or directory
2354    contents. */
2355 static svn_error_t *
2356 set_representation(svn_stream_t **contents_p,
2357                    svn_fs_t *fs,
2358                    node_revision_t *noderev,
2359                    apr_pool_t *pool)
2360 {
2361   struct rep_write_baton *wb;
2362
2363   if (! svn_fs_fs__id_is_txn(noderev->id))
2364     return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2365                              _("Attempted to write to non-transaction '%s'"),
2366                              svn_fs_fs__id_unparse(noderev->id, pool)->data);
2367
2368   SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
2369
2370   *contents_p = svn_stream_create(wb, pool);
2371   svn_stream_set_write(*contents_p, rep_write_contents);
2372   svn_stream_set_close(*contents_p, rep_write_contents_close);
2373
2374   return SVN_NO_ERROR;
2375 }
2376
2377 svn_error_t *
2378 svn_fs_fs__set_contents(svn_stream_t **stream,
2379                         svn_fs_t *fs,
2380                         node_revision_t *noderev,
2381                         apr_pool_t *pool)
2382 {
2383   if (noderev->kind != svn_node_file)
2384     return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
2385                             _("Can't set text contents of a directory"));
2386
2387   return set_representation(stream, fs, noderev, pool);
2388 }
2389
2390 svn_error_t *
2391 svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
2392                             svn_fs_t *fs,
2393                             const svn_fs_id_t *old_idp,
2394                             node_revision_t *new_noderev,
2395                             const svn_fs_fs__id_part_t *copy_id,
2396                             const svn_fs_fs__id_part_t *txn_id,
2397                             apr_pool_t *pool)
2398 {
2399   const svn_fs_id_t *id;
2400
2401   if (! copy_id)
2402     copy_id = svn_fs_fs__id_copy_id(old_idp);
2403   id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
2404                                 txn_id, pool);
2405
2406   new_noderev->id = id;
2407
2408   if (! new_noderev->copyroot_path)
2409     {
2410       new_noderev->copyroot_path = apr_pstrdup(pool,
2411                                                new_noderev->created_path);
2412       new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
2413     }
2414
2415   SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
2416                                        pool));
2417
2418   *new_id_p = id;
2419
2420   return SVN_NO_ERROR;
2421 }
2422
2423 svn_error_t *
2424 svn_fs_fs__set_proplist(svn_fs_t *fs,
2425                         node_revision_t *noderev,
2426                         apr_hash_t *proplist,
2427                         apr_pool_t *pool)
2428 {
2429   const char *filename
2430     = svn_fs_fs__path_txn_node_props(fs, noderev->id, pool);
2431   apr_file_t *file;
2432   svn_stream_t *out;
2433
2434   /* Dump the property list to the mutable property file. */
2435   SVN_ERR(svn_io_file_open(&file, filename,
2436                            APR_WRITE | APR_CREATE | APR_TRUNCATE
2437                            | APR_BUFFERED, APR_OS_DEFAULT, pool));
2438   out = svn_stream_from_aprfile2(file, TRUE, pool);
2439   SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
2440   SVN_ERR(svn_io_file_close(file, pool));
2441
2442   /* Mark the node-rev's prop rep as mutable, if not already done. */
2443   if (!noderev->prop_rep || !is_txn_rep(noderev->prop_rep))
2444     {
2445       noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
2446       noderev->prop_rep->txn_id = *svn_fs_fs__id_txn_id(noderev->id);
2447       SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE,
2448                                            pool));
2449     }
2450
2451   return SVN_NO_ERROR;
2452 }
2453
2454 /* This baton is used by the stream created for write_container_rep. */
2455 struct write_container_baton
2456 {
2457   svn_stream_t *stream;
2458
2459   apr_size_t size;
2460
2461   svn_checksum_ctx_t *md5_ctx;
2462   svn_checksum_ctx_t *sha1_ctx;
2463 };
2464
2465 /* The handler for the write_container_rep stream.  BATON is a
2466    write_container_baton, DATA has the data to write and *LEN is the number
2467    of bytes to write. */
2468 static svn_error_t *
2469 write_container_handler(void *baton,
2470                         const char *data,
2471                         apr_size_t *len)
2472 {
2473   struct write_container_baton *whb = baton;
2474
2475   SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
2476   SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
2477
2478   SVN_ERR(svn_stream_write(whb->stream, data, len));
2479   whb->size += *len;
2480
2481   return SVN_NO_ERROR;
2482 }
2483
2484 /* Callback function type.  Write the data provided by BATON into STREAM. */
2485 typedef svn_error_t *
2486 (* collection_writer_t)(svn_stream_t *stream, void *baton, apr_pool_t *pool);
2487
2488 /* Implement collection_writer_t writing the C string->svn_string_t hash
2489    given as BATON. */
2490 static svn_error_t *
2491 write_hash_to_stream(svn_stream_t *stream,
2492                      void *baton,
2493                      apr_pool_t *pool)
2494 {
2495   apr_hash_t *hash = baton;
2496   SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
2497
2498   return SVN_NO_ERROR;
2499 }
2500
2501 /* Implement collection_writer_t writing the svn_fs_dirent_t* array given
2502    as BATON. */
2503 static svn_error_t *
2504 write_directory_to_stream(svn_stream_t *stream,
2505                           void *baton,
2506                           apr_pool_t *pool)
2507 {
2508   apr_array_header_t *dir = baton;
2509   SVN_ERR(unparse_dir_entries(dir, stream, pool));
2510
2511   return SVN_NO_ERROR;
2512 }
2513
2514 /* Write out the COLLECTION as a text representation to file FILE using
2515    WRITER.  In the process, record position, the total size of the dump and
2516    MD5 as well as SHA1 in REP.   Add the representation of type ITEM_TYPE to
2517    the indexes if necessary.  If rep sharing has been enabled and REPS_HASH
2518    is not NULL, it will be used in addition to the on-disk cache to find
2519    earlier reps with the same content.  When such existing reps can be
2520    found, we will truncate the one just written from the file and return
2521    the existing rep.  Perform temporary allocations in SCRATCH_POOL. */
2522 static svn_error_t *
2523 write_container_rep(representation_t *rep,
2524                     apr_file_t *file,
2525                     void *collection,
2526                     collection_writer_t writer,
2527                     svn_fs_t *fs,
2528                     apr_hash_t *reps_hash,
2529                     apr_uint32_t item_type,
2530                     apr_pool_t *scratch_pool)
2531 {
2532   svn_stream_t *stream;
2533   struct write_container_baton *whb;
2534   svn_checksum_ctx_t *fnv1a_checksum_ctx;
2535   representation_t *old_rep;
2536   apr_off_t offset = 0;
2537
2538   SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2539
2540   whb = apr_pcalloc(scratch_pool, sizeof(*whb));
2541
2542   whb->stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
2543                                   svn_stream_from_aprfile2(file, TRUE,
2544                                                            scratch_pool),
2545                                   scratch_pool);
2546   whb->size = 0;
2547   whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
2548   whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
2549
2550   stream = svn_stream_create(whb, scratch_pool);
2551   svn_stream_set_write(stream, write_container_handler);
2552
2553   SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
2554
2555   SVN_ERR(writer(stream, collection, scratch_pool));
2556
2557   /* Store the results. */
2558   SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
2559
2560   /* Check and see if we already have a representation somewhere that's
2561      identical to the one we just wrote out. */
2562   SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool,
2563                          scratch_pool));
2564
2565   if (old_rep)
2566     {
2567       /* We need to erase from the protorev the data we just wrote. */
2568       SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
2569
2570       /* Use the old rep for this content. */
2571       memcpy(rep, old_rep, sizeof (*rep));
2572     }
2573   else
2574     {
2575       svn_fs_fs__p2l_entry_t entry;
2576
2577       /* Write out our cosmetic end marker. */
2578       SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
2579
2580       SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
2581                                   offset, scratch_pool));
2582
2583       entry.offset = offset;
2584       SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2585       entry.size = offset - entry.offset;
2586       entry.type = item_type;
2587       entry.item.revision = SVN_INVALID_REVNUM;
2588       entry.item.number = rep->item_index;
2589       SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2590                                       fnv1a_checksum_ctx,
2591                                       scratch_pool));
2592
2593       SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
2594
2595       /* update the representation */
2596       rep->size = whb->size;
2597       rep->expanded_size = whb->size;
2598     }
2599
2600   return SVN_NO_ERROR;
2601 }
2602
2603 /* Write out the COLLECTION pertaining to the NODEREV in FS as a deltified
2604    text representation to file FILE using WRITER.  In the process, record the
2605    total size and the md5 digest in REP and add the representation of type
2606    ITEM_TYPE to the indexes if necessary.  If rep sharing has been enabled and
2607    REPS_HASH is not NULL, it will be used in addition to the on-disk cache to
2608    find earlier reps with the same content.  When such existing reps can be
2609    found, we will truncate the one just written from the file and return the
2610    existing rep.
2611
2612    If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume
2613    that we want to a props representation as the base for our delta.
2614    Perform temporary allocations in SCRATCH_POOL.
2615  */
2616 static svn_error_t *
2617 write_container_delta_rep(representation_t *rep,
2618                           apr_file_t *file,
2619                           void *collection,
2620                           collection_writer_t writer,
2621                           svn_fs_t *fs,
2622                           node_revision_t *noderev,
2623                           apr_hash_t *reps_hash,
2624                           apr_uint32_t item_type,
2625                           apr_pool_t *scratch_pool)
2626 {
2627   svn_txdelta_window_handler_t diff_wh;
2628   void *diff_whb;
2629
2630   svn_stream_t *file_stream;
2631   svn_stream_t *stream;
2632   representation_t *base_rep;
2633   representation_t *old_rep;
2634   svn_checksum_ctx_t *fnv1a_checksum_ctx;
2635   svn_stream_t *source;
2636   svn_fs_fs__rep_header_t header = { 0 };
2637
2638   apr_off_t rep_end = 0;
2639   apr_off_t delta_start = 0;
2640   apr_off_t offset = 0;
2641
2642   struct write_container_baton *whb;
2643   fs_fs_data_t *ffd = fs->fsap_data;
2644   int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
2645   svn_boolean_t is_props = (item_type == SVN_FS_FS__ITEM_TYPE_FILE_PROPS)
2646                         || (item_type == SVN_FS_FS__ITEM_TYPE_DIR_PROPS);
2647
2648   /* Get the base for this delta. */
2649   SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool));
2650   SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, FALSE, scratch_pool));
2651
2652   SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2653
2654   /* Write out the rep header. */
2655   if (base_rep)
2656     {
2657       header.base_revision = base_rep->revision;
2658       header.base_item_index = base_rep->item_index;
2659       header.base_length = base_rep->size;
2660       header.type = svn_fs_fs__rep_delta;
2661     }
2662   else
2663     {
2664       header.type = svn_fs_fs__rep_self_delta;
2665     }
2666
2667   file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
2668                                   svn_stream_from_aprfile2(file, TRUE,
2669                                                            scratch_pool),
2670                                   scratch_pool);
2671   SVN_ERR(svn_fs_fs__write_rep_header(&header, file_stream, scratch_pool));
2672   SVN_ERR(svn_fs_fs__get_file_offset(&delta_start, file, scratch_pool));
2673
2674   /* Prepare to write the svndiff data. */
2675   svn_txdelta_to_svndiff3(&diff_wh,
2676                           &diff_whb,
2677                           file_stream,
2678                           diff_version,
2679                           ffd->delta_compression_level,
2680                           scratch_pool);
2681
2682   whb = apr_pcalloc(scratch_pool, sizeof(*whb));
2683   whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source,
2684                                         scratch_pool);
2685   whb->size = 0;
2686   whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
2687   whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
2688
2689   /* serialize the hash */
2690   stream = svn_stream_create(whb, scratch_pool);
2691   svn_stream_set_write(stream, write_container_handler);
2692
2693   SVN_ERR(writer(stream, collection, scratch_pool));
2694   SVN_ERR(svn_stream_close(whb->stream));
2695
2696   /* Store the results. */
2697   SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
2698
2699   /* Check and see if we already have a representation somewhere that's
2700      identical to the one we just wrote out. */
2701   SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool,
2702                          scratch_pool));
2703
2704   if (old_rep)
2705     {
2706       /* We need to erase from the protorev the data we just wrote. */
2707       SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
2708
2709       /* Use the old rep for this content. */
2710       memcpy(rep, old_rep, sizeof (*rep));
2711     }
2712   else
2713     {
2714       svn_fs_fs__p2l_entry_t entry;
2715
2716       /* Write out our cosmetic end marker. */
2717       SVN_ERR(svn_fs_fs__get_file_offset(&rep_end, file, scratch_pool));
2718       SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
2719
2720       SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
2721                                   offset, scratch_pool));
2722
2723       entry.offset = offset;
2724       SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2725       entry.size = offset - entry.offset;
2726       entry.type = item_type;
2727       entry.item.revision = SVN_INVALID_REVNUM;
2728       entry.item.number = rep->item_index;
2729       SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2730                                       fnv1a_checksum_ctx,
2731                                       scratch_pool));
2732
2733       SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
2734
2735       /* update the representation */
2736       rep->expanded_size = whb->size;
2737       rep->size = rep_end - delta_start;
2738     }
2739
2740   return SVN_NO_ERROR;
2741 }
2742
2743 /* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
2744    of (not yet committed) revision REV in FS.  Use POOL for temporary
2745    allocations.
2746
2747    If you change this function, consider updating svn_fs_fs__verify() too.
2748  */
2749 static svn_error_t *
2750 validate_root_noderev(svn_fs_t *fs,
2751                       node_revision_t *root_noderev,
2752                       svn_revnum_t rev,
2753                       apr_pool_t *pool)
2754 {
2755   svn_revnum_t head_revnum = rev-1;
2756   int head_predecessor_count;
2757
2758   SVN_ERR_ASSERT(rev > 0);
2759
2760   /* Compute HEAD_PREDECESSOR_COUNT. */
2761   {
2762     svn_fs_root_t *head_revision;
2763     const svn_fs_id_t *head_root_id;
2764     node_revision_t *head_root_noderev;
2765
2766     /* Get /@HEAD's noderev. */
2767     SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
2768     SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
2769     SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
2770                                          pool, pool));
2771     head_predecessor_count = head_root_noderev->predecessor_count;
2772   }
2773
2774   /* Check that the root noderev's predecessor count equals REV.
2775
2776      This kind of corruption was seen on svn.apache.org (both on
2777      the root noderev and on other fspaths' noderevs); see
2778      issue #4129.
2779
2780      Normally (rev == root_noderev->predecessor_count), but here we
2781      use a more roundabout check that should only trigger on new instances
2782      of the corruption, rather then trigger on each and every new commit
2783      to a repository that has triggered the bug somewhere in its root
2784      noderev's history.
2785    */
2786   if (root_noderev->predecessor_count != -1
2787       && (root_noderev->predecessor_count - head_predecessor_count)
2788          != (rev - head_revnum))
2789     {
2790       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2791                                _("predecessor count for "
2792                                  "the root node-revision is wrong: "
2793                                  "found (%d+%ld != %d), committing r%ld"),
2794                                  head_predecessor_count,
2795                                  rev - head_revnum, /* This is equal to 1. */
2796                                  root_noderev->predecessor_count,
2797                                  rev);
2798     }
2799
2800   return SVN_NO_ERROR;
2801 }
2802
2803 /* Given the potentially txn-local id PART, update that to a permanent ID
2804  * based on the REVISION currently being written and the START_ID for that
2805  * revision.  Use the repo FORMAT to decide which implementation to use.
2806  */
2807 static void
2808 get_final_id(svn_fs_fs__id_part_t *part,
2809              svn_revnum_t revision,
2810              apr_uint64_t start_id,
2811              int format)
2812 {
2813   if (part->revision == SVN_INVALID_REVNUM)
2814     {
2815       if (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
2816         {
2817           part->revision = revision;
2818         }
2819       else
2820         {
2821           part->revision = 0;
2822           part->number += start_id;
2823         }
2824     }
2825 }
2826
2827 /* Copy a node-revision specified by id ID in fileystem FS from a
2828    transaction into the proto-rev-file FILE.  Set *NEW_ID_P to a
2829    pointer to the new node-id which will be allocated in POOL.
2830    If this is a directory, copy all children as well.
2831
2832    START_NODE_ID and START_COPY_ID are
2833    the first available node and copy ids for this filesystem, for older
2834    FS formats.
2835
2836    REV is the revision number that this proto-rev-file will represent.
2837
2838    INITIAL_OFFSET is the offset of the proto-rev-file on entry to
2839    commit_body.
2840
2841    If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
2842    REPS_POOL) of each data rep that is new in this revision.
2843
2844    If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
2845    of the representations of each property rep that is new in this
2846    revision.
2847
2848    AT_ROOT is true if the node revision being written is the root
2849    node-revision.  It is only controls additional sanity checking
2850    logic.
2851
2852    Temporary allocations are also from POOL. */
2853 static svn_error_t *
2854 write_final_rev(const svn_fs_id_t **new_id_p,
2855                 apr_file_t *file,
2856                 svn_revnum_t rev,
2857                 svn_fs_t *fs,
2858                 const svn_fs_id_t *id,
2859                 apr_uint64_t start_node_id,
2860                 apr_uint64_t start_copy_id,
2861                 apr_off_t initial_offset,
2862                 apr_array_header_t *reps_to_cache,
2863                 apr_hash_t *reps_hash,
2864                 apr_pool_t *reps_pool,
2865                 svn_boolean_t at_root,
2866                 apr_pool_t *pool)
2867 {
2868   node_revision_t *noderev;
2869   apr_off_t my_offset;
2870   const svn_fs_id_t *new_id;
2871   svn_fs_fs__id_part_t node_id, copy_id, rev_item;
2872   fs_fs_data_t *ffd = fs->fsap_data;
2873   const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__id_txn_id(id);
2874   svn_stream_t *file_stream;
2875   svn_checksum_ctx_t *fnv1a_checksum_ctx;
2876   apr_pool_t *subpool;
2877
2878   *new_id_p = NULL;
2879
2880   /* Check to see if this is a transaction node. */
2881   if (! svn_fs_fs__id_is_txn(id))
2882     return SVN_NO_ERROR;
2883
2884   subpool = svn_pool_create(pool);
2885   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, subpool));
2886
2887   if (noderev->kind == svn_node_dir)
2888     {
2889       apr_array_header_t *entries;
2890       int i;
2891
2892       /* This is a directory.  Write out all the children first. */
2893
2894       SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool,
2895                                           subpool));
2896       for (i = 0; i < entries->nelts; ++i)
2897         {
2898           svn_fs_dirent_t *dirent
2899             = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
2900
2901           svn_pool_clear(subpool);
2902           SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
2903                                   start_node_id, start_copy_id, initial_offset,
2904                                   reps_to_cache, reps_hash, reps_pool, FALSE,
2905                                   subpool));
2906           if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
2907             dirent->id = svn_fs_fs__id_copy(new_id, pool);
2908         }
2909
2910       if (noderev->data_rep && is_txn_rep(noderev->data_rep))
2911         {
2912           /* Write out the contents of this directory as a text rep. */
2913           noderev->data_rep->revision = rev;
2914           if (ffd->deltify_directories)
2915             SVN_ERR(write_container_delta_rep(noderev->data_rep, file,
2916                                               entries,
2917                                               write_directory_to_stream,
2918                                               fs, noderev, NULL,
2919                                               SVN_FS_FS__ITEM_TYPE_DIR_REP,
2920                                               pool));
2921           else
2922             SVN_ERR(write_container_rep(noderev->data_rep, file, entries,
2923                                         write_directory_to_stream, fs, NULL,
2924                                         SVN_FS_FS__ITEM_TYPE_DIR_REP, pool));
2925
2926           reset_txn_in_rep(noderev->data_rep);
2927         }
2928     }
2929   else
2930     {
2931       /* This is a file.  We should make sure the data rep, if it
2932          exists in a "this" state, gets rewritten to our new revision
2933          num. */
2934
2935       if (noderev->data_rep && is_txn_rep(noderev->data_rep))
2936         {
2937           reset_txn_in_rep(noderev->data_rep);
2938           noderev->data_rep->revision = rev;
2939
2940           if (!svn_fs_fs__use_log_addressing(fs))
2941             {
2942               /* See issue 3845.  Some unknown mechanism caused the
2943                  protorev file to get truncated, so check for that
2944                  here.  */
2945               if (noderev->data_rep->item_index + noderev->data_rep->size
2946                   > initial_offset)
2947                 return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2948                                         _("Truncated protorev file detected"));
2949             }
2950         }
2951     }
2952
2953   svn_pool_destroy(subpool);
2954
2955   /* Fix up the property reps. */
2956   if (noderev->prop_rep && is_txn_rep(noderev->prop_rep))
2957     {
2958       apr_hash_t *proplist;
2959       apr_uint32_t item_type = noderev->kind == svn_node_dir
2960                              ? SVN_FS_FS__ITEM_TYPE_DIR_PROPS
2961                              : SVN_FS_FS__ITEM_TYPE_FILE_PROPS;
2962       SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
2963
2964       noderev->prop_rep->revision = rev;
2965
2966       if (ffd->deltify_properties)
2967         SVN_ERR(write_container_delta_rep(noderev->prop_rep, file, proplist,
2968                                           write_hash_to_stream, fs, noderev,
2969                                           reps_hash, item_type, pool));
2970       else
2971         SVN_ERR(write_container_rep(noderev->prop_rep, file, proplist,
2972                                     write_hash_to_stream, fs, reps_hash,
2973                                     item_type, pool));
2974
2975       reset_txn_in_rep(noderev->prop_rep);
2976     }
2977
2978   /* Convert our temporary ID into a permanent revision one. */
2979   node_id = *svn_fs_fs__id_node_id(noderev->id);
2980   get_final_id(&node_id, rev, start_node_id, ffd->format);
2981   copy_id = *svn_fs_fs__id_copy_id(noderev->id);
2982   get_final_id(&copy_id, rev, start_copy_id, ffd->format);
2983
2984   if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
2985     noderev->copyroot_rev = rev;
2986
2987   /* root nodes have a fixed ID in log addressing mode */
2988   SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
2989   if (svn_fs_fs__use_log_addressing(fs) && at_root)
2990     {
2991       /* reference the root noderev from the log-to-phys index */
2992       rev_item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
2993       SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset,
2994                                     rev_item.number, pool));
2995     }
2996   else
2997     SVN_ERR(allocate_item_index(&rev_item.number, fs, txn_id,
2998                                 my_offset, pool));
2999
3000   rev_item.revision = rev;
3001   new_id = svn_fs_fs__id_rev_create(&node_id, &copy_id, &rev_item, pool);
3002
3003   noderev->id = new_id;
3004
3005   if (ffd->rep_sharing_allowed)
3006     {
3007       /* Save the data representation's hash in the rep cache. */
3008       if (   noderev->data_rep && noderev->kind == svn_node_file
3009           && noderev->data_rep->revision == rev)
3010         {
3011           SVN_ERR_ASSERT(reps_to_cache && reps_pool);
3012           APR_ARRAY_PUSH(reps_to_cache, representation_t *)
3013             = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
3014         }
3015
3016       if (noderev->prop_rep && noderev->prop_rep->revision == rev)
3017         {
3018           /* Add new property reps to hash and on-disk cache. */
3019           representation_t *copy
3020             = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
3021
3022           SVN_ERR_ASSERT(reps_to_cache && reps_pool);
3023           APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
3024
3025           apr_hash_set(reps_hash,
3026                         copy->sha1_digest,
3027                         APR_SHA1_DIGESTSIZE,
3028                         copy);
3029         }
3030     }
3031
3032   /* don't serialize SHA1 for dirs to disk (waste of space) */
3033   if (noderev->data_rep && noderev->kind == svn_node_dir)
3034     noderev->data_rep->has_sha1 = FALSE;
3035
3036   /* don't serialize SHA1 for props to disk (waste of space) */
3037   if (noderev->prop_rep)
3038     noderev->prop_rep->has_sha1 = FALSE;
3039
3040   /* Workaround issue #4031: is-fresh-txn-root in revision files. */
3041   noderev->is_fresh_txn_root = FALSE;
3042
3043   /* Write out our new node-revision. */
3044   if (at_root)
3045     SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
3046
3047   file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
3048                                   svn_stream_from_aprfile2(file, TRUE, pool),
3049                                   pool);
3050   SVN_ERR(svn_fs_fs__write_noderev(file_stream, noderev, ffd->format,
3051                                    svn_fs_fs__fs_supports_mergeinfo(fs),
3052                                    pool));
3053
3054   /* reference the root noderev from the log-to-phys index */
3055   if (svn_fs_fs__use_log_addressing(fs))
3056     {
3057       svn_fs_fs__p2l_entry_t entry;
3058       rev_item.revision = SVN_INVALID_REVNUM;
3059
3060       entry.offset = my_offset;
3061       SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
3062       entry.size = my_offset - entry.offset;
3063       entry.type = SVN_FS_FS__ITEM_TYPE_NODEREV;
3064       entry.item = rev_item;
3065       SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
3066                                       fnv1a_checksum_ctx,
3067                                       pool));
3068
3069       SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool));
3070     }
3071
3072   /* Return our ID that references the revision file. */
3073   *new_id_p = noderev->id;
3074
3075   return SVN_NO_ERROR;
3076 }
3077
3078 /* Write the changed path info CHANGED_PATHS from transaction TXN_ID to the
3079    permanent rev-file FILE in filesystem FS.  *OFFSET_P is set the to offset
3080    in the file of the beginning of this information.  Perform temporary
3081    allocations in POOL. */
3082 static svn_error_t *
3083 write_final_changed_path_info(apr_off_t *offset_p,
3084                               apr_file_t *file,
3085                               svn_fs_t *fs,
3086                               const svn_fs_fs__id_part_t *txn_id,
3087                               apr_hash_t *changed_paths,
3088                               apr_pool_t *pool)
3089 {
3090   apr_off_t offset;
3091   svn_stream_t *stream;
3092   svn_checksum_ctx_t *fnv1a_checksum_ctx;
3093
3094   SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
3095
3096   /* write to target file & calculate checksum */
3097   stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
3098                              svn_stream_from_aprfile2(file, TRUE, pool),
3099                              pool);
3100   SVN_ERR(svn_fs_fs__write_changes(stream, fs, changed_paths, TRUE, pool));
3101
3102   *offset_p = offset;
3103
3104   /* reference changes from the indexes */
3105   if (svn_fs_fs__use_log_addressing(fs))
3106     {
3107       svn_fs_fs__p2l_entry_t entry;
3108
3109       entry.offset = offset;
3110       SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
3111       entry.size = offset - entry.offset;
3112       entry.type = SVN_FS_FS__ITEM_TYPE_CHANGES;
3113       entry.item.revision = SVN_INVALID_REVNUM;
3114       entry.item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
3115       SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
3116                                       fnv1a_checksum_ctx,
3117                                       pool));
3118
3119       SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool));
3120       SVN_ERR(store_l2p_index_entry(fs, txn_id, entry.offset,
3121                                     SVN_FS_FS__ITEM_INDEX_CHANGES, pool));
3122     }
3123
3124   return SVN_NO_ERROR;
3125 }
3126
3127 /* Open a new svn_fs_t handle to FS, set that handle's concept of "current
3128    youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
3129    NEW_REV's revision root.
3130
3131    Intended to be called as the very last step in a commit before 'current'
3132    is bumped.  This implies that we are holding the write lock. */
3133 static svn_error_t *
3134 verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
3135                                             svn_revnum_t new_rev,
3136                                             apr_pool_t *pool)
3137 {
3138 #ifdef SVN_DEBUG
3139   fs_fs_data_t *ffd = fs->fsap_data;
3140   svn_fs_t *ft; /* fs++ == ft */
3141   svn_fs_root_t *root;
3142   fs_fs_data_t *ft_ffd;
3143   apr_hash_t *fs_config;
3144
3145   SVN_ERR_ASSERT(ffd->svn_fs_open_);
3146
3147   /* make sure FT does not simply return data cached by other instances
3148    * but actually retrieves it from disk at least once.
3149    */
3150   fs_config = apr_hash_make(pool);
3151   svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
3152                            svn_uuid_generate(pool));
3153   SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
3154                             fs_config,
3155                             pool,
3156                             pool));
3157   ft_ffd = ft->fsap_data;
3158   /* Don't let FT consult rep-cache.db, either. */
3159   ft_ffd->rep_sharing_allowed = FALSE;
3160
3161   /* Time travel! */
3162   ft_ffd->youngest_rev_cache = new_rev;
3163
3164   SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
3165   SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
3166   SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
3167   SVN_ERR(svn_fs_fs__verify_root(root, pool));
3168 #endif /* SVN_DEBUG */
3169
3170   return SVN_NO_ERROR;
3171 }
3172
3173 /* Update the 'current' file to hold the correct next node and copy_ids
3174    from transaction TXN_ID in filesystem FS.  The current revision is
3175    set to REV.  Perform temporary allocations in POOL. */
3176 static svn_error_t *
3177 write_final_current(svn_fs_t *fs,
3178                     const svn_fs_fs__id_part_t *txn_id,
3179                     svn_revnum_t rev,
3180                     apr_uint64_t start_node_id,
3181                     apr_uint64_t start_copy_id,
3182                     apr_pool_t *pool)
3183 {
3184   apr_uint64_t txn_node_id;
3185   apr_uint64_t txn_copy_id;
3186   fs_fs_data_t *ffd = fs->fsap_data;
3187
3188   if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
3189     return svn_fs_fs__write_current(fs, rev, 0, 0, pool);
3190
3191   /* To find the next available ids, we add the id that used to be in
3192      the 'current' file, to the next ids from the transaction file. */
3193   SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
3194
3195   start_node_id += txn_node_id;
3196   start_copy_id += txn_copy_id;
3197
3198   return svn_fs_fs__write_current(fs, rev, start_node_id, start_copy_id,
3199                                   pool);
3200 }
3201
3202 /* Verify that the user registered with FS has all the locks necessary to
3203    permit all the changes associated with TXN_NAME.
3204    The FS write lock is assumed to be held by the caller. */
3205 static svn_error_t *
3206 verify_locks(svn_fs_t *fs,
3207              const svn_fs_fs__id_part_t *txn_id,
3208              apr_hash_t *changed_paths,
3209              apr_pool_t *pool)
3210 {
3211   apr_pool_t *iterpool;
3212   apr_array_header_t *changed_paths_sorted;
3213   svn_stringbuf_t *last_recursed = NULL;
3214   int i;
3215
3216   /* Make an array of the changed paths, and sort them depth-first-ily.  */
3217   changed_paths_sorted = svn_sort__hash(changed_paths,
3218                                         svn_sort_compare_items_as_paths,
3219                                         pool);
3220
3221   /* Now, traverse the array of changed paths, verify locks.  Note
3222      that if we need to do a recursive verification a path, we'll skip
3223      over children of that path when we get to them. */
3224   iterpool = svn_pool_create(pool);
3225   for (i = 0; i < changed_paths_sorted->nelts; i++)
3226     {
3227       const svn_sort__item_t *item;
3228       const char *path;
3229       svn_fs_path_change2_t *change;
3230       svn_boolean_t recurse = TRUE;
3231
3232       svn_pool_clear(iterpool);
3233
3234       item = &APR_ARRAY_IDX(changed_paths_sorted, i, svn_sort__item_t);
3235
3236       /* Fetch the change associated with our path.  */
3237       path = item->key;
3238       change = item->value;
3239
3240       /* If this path has already been verified as part of a recursive
3241          check of one of its parents, no need to do it again.  */
3242       if (last_recursed
3243           && svn_fspath__skip_ancestor(last_recursed->data, path))
3244         continue;
3245
3246       /* What does it mean to succeed at lock verification for a given
3247          path?  For an existing file or directory getting modified
3248          (text, props), it means we hold the lock on the file or
3249          directory.  For paths being added or removed, we need to hold
3250          the locks for that path and any children of that path.
3251
3252          WHEW!  We have no reliable way to determine the node kind
3253          of deleted items, but fortunately we are going to do a
3254          recursive check on deleted paths regardless of their kind.  */
3255       if (change->change_kind == svn_fs_path_change_modify)
3256         recurse = FALSE;
3257       SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
3258                                                 iterpool));
3259
3260       /* If we just did a recursive check, remember the path we
3261          checked (so children can be skipped).  */
3262       if (recurse)
3263         {
3264           if (! last_recursed)
3265             last_recursed = svn_stringbuf_create(path, pool);
3266           else
3267             svn_stringbuf_set(last_recursed, path);
3268         }
3269     }
3270   svn_pool_destroy(iterpool);
3271   return SVN_NO_ERROR;
3272 }
3273
3274 /* Return in *PATH the path to a file containing the properties that
3275    make up the final revision properties file.  This involves setting
3276    svn:date and removing any temporary properties associated with the
3277    commit flags. */
3278 static svn_error_t *
3279 write_final_revprop(const char **path,
3280                     svn_fs_txn_t *txn,
3281                     const svn_fs_fs__id_part_t *txn_id,
3282                     apr_pool_t *pool)
3283 {
3284   apr_hash_t *txnprops;
3285   svn_boolean_t final_mods = FALSE;
3286   svn_string_t date;
3287   svn_string_t *client_date;
3288
3289   SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, txn, pool));
3290
3291   /* Remove any temporary txn props representing 'flags'. */
3292   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
3293     {
3294       svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL);
3295       final_mods = TRUE;
3296     }
3297
3298   if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
3299     {
3300       svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL);
3301       final_mods = TRUE;
3302     }
3303
3304   client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE);
3305   if (client_date)
3306     {
3307       svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE, NULL);
3308       final_mods = TRUE;
3309     }
3310
3311   /* Update commit time to ensure that svn:date revprops remain ordered if
3312      requested. */
3313   if (!client_date || strcmp(client_date->data, "1"))
3314     {
3315       date.data = svn_time_to_cstring(apr_time_now(), pool);
3316       date.len = strlen(date.data);
3317       svn_hash_sets(txnprops, SVN_PROP_REVISION_DATE, &date);
3318       final_mods = TRUE;
3319     }
3320
3321   if (final_mods)
3322     {
3323       SVN_ERR(set_txn_proplist(txn->fs, txn_id, txnprops, TRUE, pool));
3324       *path = path_txn_props_final(txn->fs, txn_id, pool);
3325     }
3326   else
3327     {
3328       *path = path_txn_props(txn->fs, txn_id, pool);
3329     }
3330
3331   return SVN_NO_ERROR;
3332 }
3333
3334 svn_error_t *
3335 svn_fs_fs__add_index_data(svn_fs_t *fs,
3336                           apr_file_t *file,
3337                           const char *l2p_proto_index,
3338                           const char *p2l_proto_index,
3339                           svn_revnum_t revision,
3340                           apr_pool_t *pool)
3341 {
3342   apr_off_t l2p_offset;
3343   apr_off_t p2l_offset;
3344   svn_stringbuf_t *footer;
3345   unsigned char footer_length;
3346   svn_checksum_t *l2p_checksum;
3347   svn_checksum_t *p2l_checksum;
3348
3349   /* Append the actual index data to the pack file. */
3350   l2p_offset = 0;
3351   SVN_ERR(svn_io_file_seek(file, APR_END, &l2p_offset, pool));
3352   SVN_ERR(svn_fs_fs__l2p_index_append(&l2p_checksum, fs, file,
3353                                       l2p_proto_index, revision,
3354                                       pool, pool));
3355
3356   p2l_offset = 0;
3357   SVN_ERR(svn_io_file_seek(file, APR_END, &p2l_offset, pool));
3358   SVN_ERR(svn_fs_fs__p2l_index_append(&p2l_checksum, fs, file,
3359                                       p2l_proto_index, revision,
3360                                       pool, pool));
3361
3362   /* Append footer. */
3363   footer = svn_fs_fs__unparse_footer(l2p_offset, l2p_checksum,
3364                                      p2l_offset, p2l_checksum, pool, pool);
3365   SVN_ERR(svn_io_file_write_full(file, footer->data, footer->len, NULL,
3366                                  pool));
3367
3368   footer_length = footer->len;
3369   SVN_ERR_ASSERT(footer_length == footer->len);
3370   SVN_ERR(svn_io_file_write_full(file, &footer_length, 1, NULL, pool));
3371
3372   return SVN_NO_ERROR;
3373 }
3374
3375 /* Baton used for commit_body below. */
3376 struct commit_baton {
3377   svn_revnum_t *new_rev_p;
3378   svn_fs_t *fs;
3379   svn_fs_txn_t *txn;
3380   apr_array_header_t *reps_to_cache;
3381   apr_hash_t *reps_hash;
3382   apr_pool_t *reps_pool;
3383 };
3384
3385 /* The work-horse for svn_fs_fs__commit, called with the FS write lock.
3386    This implements the svn_fs_fs__with_write_lock() 'body' callback
3387    type.  BATON is a 'struct commit_baton *'. */
3388 static svn_error_t *
3389 commit_body(void *baton, apr_pool_t *pool)
3390 {
3391   struct commit_baton *cb = baton;
3392   fs_fs_data_t *ffd = cb->fs->fsap_data;
3393   const char *old_rev_filename, *rev_filename, *proto_filename;
3394   const char *revprop_filename, *final_revprop;
3395   const svn_fs_id_t *root_id, *new_root_id;
3396   apr_uint64_t start_node_id;
3397   apr_uint64_t start_copy_id;
3398   svn_revnum_t old_rev, new_rev;
3399   apr_file_t *proto_file;
3400   void *proto_file_lockcookie;
3401   apr_off_t initial_offset, changed_path_offset;
3402   const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__txn_get_id(cb->txn);
3403   apr_hash_t *changed_paths;
3404
3405   /* Re-Read the current repository format.  All our repo upgrade and
3406      config evaluation strategies are such that existing information in
3407      FS and FFD remains valid.
3408
3409      Although we don't recommend upgrading hot repositories, people may
3410      still do it and we must make sure to either handle them gracefully
3411      or to error out.
3412
3413      Committing pre-format 3 txns will fail after upgrade to format 3+
3414      because the proto-rev cannot be found; no further action needed.
3415      Upgrades from pre-f7 to f7+ means a potential change in addressing
3416      mode for the final rev.  We must be sure to detect that cause because
3417      the failure would only manifest once the new revision got committed.
3418    */
3419   SVN_ERR(svn_fs_fs__read_format_file(cb->fs, pool));
3420
3421   /* Read the current youngest revision and, possibly, the next available
3422      node id and copy id (for old format filesystems).  Update the cached
3423      value for the youngest revision, because we have just checked it. */
3424   SVN_ERR(svn_fs_fs__read_current(&old_rev, &start_node_id, &start_copy_id,
3425                                   cb->fs, pool));
3426   ffd->youngest_rev_cache = old_rev;
3427
3428   /* Check to make sure this transaction is based off the most recent
3429      revision. */
3430   if (cb->txn->base_rev != old_rev)
3431     return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
3432                             _("Transaction out of date"));
3433
3434   /* We need the changes list for verification as well as for writing it
3435      to the final rev file. */
3436   SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, cb->fs, txn_id,
3437                                        pool));
3438
3439   /* Locks may have been added (or stolen) between the calling of
3440      previous svn_fs.h functions and svn_fs_commit_txn(), so we need
3441      to re-examine every changed-path in the txn and re-verify all
3442      discovered locks. */
3443   SVN_ERR(verify_locks(cb->fs, txn_id, changed_paths, pool));
3444
3445   /* We are going to be one better than this puny old revision. */
3446   new_rev = old_rev + 1;
3447
3448   /* Get a write handle on the proto revision file. */
3449   SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
3450                                  cb->fs, txn_id, pool));
3451   SVN_ERR(svn_fs_fs__get_file_offset(&initial_offset, proto_file, pool));
3452
3453   /* Write out all the node-revisions and directory contents. */
3454   root_id = svn_fs_fs__id_txn_create_root(txn_id, pool);
3455   SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
3456                           start_node_id, start_copy_id, initial_offset,
3457                           cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
3458                           TRUE, pool));
3459
3460   /* Write the changed-path information. */
3461   SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
3462                                         cb->fs, txn_id, changed_paths,
3463                                         pool));
3464
3465   if (svn_fs_fs__use_log_addressing(cb->fs))
3466     {
3467       /* Append the index data to the rev file. */
3468       SVN_ERR(svn_fs_fs__add_index_data(cb->fs, proto_file,
3469                       svn_fs_fs__path_l2p_proto_index(cb->fs, txn_id, pool),
3470                       svn_fs_fs__path_p2l_proto_index(cb->fs, txn_id, pool),
3471                       new_rev, pool));
3472     }
3473   else
3474     {
3475       /* Write the final line. */
3476
3477       svn_stringbuf_t *trailer
3478         = svn_fs_fs__unparse_revision_trailer
3479                   ((apr_off_t)svn_fs_fs__id_item(new_root_id),
3480                    changed_path_offset,
3481                    pool);
3482       SVN_ERR(svn_io_file_write_full(proto_file, trailer->data, trailer->len,
3483                                      NULL, pool));
3484     }
3485
3486   SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
3487   SVN_ERR(svn_io_file_close(proto_file, pool));
3488
3489   /* We don't unlock the prototype revision file immediately to avoid a
3490      race with another caller writing to the prototype revision file
3491      before we commit it. */
3492
3493   /* Create the shard for the rev and revprop file, if we're sharding and
3494      this is the first revision of a new shard.  We don't care if this
3495      fails because the shard already existed for some reason. */
3496   if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
3497     {
3498       /* Create the revs shard. */
3499         {
3500           const char *new_dir
3501             = svn_fs_fs__path_rev_shard(cb->fs, new_rev, pool);
3502           svn_error_t *err
3503             = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
3504           if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3505             return svn_error_trace(err);
3506           svn_error_clear(err);
3507           SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3508                                                     PATH_REVS_DIR,
3509                                                     pool),
3510                                     new_dir, pool));
3511         }
3512
3513       /* Create the revprops shard. */
3514       SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev));
3515         {
3516           const char *new_dir
3517             = svn_fs_fs__path_revprops_shard(cb->fs, new_rev, pool);
3518           svn_error_t *err
3519             = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
3520           if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3521             return svn_error_trace(err);
3522           svn_error_clear(err);
3523           SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3524                                                     PATH_REVPROPS_DIR,
3525                                                     pool),
3526                                     new_dir, pool));
3527         }
3528     }
3529
3530   /* Move the finished rev file into place.
3531
3532      ### This "breaks" the transaction by removing the protorev file
3533      ### but the revision is not yet complete.  If this commit does
3534      ### not complete for any reason the transaction will be lost. */
3535   old_rev_filename = svn_fs_fs__path_rev_absolute(cb->fs, old_rev, pool);
3536   rev_filename = svn_fs_fs__path_rev(cb->fs, new_rev, pool);
3537   proto_filename = svn_fs_fs__path_txn_proto_rev(cb->fs, txn_id, pool);
3538   SVN_ERR(svn_fs_fs__move_into_place(proto_filename, rev_filename,
3539                                      old_rev_filename, pool));
3540
3541   /* Now that we've moved the prototype revision file out of the way,
3542      we can unlock it (since further attempts to write to the file
3543      will fail as it no longer exists).  We must do this so that we can
3544      remove the transaction directory later. */
3545   SVN_ERR(unlock_proto_rev(cb->fs, txn_id, proto_file_lockcookie, pool));
3546
3547   /* Move the revprops file into place. */
3548   SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev));
3549   SVN_ERR(write_final_revprop(&revprop_filename, cb->txn, txn_id, pool));
3550   final_revprop = svn_fs_fs__path_revprops(cb->fs, new_rev, pool);
3551   SVN_ERR(svn_fs_fs__move_into_place(revprop_filename, final_revprop,
3552                                      old_rev_filename, pool));
3553
3554   /* Update the 'current' file. */
3555   SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
3556   SVN_ERR(write_final_current(cb->fs, txn_id, new_rev, start_node_id,
3557                               start_copy_id, pool));
3558
3559   /* At this point the new revision is committed and globally visible
3560      so let the caller know it succeeded by giving it the new revision
3561      number, which fulfills svn_fs_commit_txn() contract.  Any errors
3562      after this point do not change the fact that a new revision was
3563      created. */
3564   *cb->new_rev_p = new_rev;
3565
3566   ffd->youngest_rev_cache = new_rev;
3567
3568   /* Remove this transaction directory. */
3569   SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
3570
3571   return SVN_NO_ERROR;
3572 }
3573
3574 /* Add the representations in REPS_TO_CACHE (an array of representation_t *)
3575  * to the rep-cache database of FS. */
3576 static svn_error_t *
3577 write_reps_to_cache(svn_fs_t *fs,
3578                     const apr_array_header_t *reps_to_cache,
3579                     apr_pool_t *scratch_pool)
3580 {
3581   int i;
3582
3583   for (i = 0; i < reps_to_cache->nelts; i++)
3584     {
3585       representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
3586
3587       SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, scratch_pool));
3588     }
3589
3590   return SVN_NO_ERROR;
3591 }
3592
3593 svn_error_t *
3594 svn_fs_fs__commit(svn_revnum_t *new_rev_p,
3595                   svn_fs_t *fs,
3596                   svn_fs_txn_t *txn,
3597                   apr_pool_t *pool)
3598 {
3599   struct commit_baton cb;
3600   fs_fs_data_t *ffd = fs->fsap_data;
3601
3602   cb.new_rev_p = new_rev_p;
3603   cb.fs = fs;
3604   cb.txn = txn;
3605
3606   if (ffd->rep_sharing_allowed)
3607     {
3608       cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
3609       cb.reps_hash = apr_hash_make(pool);
3610       cb.reps_pool = pool;
3611     }
3612   else
3613     {
3614       cb.reps_to_cache = NULL;
3615       cb.reps_hash = NULL;
3616       cb.reps_pool = NULL;
3617     }
3618
3619   SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
3620
3621   /* At this point, *NEW_REV_P has been set, so errors below won't affect
3622      the success of the commit.  (See svn_fs_commit_txn().)  */
3623
3624   if (ffd->rep_sharing_allowed)
3625     {
3626       SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
3627
3628       /* Write new entries to the rep-sharing database.
3629        *
3630        * We use an sqlite transaction to speed things up;
3631        * see <http://www.sqlite.org/faq.html#q19>.
3632        */
3633       /* ### A commit that touches thousands of files will starve other
3634              (reader/writer) commits for the duration of the below call.
3635              Maybe write in batches? */
3636       SVN_SQLITE__WITH_TXN(
3637         write_reps_to_cache(fs, cb.reps_to_cache, pool),
3638         ffd->rep_cache_db);
3639     }
3640
3641   return SVN_NO_ERROR;
3642 }
3643
3644
3645 svn_error_t *
3646 svn_fs_fs__list_transactions(apr_array_header_t **names_p,
3647                              svn_fs_t *fs,
3648                              apr_pool_t *pool)
3649 {
3650   const char *txn_dir;
3651   apr_hash_t *dirents;
3652   apr_hash_index_t *hi;
3653   apr_array_header_t *names;
3654   apr_size_t ext_len = strlen(PATH_EXT_TXN);
3655
3656   names = apr_array_make(pool, 1, sizeof(const char *));
3657
3658   /* Get the transactions directory. */
3659   txn_dir = svn_fs_fs__path_txns_dir(fs, pool);
3660
3661   /* Now find a listing of this directory. */
3662   SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
3663
3664   /* Loop through all the entries and return anything that ends with '.txn'. */
3665   for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
3666     {
3667       const char *name = apr_hash_this_key(hi);
3668       apr_ssize_t klen = apr_hash_this_key_len(hi);
3669       const char *id;
3670
3671       /* The name must end with ".txn" to be considered a transaction. */
3672       if ((apr_size_t) klen <= ext_len
3673           || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
3674         continue;
3675
3676       /* Truncate the ".txn" extension and store the ID. */
3677       id = apr_pstrndup(pool, name, strlen(name) - ext_len);
3678       APR_ARRAY_PUSH(names, const char *) = id;
3679     }
3680
3681   *names_p = names;
3682
3683   return SVN_NO_ERROR;
3684 }
3685
3686 svn_error_t *
3687 svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
3688                     svn_fs_t *fs,
3689                     const char *name,
3690                     apr_pool_t *pool)
3691 {
3692   svn_fs_txn_t *txn;
3693   fs_txn_data_t *ftd;
3694   svn_node_kind_t kind;
3695   transaction_t *local_txn;
3696   svn_fs_fs__id_part_t txn_id;
3697
3698   SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, name));
3699
3700   /* First check to see if the directory exists. */
3701   SVN_ERR(svn_io_check_path(svn_fs_fs__path_txn_dir(fs, &txn_id, pool),
3702                             &kind, pool));
3703
3704   /* Did we find it? */
3705   if (kind != svn_node_dir)
3706     return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
3707                              _("No such transaction '%s'"),
3708                              name);
3709
3710   txn = apr_pcalloc(pool, sizeof(*txn));
3711   ftd = apr_pcalloc(pool, sizeof(*ftd));
3712   ftd->txn_id = txn_id;
3713
3714   /* Read in the root node of this transaction. */
3715   txn->id = apr_pstrdup(pool, name);
3716   txn->fs = fs;
3717
3718   SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, &txn_id, pool));
3719
3720   txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
3721
3722   txn->vtable = &txn_vtable;
3723   txn->fsap_data = ftd;
3724   *txn_p = txn;
3725
3726   return SVN_NO_ERROR;
3727 }
3728
3729 svn_error_t *
3730 svn_fs_fs__txn_proplist(apr_hash_t **table_p,
3731                         svn_fs_txn_t *txn,
3732                         apr_pool_t *pool)
3733 {
3734   apr_hash_t *proplist = apr_hash_make(pool);
3735   SVN_ERR(get_txn_proplist(proplist, txn->fs, svn_fs_fs__txn_get_id(txn),
3736                            pool));
3737   *table_p = proplist;
3738
3739   return SVN_NO_ERROR;
3740 }
3741
3742
3743 svn_error_t *
3744 svn_fs_fs__delete_node_revision(svn_fs_t *fs,
3745                                 const svn_fs_id_t *id,
3746                                 apr_pool_t *pool)
3747 {
3748   node_revision_t *noderev;
3749
3750   SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, pool));
3751
3752   /* Delete any mutable property representation. */
3753   if (noderev->prop_rep && is_txn_rep(noderev->prop_rep))
3754     SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_props(fs, id, pool),
3755                                 FALSE, pool));
3756
3757   /* Delete any mutable data representation. */
3758   if (noderev->data_rep && is_txn_rep(noderev->data_rep)
3759       && noderev->kind == svn_node_dir)
3760     {
3761       fs_fs_data_t *ffd = fs->fsap_data;
3762       SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_children(fs, id,
3763                                                                     pool),
3764                                   FALSE, pool));
3765
3766       /* remove the corresponding entry from the cache, if such exists */
3767       if (ffd->txn_dir_cache)
3768         {
3769           const char *key = svn_fs_fs__id_unparse(id, pool)->data;
3770           SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
3771         }
3772     }
3773
3774   return svn_io_remove_file2(svn_fs_fs__path_txn_node_rev(fs, id, pool),
3775                              FALSE, pool);
3776 }
3777
3778 \f
3779
3780 /*** Transactions ***/
3781
3782 svn_error_t *
3783 svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
3784                        const svn_fs_id_t **base_root_id_p,
3785                        svn_fs_t *fs,
3786                        const svn_fs_fs__id_part_t *txn_id,
3787                        apr_pool_t *pool)
3788 {
3789   transaction_t *txn;
3790   SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_id, pool));
3791   *root_id_p = txn->root_id;
3792   *base_root_id_p = txn->base_id;
3793   return SVN_NO_ERROR;
3794 }
3795
3796
3797 /* Generic transaction operations.  */
3798
3799 svn_error_t *
3800 svn_fs_fs__txn_prop(svn_string_t **value_p,
3801                     svn_fs_txn_t *txn,
3802                     const char *propname,
3803                     apr_pool_t *pool)
3804 {
3805   apr_hash_t *table;
3806   svn_fs_t *fs = txn->fs;
3807
3808   SVN_ERR(svn_fs__check_fs(fs, TRUE));
3809   SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
3810
3811   *value_p = svn_hash_gets(table, propname);
3812
3813   return SVN_NO_ERROR;
3814 }
3815
3816 svn_error_t *
3817 svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
3818                      svn_fs_t *fs,
3819                      svn_revnum_t rev,
3820                      apr_uint32_t flags,
3821                      apr_pool_t *pool)
3822 {
3823   svn_string_t date;
3824   fs_txn_data_t *ftd;
3825   apr_hash_t *props = apr_hash_make(pool);
3826
3827   SVN_ERR(svn_fs__check_fs(fs, TRUE));
3828
3829   SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
3830
3831   /* Put a datestamp on the newly created txn, so we always know
3832      exactly how old it is.  (This will help sysadmins identify
3833      long-abandoned txns that may need to be manually removed.)  When
3834      a txn is promoted to a revision, this property will be
3835      automatically overwritten with a revision datestamp. */
3836   date.data = svn_time_to_cstring(apr_time_now(), pool);
3837   date.len = strlen(date.data);
3838
3839   svn_hash_sets(props, SVN_PROP_REVISION_DATE, &date);
3840
3841   /* Set temporary txn props that represent the requested 'flags'
3842      behaviors. */
3843   if (flags & SVN_FS_TXN_CHECK_OOD)
3844     svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_OOD,
3845                   svn_string_create("true", pool));
3846
3847   if (flags & SVN_FS_TXN_CHECK_LOCKS)
3848     svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_LOCKS,
3849                   svn_string_create("true", pool));
3850
3851   if (flags & SVN_FS_TXN_CLIENT_DATE)
3852     svn_hash_sets(props, SVN_FS__PROP_TXN_CLIENT_DATE,
3853                   svn_string_create("0", pool));
3854
3855   ftd = (*txn_p)->fsap_data;
3856   return svn_error_trace(set_txn_proplist(fs, &ftd->txn_id, props, FALSE,
3857                                           pool));
3858 }