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