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