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 */
58static APR_INLINE const char *
59path_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
73static APR_INLINE const char *
74path_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
82static APR_INLINE const char *
83path_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
91static APR_INLINE const char *
92path_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. */
102static 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 */
114typedef 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
120const svn_fs_fs__id_part_t *
121svn_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). */
134static fs_fs_shared_txn_data_t *
135get_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. */
181static void
182free_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. */
213static svn_error_t *
214with_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. */
233struct 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(). */
240static svn_error_t *
241unlock_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. */
280static svn_error_t *
281unlock_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. */
295struct 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(). */
302static svn_error_t *
303get_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. */
378static svn_error_t *
379auto_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. */
426static svn_error_t *
427get_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(). */
479static svn_error_t *
480purge_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. */
492static svn_error_t *
493purge_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
500
501svn_error_t *
502svn_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 */
538static svn_error_t *
539store_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
574static svn_error_t *
575unparse_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. */
640static svn_error_t *
641unparse_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 */
664static svn_fs_path_change2_t *
665path_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. */
682static svn_error_t *
683fold_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(). */
814typedef 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. */
829static svn_error_t *
830process_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
897svn_error_t *
898svn_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
929svn_error_t *
930svn_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.  */
973static svn_error_t *
974create_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(). */
1003struct 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. */
1012static svn_error_t *
1013get_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. */
1045static svn_error_t *
1046create_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. */
1080static svn_error_t *
1081create_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
1126svn_error_t *
1127svn_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. */
1178static svn_error_t *
1179get_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. */
1213static svn_error_t *
1214set_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
1239svn_error_t *
1240svn_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
1255svn_error_t *
1256svn_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
1294svn_error_t *
1295svn_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. */
1323static svn_error_t *
1324write_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. */
1353static svn_error_t *
1354read_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. */
1387static svn_error_t *
1388get_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
1406svn_error_t *
1407svn_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
1427svn_error_t *
1428svn_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
1452svn_error_t *
1453svn_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
1483svn_error_t *
1484svn_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 */
1500static svn_error_t *
1501set_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 */
1520static svn_boolean_t
1521is_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 */
1528static void
1529reset_txn_in_rep(representation_t *rep)
1530{
1531  svn_fs_fs__id_txn_reset(&rep->txn_id);
1532}
1533
1534svn_error_t *
1535svn_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
1692svn_error_t *
1693svn_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 */
1740static svn_error_t *
1741store_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 */
1766static svn_error_t *
1767store_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 */
1791static svn_error_t *
1792allocate_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 */
1844typedef 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 */
1853static svn_error_t *
1854fnv1a_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 */
1870static svn_stream_t *
1871fnv1a_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 */
1891static svn_error_t *
1892fnv1a_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. */
1908struct 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. */
1954static svn_error_t *
1955rep_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 */
1975static svn_error_t *
1976shards_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. */
2013static svn_error_t *
2014choose_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. */
2133static apr_status_t
2134rep_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
2162static void
2163txdelta_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. */
2195static svn_error_t *
2196rep_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 */
2286static svn_error_t *
2287get_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 */
2490static svn_error_t *
2491digests_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. */
2513static svn_error_t *
2514rep_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. */
2608static svn_error_t *
2609set_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
2630svn_error_t *
2631svn_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
2643svn_error_t *
2644svn_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
2676svn_error_t *
2677svn_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. */
2710struct 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. */
2725static svn_error_t *
2726write_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. */
2743typedef 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. */
2748static svn_error_t *
2749write_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. */
2761static svn_error_t *
2762write_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. */
2785static svn_error_t *
2786write_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 */
2891static svn_error_t *
2892write_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 */
3026static svn_error_t *
3027validate_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 */
3083static void
3084get_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. */
3132static svn_error_t *
3133write_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. */
3402static svn_error_t *
3403write_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. */
3456static svn_error_t *
3457verify_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. */
3497static svn_error_t *
3498write_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. */
3526static svn_error_t *
3527verify_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. */
3598static svn_error_t *
3599write_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
3651svn_error_t *
3652svn_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. */
3694static svn_error_t *
3695promote_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. */
3728struct 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 *'. */
3740static svn_error_t *
3741commit_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. */
3940static svn_error_t *
3941write_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
3957svn_error_t *
3958svn_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
4023svn_error_t *
4024svn_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
4064svn_error_t *
4065svn_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
4107svn_error_t *
4108svn_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
4121svn_error_t *
4122svn_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
4157
4158/*** Transactions ***/
4159
4160svn_error_t *
4161svn_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
4177svn_error_t *
4178svn_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
4194svn_error_t *
4195svn_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}
4236