1289177Speter/* transaction.c --- transaction-related functions of FSFS
2289177Speter *
3289177Speter * ====================================================================
4289177Speter *    Licensed to the Apache Software Foundation (ASF) under one
5289177Speter *    or more contributor license agreements.  See the NOTICE file
6289177Speter *    distributed with this work for additional information
7289177Speter *    regarding copyright ownership.  The ASF licenses this file
8289177Speter *    to you under the Apache License, Version 2.0 (the
9289177Speter *    "License"); you may not use this file except in compliance
10289177Speter *    with the License.  You may obtain a copy of the License at
11289177Speter *
12289177Speter *      http://www.apache.org/licenses/LICENSE-2.0
13289177Speter *
14289177Speter *    Unless required by applicable law or agreed to in writing,
15289177Speter *    software distributed under the License is distributed on an
16289177Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17289177Speter *    KIND, either express or implied.  See the License for the
18289177Speter *    specific language governing permissions and limitations
19289177Speter *    under the License.
20289177Speter * ====================================================================
21289177Speter */
22289177Speter
23289177Speter#include "transaction.h"
24289177Speter
25289177Speter#include <assert.h>
26289177Speter#include <apr_sha1.h>
27289177Speter
28289177Speter#include "svn_hash.h"
29289177Speter#include "svn_props.h"
30289177Speter#include "svn_sorts.h"
31289177Speter#include "svn_time.h"
32289177Speter#include "svn_dirent_uri.h"
33289177Speter
34289177Speter#include "fs_fs.h"
35289177Speter#include "index.h"
36289177Speter#include "tree.h"
37289177Speter#include "util.h"
38289177Speter#include "id.h"
39289177Speter#include "low_level.h"
40289177Speter#include "temp_serializer.h"
41289177Speter#include "cached_data.h"
42289177Speter#include "lock.h"
43289177Speter#include "rep-cache.h"
44289177Speter
45289177Speter#include "private/svn_fs_util.h"
46289177Speter#include "private/svn_fspath.h"
47289177Speter#include "private/svn_sorts_private.h"
48289177Speter#include "private/svn_subr_private.h"
49289177Speter#include "private/svn_string_private.h"
50289177Speter#include "../libsvn_fs/fs-loader.h"
51289177Speter
52289177Speter#include "svn_private_config.h"
53289177Speter
54289177Speter/* Return the name of the sha1->rep mapping file in transaction TXN_ID
55289177Speter * within FS for the given SHA1 checksum.  Use POOL for allocations.
56289177Speter */
57289177Speterstatic APR_INLINE const char *
58289177Speterpath_txn_sha1(svn_fs_t *fs,
59289177Speter              const svn_fs_fs__id_part_t *txn_id,
60289177Speter              const unsigned char *sha1,
61289177Speter              apr_pool_t *pool)
62289177Speter{
63289177Speter  svn_checksum_t checksum;
64289177Speter  checksum.digest = sha1;
65289177Speter  checksum.kind = svn_checksum_sha1;
66289177Speter
67289177Speter  return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
68289177Speter                         svn_checksum_to_cstring(&checksum, pool),
69289177Speter                         pool);
70289177Speter}
71289177Speter
72289177Speterstatic APR_INLINE const char *
73289177Speterpath_txn_changes(svn_fs_t *fs,
74289177Speter                 const svn_fs_fs__id_part_t *txn_id,
75289177Speter                 apr_pool_t *pool)
76289177Speter{
77289177Speter  return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
78289177Speter                         PATH_CHANGES, pool);
79289177Speter}
80289177Speter
81289177Speterstatic APR_INLINE const char *
82289177Speterpath_txn_props(svn_fs_t *fs,
83289177Speter               const svn_fs_fs__id_part_t *txn_id,
84289177Speter               apr_pool_t *pool)
85289177Speter{
86289177Speter  return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
87289177Speter                         PATH_TXN_PROPS, pool);
88289177Speter}
89289177Speter
90289177Speterstatic APR_INLINE const char *
91289177Speterpath_txn_props_final(svn_fs_t *fs,
92289177Speter                     const svn_fs_fs__id_part_t *txn_id,
93289177Speter                     apr_pool_t *pool)
94289177Speter{
95289177Speter  return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
96289177Speter                         PATH_TXN_PROPS_FINAL, pool);
97289177Speter}
98289177Speter
99289177Speterstatic APR_INLINE const char *
100289177Speterpath_txn_next_ids(svn_fs_t *fs,
101289177Speter                  const svn_fs_fs__id_part_t *txn_id,
102289177Speter                  apr_pool_t *pool)
103289177Speter{
104289177Speter  return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
105289177Speter                         PATH_NEXT_IDS, pool);
106289177Speter}
107289177Speter
108289177Speter
109289177Speter/* The vtable associated with an open transaction object. */
110289177Speterstatic txn_vtable_t txn_vtable = {
111289177Speter  svn_fs_fs__commit_txn,
112289177Speter  svn_fs_fs__abort_txn,
113289177Speter  svn_fs_fs__txn_prop,
114289177Speter  svn_fs_fs__txn_proplist,
115289177Speter  svn_fs_fs__change_txn_prop,
116289177Speter  svn_fs_fs__txn_root,
117289177Speter  svn_fs_fs__change_txn_props
118289177Speter};
119289177Speter
120289177Speter/* FSFS-specific data being attached to svn_fs_txn_t.
121289177Speter */
122289177Spetertypedef struct fs_txn_data_t
123289177Speter{
124289177Speter  /* Strongly typed representation of the TXN's ID member. */
125289177Speter  svn_fs_fs__id_part_t txn_id;
126289177Speter} fs_txn_data_t;
127289177Speter
128289177Speterconst svn_fs_fs__id_part_t *
129289177Spetersvn_fs_fs__txn_get_id(svn_fs_txn_t *txn)
130289177Speter{
131289177Speter  fs_txn_data_t *ftd = txn->fsap_data;
132289177Speter  return &ftd->txn_id;
133289177Speter}
134289177Speter
135289177Speter/* Functions for working with shared transaction data. */
136289177Speter
137289177Speter/* Return the transaction object for transaction TXN_ID from the
138289177Speter   transaction list of filesystem FS (which must already be locked via the
139289177Speter   txn_list_lock mutex).  If the transaction does not exist in the list,
140289177Speter   then create a new transaction object and return it (if CREATE_NEW is
141289177Speter   true) or return NULL (otherwise). */
142289177Speterstatic fs_fs_shared_txn_data_t *
143289177Speterget_shared_txn(svn_fs_t *fs,
144289177Speter               const svn_fs_fs__id_part_t *txn_id,
145289177Speter               svn_boolean_t create_new)
146289177Speter{
147289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
148289177Speter  fs_fs_shared_data_t *ffsd = ffd->shared;
149289177Speter  fs_fs_shared_txn_data_t *txn;
150289177Speter
151289177Speter  for (txn = ffsd->txns; txn; txn = txn->next)
152289177Speter    if (svn_fs_fs__id_part_eq(&txn->txn_id, txn_id))
153289177Speter      break;
154289177Speter
155289177Speter  if (txn || !create_new)
156289177Speter    return txn;
157289177Speter
158289177Speter  /* Use the transaction object from the (single-object) freelist,
159289177Speter     if one is available, or otherwise create a new object. */
160289177Speter  if (ffsd->free_txn)
161289177Speter    {
162289177Speter      txn = ffsd->free_txn;
163289177Speter      ffsd->free_txn = NULL;
164289177Speter    }
165289177Speter  else
166289177Speter    {
167289177Speter      apr_pool_t *subpool = svn_pool_create(ffsd->common_pool);
168289177Speter      txn = apr_palloc(subpool, sizeof(*txn));
169289177Speter      txn->pool = subpool;
170289177Speter    }
171289177Speter
172289177Speter  txn->txn_id = *txn_id;
173289177Speter  txn->being_written = FALSE;
174289177Speter
175289177Speter  /* Link this transaction into the head of the list.  We will typically
176289177Speter     be dealing with only one active transaction at a time, so it makes
177289177Speter     sense for searches through the transaction list to look at the
178289177Speter     newest transactions first.  */
179289177Speter  txn->next = ffsd->txns;
180289177Speter  ffsd->txns = txn;
181289177Speter
182289177Speter  return txn;
183289177Speter}
184289177Speter
185289177Speter/* Free the transaction object for transaction TXN_ID, and remove it
186289177Speter   from the transaction list of filesystem FS (which must already be
187289177Speter   locked via the txn_list_lock mutex).  Do nothing if the transaction
188289177Speter   does not exist. */
189289177Speterstatic void
190289177Speterfree_shared_txn(svn_fs_t *fs, const svn_fs_fs__id_part_t *txn_id)
191289177Speter{
192289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
193289177Speter  fs_fs_shared_data_t *ffsd = ffd->shared;
194289177Speter  fs_fs_shared_txn_data_t *txn, *prev = NULL;
195289177Speter
196289177Speter  for (txn = ffsd->txns; txn; prev = txn, txn = txn->next)
197289177Speter    if (svn_fs_fs__id_part_eq(&txn->txn_id, txn_id))
198289177Speter      break;
199289177Speter
200289177Speter  if (!txn)
201289177Speter    return;
202289177Speter
203289177Speter  if (prev)
204289177Speter    prev->next = txn->next;
205289177Speter  else
206289177Speter    ffsd->txns = txn->next;
207289177Speter
208289177Speter  /* As we typically will be dealing with one transaction after another,
209289177Speter     we will maintain a single-object free list so that we can hopefully
210289177Speter     keep reusing the same transaction object. */
211289177Speter  if (!ffsd->free_txn)
212289177Speter    ffsd->free_txn = txn;
213289177Speter  else
214289177Speter    svn_pool_destroy(txn->pool);
215289177Speter}
216289177Speter
217289177Speter
218289177Speter/* Obtain a lock on the transaction list of filesystem FS, call BODY
219289177Speter   with FS, BATON, and POOL, and then unlock the transaction list.
220289177Speter   Return what BODY returned. */
221289177Speterstatic svn_error_t *
222289177Speterwith_txnlist_lock(svn_fs_t *fs,
223289177Speter                  svn_error_t *(*body)(svn_fs_t *fs,
224289177Speter                                       const void *baton,
225289177Speter                                       apr_pool_t *pool),
226289177Speter                  const void *baton,
227289177Speter                  apr_pool_t *pool)
228289177Speter{
229289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
230289177Speter  fs_fs_shared_data_t *ffsd = ffd->shared;
231289177Speter
232289177Speter  SVN_MUTEX__WITH_LOCK(ffsd->txn_list_lock,
233289177Speter                       body(fs, baton, pool));
234289177Speter
235289177Speter  return SVN_NO_ERROR;
236289177Speter}
237289177Speter
238289177Speter
239289177Speter/* A structure used by unlock_proto_rev() and unlock_proto_rev_body(),
240289177Speter   which see. */
241289177Speterstruct unlock_proto_rev_baton
242289177Speter{
243289177Speter  svn_fs_fs__id_part_t txn_id;
244289177Speter  void *lockcookie;
245289177Speter};
246289177Speter
247289177Speter/* Callback used in the implementation of unlock_proto_rev(). */
248289177Speterstatic svn_error_t *
249289177Speterunlock_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
250289177Speter{
251289177Speter  const struct unlock_proto_rev_baton *b = baton;
252289177Speter  apr_file_t *lockfile = b->lockcookie;
253289177Speter  fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, &b->txn_id, FALSE);
254289177Speter  apr_status_t apr_err;
255289177Speter
256289177Speter  if (!txn)
257289177Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
258289177Speter                             _("Can't unlock unknown transaction '%s'"),
259289177Speter                             svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
260289177Speter  if (!txn->being_written)
261289177Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
262289177Speter                             _("Can't unlock nonlocked transaction '%s'"),
263289177Speter                             svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
264289177Speter
265289177Speter  apr_err = apr_file_unlock(lockfile);
266289177Speter  if (apr_err)
267289177Speter    return svn_error_wrap_apr
268289177Speter      (apr_err,
269289177Speter       _("Can't unlock prototype revision lockfile for transaction '%s'"),
270289177Speter       svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
271289177Speter  apr_err = apr_file_close(lockfile);
272289177Speter  if (apr_err)
273289177Speter    return svn_error_wrap_apr
274289177Speter      (apr_err,
275289177Speter       _("Can't close prototype revision lockfile for transaction '%s'"),
276289177Speter       svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
277289177Speter
278289177Speter  txn->being_written = FALSE;
279289177Speter
280289177Speter  return SVN_NO_ERROR;
281289177Speter}
282289177Speter
283289177Speter/* Unlock the prototype revision file for transaction TXN_ID in filesystem
284289177Speter   FS using cookie LOCKCOOKIE.  The original prototype revision file must
285289177Speter   have been closed _before_ calling this function.
286289177Speter
287289177Speter   Perform temporary allocations in POOL. */
288289177Speterstatic svn_error_t *
289289177Speterunlock_proto_rev(svn_fs_t *fs,
290289177Speter                 const svn_fs_fs__id_part_t *txn_id,
291289177Speter                 void *lockcookie,
292289177Speter                 apr_pool_t *pool)
293289177Speter{
294289177Speter  struct unlock_proto_rev_baton b;
295289177Speter
296289177Speter  b.txn_id = *txn_id;
297289177Speter  b.lockcookie = lockcookie;
298289177Speter  return with_txnlist_lock(fs, unlock_proto_rev_body, &b, pool);
299289177Speter}
300289177Speter
301289177Speter/* A structure used by get_writable_proto_rev() and
302289177Speter   get_writable_proto_rev_body(), which see. */
303289177Speterstruct get_writable_proto_rev_baton
304289177Speter{
305289177Speter  void **lockcookie;
306289177Speter  svn_fs_fs__id_part_t txn_id;
307289177Speter};
308289177Speter
309289177Speter/* Callback used in the implementation of get_writable_proto_rev(). */
310289177Speterstatic svn_error_t *
311289177Speterget_writable_proto_rev_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
312289177Speter{
313289177Speter  const struct get_writable_proto_rev_baton *b = baton;
314289177Speter  void **lockcookie = b->lockcookie;
315289177Speter  fs_fs_shared_txn_data_t *txn = get_shared_txn(fs, &b->txn_id, TRUE);
316289177Speter
317289177Speter  /* First, ensure that no thread in this process (including this one)
318289177Speter     is currently writing to this transaction's proto-rev file. */
319289177Speter  if (txn->being_written)
320289177Speter    return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
321289177Speter                             _("Cannot write to the prototype revision file "
322289177Speter                               "of transaction '%s' because a previous "
323289177Speter                               "representation is currently being written by "
324289177Speter                               "this process"),
325289177Speter                             svn_fs_fs__id_txn_unparse(&b->txn_id, pool));
326289177Speter
327289177Speter
328289177Speter  /* We know that no thread in this process is writing to the proto-rev
329289177Speter     file, and by extension, that no thread in this process is holding a
330289177Speter     lock on the prototype revision lock file.  It is therefore safe
331289177Speter     for us to attempt to lock this file, to see if any other process
332289177Speter     is holding a lock. */
333289177Speter
334289177Speter  {
335289177Speter    apr_file_t *lockfile;
336289177Speter    apr_status_t apr_err;
337289177Speter    const char *lockfile_path
338289177Speter      = svn_fs_fs__path_txn_proto_rev_lock(fs, &b->txn_id, pool);
339289177Speter
340289177Speter    /* Open the proto-rev lockfile, creating it if necessary, as it may
341289177Speter       not exist if the transaction dates from before the lockfiles were
342289177Speter       introduced.
343289177Speter
344289177Speter       ### We'd also like to use something like svn_io_file_lock2(), but
345289177Speter           that forces us to create a subpool just to be able to unlock
346289177Speter           the file, which seems a waste. */
347289177Speter    SVN_ERR(svn_io_file_open(&lockfile, lockfile_path,
348289177Speter                             APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
349289177Speter
350289177Speter    apr_err = apr_file_lock(lockfile,
351289177Speter                            APR_FLOCK_EXCLUSIVE | APR_FLOCK_NONBLOCK);
352289177Speter    if (apr_err)
353289177Speter      {
354289177Speter        svn_error_clear(svn_io_file_close(lockfile, pool));
355289177Speter
356289177Speter        if (APR_STATUS_IS_EAGAIN(apr_err))
357289177Speter          return svn_error_createf(SVN_ERR_FS_REP_BEING_WRITTEN, NULL,
358289177Speter                                   _("Cannot write to the prototype revision "
359289177Speter                                     "file of transaction '%s' because a "
360289177Speter                                     "previous representation is currently "
361289177Speter                                     "being written by another process"),
362289177Speter                                   svn_fs_fs__id_txn_unparse(&b->txn_id,
363289177Speter                                                             pool));
364289177Speter
365289177Speter        return svn_error_wrap_apr(apr_err,
366289177Speter                                  _("Can't get exclusive lock on file '%s'"),
367289177Speter                                  svn_dirent_local_style(lockfile_path, pool));
368289177Speter      }
369289177Speter
370289177Speter    *lockcookie = lockfile;
371289177Speter  }
372289177Speter
373289177Speter  /* We've successfully locked the transaction; mark it as such. */
374289177Speter  txn->being_written = TRUE;
375289177Speter
376289177Speter  return SVN_NO_ERROR;
377289177Speter}
378289177Speter
379289177Speter/* Make sure the length ACTUAL_LENGTH of the proto-revision file PROTO_REV
380289177Speter   of transaction TXN_ID in filesystem FS matches the proto-index file.
381289177Speter   Trim any crash / failure related extra data from the proto-rev file.
382289177Speter
383289177Speter   If the prototype revision file is too short, we can't do much but bail out.
384289177Speter
385289177Speter   Perform all allocations in POOL. */
386289177Speterstatic svn_error_t *
387289177Speterauto_truncate_proto_rev(svn_fs_t *fs,
388289177Speter                        apr_file_t *proto_rev,
389289177Speter                        apr_off_t actual_length,
390289177Speter                        const svn_fs_fs__id_part_t *txn_id,
391289177Speter                        apr_pool_t *pool)
392289177Speter{
393289177Speter  /* Only relevant for newer FSFS formats. */
394289177Speter  if (svn_fs_fs__use_log_addressing(fs))
395289177Speter    {
396289177Speter      /* Determine file range covered by the proto-index so far.  Note that
397289177Speter         we always append to both file, i.e. the last index entry also
398289177Speter         corresponds to the last addition in the rev file. */
399289177Speter      const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool);
400289177Speter      apr_file_t *file;
401289177Speter      apr_off_t indexed_length;
402289177Speter
403289177Speter      SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool));
404289177Speter      SVN_ERR(svn_fs_fs__p2l_proto_index_next_offset(&indexed_length, file,
405289177Speter                                                     pool));
406289177Speter      SVN_ERR(svn_io_file_close(file, pool));
407289177Speter
408289177Speter      /* Handle mismatches. */
409289177Speter      if (indexed_length < actual_length)
410289177Speter        SVN_ERR(svn_io_file_trunc(proto_rev, indexed_length, pool));
411289177Speter      else if (indexed_length > actual_length)
412289177Speter        return svn_error_createf(SVN_ERR_FS_INDEX_INCONSISTENT,
413289177Speter                                 NULL,
414289177Speter                                 _("p2l proto index offset %s beyond proto"
415289177Speter                                   "rev file size %s for TXN %s"),
416289177Speter                                   apr_off_t_toa(pool, indexed_length),
417289177Speter                                   apr_off_t_toa(pool, actual_length),
418289177Speter                                   svn_fs_fs__id_txn_unparse(txn_id, pool));
419289177Speter    }
420289177Speter
421289177Speter  return SVN_NO_ERROR;
422289177Speter}
423289177Speter
424289177Speter/* Get a handle to the prototype revision file for transaction TXN_ID in
425289177Speter   filesystem FS, and lock it for writing.  Return FILE, a file handle
426289177Speter   positioned at the end of the file, and LOCKCOOKIE, a cookie that
427289177Speter   should be passed to unlock_proto_rev() to unlock the file once FILE
428289177Speter   has been closed.
429289177Speter
430289177Speter   If the prototype revision file is already locked, return error
431289177Speter   SVN_ERR_FS_REP_BEING_WRITTEN.
432289177Speter
433289177Speter   Perform all allocations in POOL. */
434289177Speterstatic svn_error_t *
435289177Speterget_writable_proto_rev(apr_file_t **file,
436289177Speter                       void **lockcookie,
437289177Speter                       svn_fs_t *fs,
438289177Speter                       const svn_fs_fs__id_part_t *txn_id,
439289177Speter                       apr_pool_t *pool)
440289177Speter{
441289177Speter  struct get_writable_proto_rev_baton b;
442289177Speter  svn_error_t *err;
443289177Speter  apr_off_t end_offset = 0;
444289177Speter
445289177Speter  b.lockcookie = lockcookie;
446289177Speter  b.txn_id = *txn_id;
447289177Speter
448289177Speter  SVN_ERR(with_txnlist_lock(fs, get_writable_proto_rev_body, &b, pool));
449289177Speter
450289177Speter  /* Now open the prototype revision file and seek to the end. */
451289177Speter  err = svn_io_file_open(file,
452289177Speter                         svn_fs_fs__path_txn_proto_rev(fs, txn_id, pool),
453289177Speter                         APR_READ | APR_WRITE | APR_BUFFERED, APR_OS_DEFAULT,
454289177Speter                         pool);
455289177Speter
456289177Speter  /* You might expect that we could dispense with the following seek
457289177Speter     and achieve the same thing by opening the file using APR_APPEND.
458289177Speter     Unfortunately, APR's buffered file implementation unconditionally
459289177Speter     places its initial file pointer at the start of the file (even for
460289177Speter     files opened with APR_APPEND), so we need this seek to reconcile
461289177Speter     the APR file pointer to the OS file pointer (since we need to be
462289177Speter     able to read the current file position later). */
463289177Speter  if (!err)
464289177Speter    err = svn_io_file_seek(*file, APR_END, &end_offset, pool);
465289177Speter
466289177Speter  /* We don't want unused sections (such as leftovers from failed delta
467289177Speter     stream) in our file.  If we use log addressing, we would need an
468289177Speter     index entry for the unused section and that section would need to
469289177Speter     be all NUL by convention.  So, detect and fix those cases by truncating
470289177Speter     the protorev file. */
471289177Speter  if (!err)
472289177Speter    err = auto_truncate_proto_rev(fs, *file, end_offset, txn_id, pool);
473289177Speter
474289177Speter  if (err)
475289177Speter    {
476289177Speter      err = svn_error_compose_create(
477289177Speter              err,
478289177Speter              unlock_proto_rev(fs, txn_id, *lockcookie, pool));
479289177Speter
480289177Speter      *lockcookie = NULL;
481289177Speter    }
482289177Speter
483289177Speter  return svn_error_trace(err);
484289177Speter}
485289177Speter
486289177Speter/* Callback used in the implementation of purge_shared_txn(). */
487289177Speterstatic svn_error_t *
488289177Speterpurge_shared_txn_body(svn_fs_t *fs, const void *baton, apr_pool_t *pool)
489289177Speter{
490289177Speter  const svn_fs_fs__id_part_t *txn_id = baton;
491289177Speter
492289177Speter  free_shared_txn(fs, txn_id);
493289177Speter  svn_fs_fs__reset_txn_caches(fs);
494289177Speter
495289177Speter  return SVN_NO_ERROR;
496289177Speter}
497289177Speter
498289177Speter/* Purge the shared data for transaction TXN_ID in filesystem FS.
499289177Speter   Perform all allocations in POOL. */
500289177Speterstatic svn_error_t *
501289177Speterpurge_shared_txn(svn_fs_t *fs,
502289177Speter                 const svn_fs_fs__id_part_t *txn_id,
503289177Speter                 apr_pool_t *pool)
504289177Speter{
505289177Speter  return with_txnlist_lock(fs, purge_shared_txn_body, txn_id, pool);
506289177Speter}
507289177Speter
508289177Speter
509289177Spetersvn_error_t *
510289177Spetersvn_fs_fs__put_node_revision(svn_fs_t *fs,
511289177Speter                             const svn_fs_id_t *id,
512289177Speter                             node_revision_t *noderev,
513289177Speter                             svn_boolean_t fresh_txn_root,
514289177Speter                             apr_pool_t *pool)
515289177Speter{
516289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
517289177Speter  apr_file_t *noderev_file;
518289177Speter
519289177Speter  noderev->is_fresh_txn_root = fresh_txn_root;
520289177Speter
521289177Speter  if (! svn_fs_fs__id_is_txn(id))
522289177Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
523289177Speter                             _("Attempted to write to non-transaction '%s'"),
524289177Speter                             svn_fs_fs__id_unparse(id, pool)->data);
525289177Speter
526289177Speter  SVN_ERR(svn_io_file_open(&noderev_file,
527289177Speter                           svn_fs_fs__path_txn_node_rev(fs, id, pool),
528289177Speter                           APR_WRITE | APR_CREATE | APR_TRUNCATE
529289177Speter                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
530289177Speter
531289177Speter  SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile2(noderev_file, TRUE,
532289177Speter                                                            pool),
533289177Speter                                   noderev, ffd->format,
534289177Speter                                   svn_fs_fs__fs_supports_mergeinfo(fs),
535289177Speter                                   pool));
536289177Speter
537289177Speter  SVN_ERR(svn_io_file_close(noderev_file, pool));
538289177Speter
539289177Speter  return SVN_NO_ERROR;
540289177Speter}
541289177Speter
542289177Speter/* For the in-transaction NODEREV within FS, write the sha1->rep mapping
543289177Speter * file in the respective transaction, if rep sharing has been enabled etc.
544289177Speter * Use SCATCH_POOL for temporary allocations.
545289177Speter */
546289177Speterstatic svn_error_t *
547289177Speterstore_sha1_rep_mapping(svn_fs_t *fs,
548289177Speter                       node_revision_t *noderev,
549289177Speter                       apr_pool_t *scratch_pool)
550289177Speter{
551289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
552289177Speter
553289177Speter  /* if rep sharing has been enabled and the noderev has a data rep and
554289177Speter   * its SHA-1 is known, store the rep struct under its SHA1. */
555289177Speter  if (   ffd->rep_sharing_allowed
556289177Speter      && noderev->data_rep
557289177Speter      && noderev->data_rep->has_sha1)
558289177Speter    {
559289177Speter      apr_file_t *rep_file;
560289177Speter      const char *file_name = path_txn_sha1(fs,
561289177Speter                                            &noderev->data_rep->txn_id,
562289177Speter                                            noderev->data_rep->sha1_digest,
563289177Speter                                            scratch_pool);
564289177Speter      svn_stringbuf_t *rep_string
565289177Speter        = svn_fs_fs__unparse_representation(noderev->data_rep,
566289177Speter                                            ffd->format,
567289177Speter                                            (noderev->kind == svn_node_dir),
568289177Speter                                            scratch_pool, scratch_pool);
569289177Speter      SVN_ERR(svn_io_file_open(&rep_file, file_name,
570289177Speter                               APR_WRITE | APR_CREATE | APR_TRUNCATE
571289177Speter                               | APR_BUFFERED, APR_OS_DEFAULT, scratch_pool));
572289177Speter
573289177Speter      SVN_ERR(svn_io_file_write_full(rep_file, rep_string->data,
574289177Speter                                     rep_string->len, NULL, scratch_pool));
575289177Speter
576289177Speter      SVN_ERR(svn_io_file_close(rep_file, scratch_pool));
577289177Speter    }
578289177Speter
579289177Speter  return SVN_NO_ERROR;
580289177Speter}
581289177Speter
582289177Speterstatic svn_error_t *
583289177Speterunparse_dir_entry(svn_fs_dirent_t *dirent,
584289177Speter                  svn_stream_t *stream,
585289177Speter                  apr_pool_t *pool)
586289177Speter{
587289177Speter  const char *val
588289177Speter    = apr_psprintf(pool, "%s %s",
589289177Speter                   (dirent->kind == svn_node_file) ? SVN_FS_FS__KIND_FILE
590289177Speter                                                   : SVN_FS_FS__KIND_DIR,
591289177Speter                   svn_fs_fs__id_unparse(dirent->id, pool)->data);
592289177Speter
593289177Speter  SVN_ERR(svn_stream_printf(stream, pool, "K %" APR_SIZE_T_FMT "\n%s\n"
594289177Speter                            "V %" APR_SIZE_T_FMT "\n%s\n",
595289177Speter                            strlen(dirent->name), dirent->name,
596289177Speter                            strlen(val), val));
597289177Speter  return SVN_NO_ERROR;
598289177Speter}
599289177Speter
600289177Speter/* Write the directory given as array of dirent structs in ENTRIES to STREAM.
601289177Speter   Perform temporary allocations in POOL. */
602289177Speterstatic svn_error_t *
603289177Speterunparse_dir_entries(apr_array_header_t *entries,
604289177Speter                    svn_stream_t *stream,
605289177Speter                    apr_pool_t *pool)
606289177Speter{
607289177Speter  apr_pool_t *iterpool = svn_pool_create(pool);
608289177Speter  int i;
609289177Speter  for (i = 0; i < entries->nelts; ++i)
610289177Speter    {
611289177Speter      svn_fs_dirent_t *dirent;
612289177Speter
613289177Speter      svn_pool_clear(iterpool);
614289177Speter      dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
615289177Speter      SVN_ERR(unparse_dir_entry(dirent, stream, iterpool));
616289177Speter    }
617289177Speter
618289177Speter  SVN_ERR(svn_stream_printf(stream, pool, "%s\n", SVN_HASH_TERMINATOR));
619289177Speter
620289177Speter  svn_pool_destroy(iterpool);
621289177Speter  return SVN_NO_ERROR;
622289177Speter}
623289177Speter
624289177Speter/* Return a deep copy of SOURCE and allocate it in RESULT_POOL.
625289177Speter */
626289177Speterstatic svn_fs_path_change2_t *
627289177Speterpath_change_dup(const svn_fs_path_change2_t *source,
628289177Speter                apr_pool_t *result_pool)
629289177Speter{
630289177Speter  svn_fs_path_change2_t *result = apr_pmemdup(result_pool, source,
631289177Speter                                              sizeof(*source));
632289177Speter  result->node_rev_id = svn_fs_fs__id_copy(source->node_rev_id, result_pool);
633289177Speter  if (source->copyfrom_path)
634289177Speter    result->copyfrom_path = apr_pstrdup(result_pool, source->copyfrom_path);
635289177Speter
636289177Speter  return result;
637289177Speter}
638289177Speter
639289177Speter/* Merge the internal-use-only CHANGE into a hash of public-FS
640289177Speter   svn_fs_path_change2_t CHANGED_PATHS, collapsing multiple changes into a
641289177Speter   single summarical (is that real word?) change per path.  DELETIONS is
642289177Speter   also a path->svn_fs_path_change2_t hash and contains all the deletions
643289177Speter   that got turned into a replacement. */
644289177Speterstatic svn_error_t *
645289177Speterfold_change(apr_hash_t *changed_paths,
646289177Speter            apr_hash_t *deletions,
647289177Speter            const change_t *change)
648289177Speter{
649289177Speter  apr_pool_t *pool = apr_hash_pool_get(changed_paths);
650289177Speter  svn_fs_path_change2_t *old_change, *new_change;
651289177Speter  const svn_string_t *path = &change->path;
652289177Speter  const svn_fs_path_change2_t *info = &change->info;
653289177Speter
654289177Speter  if ((old_change = apr_hash_get(changed_paths, path->data, path->len)))
655289177Speter    {
656289177Speter      /* This path already exists in the hash, so we have to merge
657289177Speter         this change into the already existing one. */
658289177Speter
659289177Speter      /* Sanity check:  only allow NULL node revision ID in the
660289177Speter         `reset' case. */
661289177Speter      if ((! info->node_rev_id)
662289177Speter           && (info->change_kind != svn_fs_path_change_reset))
663289177Speter        return svn_error_create
664289177Speter          (SVN_ERR_FS_CORRUPT, NULL,
665289177Speter           _("Missing required node revision ID"));
666289177Speter
667289177Speter      /* Sanity check: we should be talking about the same node
668289177Speter         revision ID as our last change except where the last change
669289177Speter         was a deletion. */
670289177Speter      if (info->node_rev_id
671289177Speter          && (! svn_fs_fs__id_eq(old_change->node_rev_id, info->node_rev_id))
672289177Speter          && (old_change->change_kind != svn_fs_path_change_delete))
673289177Speter        return svn_error_create
674289177Speter          (SVN_ERR_FS_CORRUPT, NULL,
675289177Speter           _("Invalid change ordering: new node revision ID "
676289177Speter             "without delete"));
677289177Speter
678289177Speter      /* Sanity check: an add, replacement, or reset must be the first
679289177Speter         thing to follow a deletion. */
680289177Speter      if ((old_change->change_kind == svn_fs_path_change_delete)
681289177Speter          && (! ((info->change_kind == svn_fs_path_change_replace)
682289177Speter                 || (info->change_kind == svn_fs_path_change_reset)
683289177Speter                 || (info->change_kind == svn_fs_path_change_add))))
684289177Speter        return svn_error_create
685289177Speter          (SVN_ERR_FS_CORRUPT, NULL,
686289177Speter           _("Invalid change ordering: non-add change on deleted path"));
687289177Speter
688289177Speter      /* Sanity check: an add can't follow anything except
689289177Speter         a delete or reset.  */
690289177Speter      if ((info->change_kind == svn_fs_path_change_add)
691289177Speter          && (old_change->change_kind != svn_fs_path_change_delete)
692289177Speter          && (old_change->change_kind != svn_fs_path_change_reset))
693289177Speter        return svn_error_create
694289177Speter          (SVN_ERR_FS_CORRUPT, NULL,
695289177Speter           _("Invalid change ordering: add change on preexisting path"));
696289177Speter
697289177Speter      /* Now, merge that change in. */
698289177Speter      switch (info->change_kind)
699289177Speter        {
700289177Speter        case svn_fs_path_change_reset:
701289177Speter          /* A reset here will simply remove the path change from the
702289177Speter             hash. */
703289177Speter          apr_hash_set(changed_paths, path->data, path->len, NULL);
704289177Speter          break;
705289177Speter
706289177Speter        case svn_fs_path_change_delete:
707289177Speter          if (old_change->change_kind == svn_fs_path_change_add)
708289177Speter            {
709289177Speter              /* If the path was introduced in this transaction via an
710289177Speter                 add, and we are deleting it, just remove the path
711289177Speter                 altogether.  (The caller will delete any child paths.) */
712289177Speter              apr_hash_set(changed_paths, path->data, path->len, NULL);
713289177Speter            }
714289177Speter          else if (old_change->change_kind == svn_fs_path_change_replace)
715289177Speter            {
716289177Speter              /* A deleting a 'replace' restore the original deletion. */
717289177Speter              new_change = apr_hash_get(deletions, path->data, path->len);
718289177Speter              SVN_ERR_ASSERT(new_change);
719289177Speter              apr_hash_set(changed_paths, path->data, path->len, new_change);
720289177Speter            }
721289177Speter          else
722289177Speter            {
723289177Speter              /* A deletion overrules a previous change (modify). */
724289177Speter              new_change = path_change_dup(info, pool);
725289177Speter              apr_hash_set(changed_paths, path->data, path->len, new_change);
726289177Speter            }
727289177Speter          break;
728289177Speter
729289177Speter        case svn_fs_path_change_add:
730289177Speter        case svn_fs_path_change_replace:
731289177Speter          /* An add at this point must be following a previous delete,
732289177Speter             so treat it just like a replace.  Remember the original
733289177Speter             deletion such that we are able to delete this path again
734289177Speter             (the replacement may have changed node kind and id). */
735289177Speter          new_change = path_change_dup(info, pool);
736289177Speter          new_change->change_kind = svn_fs_path_change_replace;
737289177Speter
738289177Speter          apr_hash_set(changed_paths, path->data, path->len, new_change);
739289177Speter
740289177Speter          /* Remember the original change.
741289177Speter           * Make sure to allocate the hash key in a durable pool. */
742289177Speter          apr_hash_set(deletions,
743289177Speter                       apr_pstrmemdup(apr_hash_pool_get(deletions),
744289177Speter                                      path->data, path->len),
745289177Speter                       path->len, old_change);
746289177Speter          break;
747289177Speter
748289177Speter        case svn_fs_path_change_modify:
749289177Speter        default:
750289177Speter          /* If the new change modifies some attribute of the node, set
751289177Speter             the corresponding flag, whether it already was set or not.
752289177Speter             Note: We do not reset a flag to FALSE if a change is undone. */
753289177Speter          if (info->text_mod)
754289177Speter            old_change->text_mod = TRUE;
755289177Speter          if (info->prop_mod)
756289177Speter            old_change->prop_mod = TRUE;
757289177Speter          if (info->mergeinfo_mod == svn_tristate_true)
758289177Speter            old_change->mergeinfo_mod = svn_tristate_true;
759289177Speter          break;
760289177Speter        }
761289177Speter    }
762289177Speter  else
763289177Speter    {
764289177Speter      /* Add this path.  The API makes no guarantees that this (new) key
765289177Speter         will not be retained.  Thus, we copy the key into the target pool
766289177Speter         to ensure a proper lifetime.  */
767289177Speter      apr_hash_set(changed_paths,
768289177Speter                   apr_pstrmemdup(pool, path->data, path->len), path->len,
769289177Speter                   path_change_dup(info, pool));
770289177Speter    }
771289177Speter
772289177Speter  return SVN_NO_ERROR;
773289177Speter}
774289177Speter
775289177Speter/* Baton type to be used with process_changes(). */
776289177Spetertypedef struct process_changes_baton_t
777289177Speter{
778289177Speter  /* Folded list of path changes. */
779289177Speter  apr_hash_t *changed_paths;
780289177Speter
781289177Speter  /* Path changes that are deletions and have been turned into
782289177Speter     replacements.  If those replacements get deleted again, this
783289177Speter     container contains the record that we have to revert to. */
784289177Speter  apr_hash_t *deletions;
785289177Speter} process_changes_baton_t;
786289177Speter
787289177Speter/* An implementation of svn_fs_fs__change_receiver_t.
788289177Speter   Examine all the changed path entries in CHANGES and store them in
789289177Speter   *CHANGED_PATHS.  Folding is done to remove redundant or unnecessary
790289177Speter   data. Do all allocations in POOL. */
791289177Speterstatic svn_error_t *
792289177Speterprocess_changes(void *baton_p,
793289177Speter                change_t *change,
794289177Speter                apr_pool_t *scratch_pool)
795289177Speter{
796289177Speter  process_changes_baton_t *baton = baton_p;
797289177Speter
798289177Speter  SVN_ERR(fold_change(baton->changed_paths, baton->deletions, change));
799289177Speter
800289177Speter  /* Now, if our change was a deletion or replacement, we have to
801289177Speter     blow away any changes thus far on paths that are (or, were)
802289177Speter     children of this path.
803289177Speter     ### i won't bother with another iteration pool here -- at
804289177Speter     most we talking about a few extra dups of paths into what
805289177Speter     is already a temporary subpool.
806289177Speter  */
807289177Speter
808289177Speter  if ((change->info.change_kind == svn_fs_path_change_delete)
809289177Speter       || (change->info.change_kind == svn_fs_path_change_replace))
810289177Speter    {
811289177Speter      apr_hash_index_t *hi;
812289177Speter
813289177Speter      /* a potential child path must contain at least 2 more chars
814289177Speter         (the path separator plus at least one char for the name).
815289177Speter         Also, we should not assume that all paths have been normalized
816289177Speter         i.e. some might have trailing path separators.
817289177Speter      */
818289177Speter      apr_ssize_t path_len = change->path.len;
819289177Speter      apr_ssize_t min_child_len = path_len == 0
820289177Speter                                ? 1
821289177Speter                                : change->path.data[path_len-1] == '/'
822289177Speter                                    ? path_len + 1
823289177Speter                                    : path_len + 2;
824289177Speter
825289177Speter      /* CAUTION: This is the inner loop of an O(n^2) algorithm.
826289177Speter         The number of changes to process may be >> 1000.
827289177Speter         Therefore, keep the inner loop as tight as possible.
828289177Speter      */
829289177Speter      for (hi = apr_hash_first(scratch_pool, baton->changed_paths);
830289177Speter           hi;
831289177Speter           hi = apr_hash_next(hi))
832289177Speter        {
833289177Speter          /* KEY is the path. */
834289177Speter          const void *path;
835289177Speter          apr_ssize_t klen;
836289177Speter          svn_fs_path_change2_t *old_change;
837289177Speter          apr_hash_this(hi, &path, &klen, (void**)&old_change);
838289177Speter
839289177Speter          /* If we come across a child of our path, remove it.
840289177Speter             Call svn_fspath__skip_ancestor only if there is a chance that
841289177Speter             this is actually a sub-path.
842289177Speter           */
843289177Speter          if (klen >= min_child_len)
844289177Speter            {
845289177Speter              const char *child;
846289177Speter
847289177Speter              child = svn_fspath__skip_ancestor(change->path.data, path);
848289177Speter              if (child && child[0] != '\0')
849289177Speter                {
850289177Speter                  apr_hash_set(baton->changed_paths, path, klen, NULL);
851289177Speter                }
852289177Speter            }
853289177Speter        }
854289177Speter    }
855289177Speter
856289177Speter  return SVN_NO_ERROR;
857289177Speter}
858289177Speter
859289177Spetersvn_error_t *
860289177Spetersvn_fs_fs__txn_changes_fetch(apr_hash_t **changed_paths_p,
861289177Speter                             svn_fs_t *fs,
862289177Speter                             const svn_fs_fs__id_part_t *txn_id,
863289177Speter                             apr_pool_t *pool)
864289177Speter{
865289177Speter  apr_file_t *file;
866289177Speter  apr_hash_t *changed_paths = apr_hash_make(pool);
867289177Speter  apr_pool_t *scratch_pool = svn_pool_create(pool);
868289177Speter  process_changes_baton_t baton;
869289177Speter
870289177Speter  baton.changed_paths = changed_paths;
871289177Speter  baton.deletions = apr_hash_make(scratch_pool);
872289177Speter
873289177Speter  SVN_ERR(svn_io_file_open(&file,
874289177Speter                           path_txn_changes(fs, txn_id, scratch_pool),
875289177Speter                           APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
876289177Speter                           scratch_pool));
877289177Speter
878289177Speter  SVN_ERR(svn_fs_fs__read_changes_incrementally(
879289177Speter                                  svn_stream_from_aprfile2(file, TRUE,
880289177Speter                                                           scratch_pool),
881289177Speter                                  process_changes, &baton,
882289177Speter                                  scratch_pool));
883289177Speter  svn_pool_destroy(scratch_pool);
884289177Speter
885289177Speter  *changed_paths_p = changed_paths;
886289177Speter
887289177Speter  return SVN_NO_ERROR;
888289177Speter}
889289177Speter
890289177Speter
891289177Spetersvn_error_t *
892289177Spetersvn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
893289177Speter                         svn_fs_t *fs,
894289177Speter                         svn_revnum_t rev,
895289177Speter                         apr_pool_t *pool)
896289177Speter{
897289177Speter  apr_hash_t *changed_paths;
898289177Speter  apr_array_header_t *changes;
899289177Speter  int i;
900289177Speter
901289177Speter  SVN_ERR(svn_fs_fs__get_changes(&changes, fs, rev, pool));
902289177Speter
903289177Speter  changed_paths = svn_hash__make(pool);
904289177Speter  for (i = 0; i < changes->nelts; ++i)
905289177Speter    {
906289177Speter      change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
907289177Speter      apr_hash_set(changed_paths, change->path.data, change->path.len,
908289177Speter                   &change->info);
909289177Speter    }
910289177Speter
911289177Speter  *changed_paths_p = changed_paths;
912289177Speter
913289177Speter  return SVN_NO_ERROR;
914289177Speter}
915289177Speter
916289177Speter/* Copy a revision node-rev SRC into the current transaction TXN_ID in
917289177Speter   the filesystem FS.  This is only used to create the root of a transaction.
918289177Speter   Allocations are from POOL.  */
919289177Speterstatic svn_error_t *
920289177Spetercreate_new_txn_noderev_from_rev(svn_fs_t *fs,
921289177Speter                                const svn_fs_fs__id_part_t *txn_id,
922289177Speter                                svn_fs_id_t *src,
923289177Speter                                apr_pool_t *pool)
924289177Speter{
925289177Speter  node_revision_t *noderev;
926289177Speter  const svn_fs_fs__id_part_t *node_id, *copy_id;
927289177Speter
928289177Speter  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, src, pool, pool));
929289177Speter
930289177Speter  if (svn_fs_fs__id_is_txn(noderev->id))
931289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
932289177Speter                            _("Copying from transactions not allowed"));
933289177Speter
934289177Speter  noderev->predecessor_id = noderev->id;
935289177Speter  noderev->predecessor_count++;
936289177Speter  noderev->copyfrom_path = NULL;
937289177Speter  noderev->copyfrom_rev = SVN_INVALID_REVNUM;
938289177Speter
939289177Speter  /* For the transaction root, the copyroot never changes. */
940289177Speter
941289177Speter  node_id = svn_fs_fs__id_node_id(noderev->id);
942289177Speter  copy_id = svn_fs_fs__id_copy_id(noderev->id);
943289177Speter  noderev->id = svn_fs_fs__id_txn_create(node_id, copy_id, txn_id, pool);
944289177Speter
945289177Speter  return svn_fs_fs__put_node_revision(fs, noderev->id, noderev, TRUE, pool);
946289177Speter}
947289177Speter
948289177Speter/* A structure used by get_and_increment_txn_key_body(). */
949289177Speterstruct get_and_increment_txn_key_baton {
950289177Speter  svn_fs_t *fs;
951289177Speter  apr_uint64_t txn_number;
952289177Speter  apr_pool_t *pool;
953289177Speter};
954289177Speter
955289177Speter/* Callback used in the implementation of create_txn_dir().  This gets
956289177Speter   the current base 36 value in PATH_TXN_CURRENT and increments it.
957289177Speter   It returns the original value by the baton. */
958289177Speterstatic svn_error_t *
959289177Speterget_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
960289177Speter{
961289177Speter  struct get_and_increment_txn_key_baton *cb = baton;
962289177Speter  const char *txn_current_filename
963289177Speter    = svn_fs_fs__path_txn_current(cb->fs, pool);
964289177Speter  char new_id_str[SVN_INT64_BUFFER_SIZE + 1]; /* add space for a newline */
965289177Speter  apr_size_t line_length;
966289177Speter
967289177Speter  svn_stringbuf_t *buf;
968289177Speter  SVN_ERR(svn_fs_fs__read_content(&buf, txn_current_filename, cb->pool));
969289177Speter
970289177Speter  /* assign the current txn counter value to our result */
971289177Speter  cb->txn_number = svn__base36toui64(NULL, buf->data);
972289177Speter
973289177Speter  /* remove trailing newlines */
974289177Speter  line_length = svn__ui64tobase36(new_id_str, cb->txn_number+1);
975289177Speter  new_id_str[line_length] = '\n';
976289177Speter
977289177Speter  /* Increment the key and add a trailing \n to the string so the
978289177Speter     txn-current file has a newline in it. */
979289177Speter  SVN_ERR(svn_io_write_atomic(txn_current_filename, new_id_str,
980289177Speter                              line_length + 1,
981289177Speter                              txn_current_filename /* copy_perms path */,
982289177Speter                              pool));
983289177Speter
984289177Speter  return SVN_NO_ERROR;
985289177Speter}
986289177Speter
987289177Speter/* Create a unique directory for a transaction in FS based on revision REV.
988289177Speter   Return the ID for this transaction in *ID_P and *TXN_ID.  Use a sequence
989289177Speter   value in the transaction ID to prevent reuse of transaction IDs. */
990289177Speterstatic svn_error_t *
991289177Spetercreate_txn_dir(const char **id_p,
992289177Speter               svn_fs_fs__id_part_t *txn_id,
993289177Speter               svn_fs_t *fs,
994289177Speter               svn_revnum_t rev,
995289177Speter               apr_pool_t *pool)
996289177Speter{
997289177Speter  struct get_and_increment_txn_key_baton cb;
998289177Speter  const char *txn_dir;
999289177Speter
1000289177Speter  /* Get the current transaction sequence value, which is a base-36
1001289177Speter     number, from the txn-current file, and write an
1002289177Speter     incremented value back out to the file.  Place the revision
1003289177Speter     number the transaction is based off into the transaction id. */
1004289177Speter  cb.pool = pool;
1005289177Speter  cb.fs = fs;
1006289177Speter  SVN_ERR(svn_fs_fs__with_txn_current_lock(fs,
1007289177Speter                                           get_and_increment_txn_key_body,
1008289177Speter                                           &cb,
1009289177Speter                                           pool));
1010289177Speter  txn_id->revision = rev;
1011289177Speter  txn_id->number = cb.txn_number;
1012289177Speter
1013289177Speter  *id_p = svn_fs_fs__id_txn_unparse(txn_id, pool);
1014289177Speter  txn_dir = svn_fs_fs__path_txn_dir(fs, txn_id, pool);
1015289177Speter
1016289177Speter  return svn_io_dir_make(txn_dir, APR_OS_DEFAULT, pool);
1017289177Speter}
1018289177Speter
1019289177Speter/* Create a unique directory for a transaction in FS based on revision
1020289177Speter   REV.  Return the ID for this transaction in *ID_P and *TXN_ID.  This
1021289177Speter   implementation is used in svn 1.4 and earlier repositories and is
1022289177Speter   kept in 1.5 and greater to support the --pre-1.4-compatible and
1023289177Speter   --pre-1.5-compatible repository creation options.  Reused
1024289177Speter   transaction IDs are possible with this implementation. */
1025289177Speterstatic svn_error_t *
1026289177Spetercreate_txn_dir_pre_1_5(const char **id_p,
1027289177Speter                       svn_fs_fs__id_part_t *txn_id,
1028289177Speter                       svn_fs_t *fs,
1029289177Speter                       svn_revnum_t rev,
1030289177Speter                       apr_pool_t *pool)
1031289177Speter{
1032289177Speter  unsigned int i;
1033289177Speter  apr_pool_t *subpool;
1034289177Speter  const char *unique_path, *prefix;
1035289177Speter
1036289177Speter  /* Try to create directories named "<txndir>/<rev>-<uniqueifier>.txn". */
1037289177Speter  prefix = svn_dirent_join(svn_fs_fs__path_txns_dir(fs, pool),
1038289177Speter                           apr_psprintf(pool, "%ld", rev), pool);
1039289177Speter
1040289177Speter  subpool = svn_pool_create(pool);
1041289177Speter  for (i = 1; i <= 99999; i++)
1042289177Speter    {
1043289177Speter      svn_error_t *err;
1044289177Speter
1045289177Speter      svn_pool_clear(subpool);
1046289177Speter      unique_path = apr_psprintf(subpool, "%s-%u" PATH_EXT_TXN, prefix, i);
1047289177Speter      err = svn_io_dir_make(unique_path, APR_OS_DEFAULT, subpool);
1048289177Speter      if (! err)
1049289177Speter        {
1050289177Speter          /* We succeeded.  Return the basename minus the ".txn" extension. */
1051289177Speter          const char *name = svn_dirent_basename(unique_path, subpool);
1052289177Speter          *id_p = apr_pstrndup(pool, name,
1053289177Speter                               strlen(name) - strlen(PATH_EXT_TXN));
1054289177Speter          SVN_ERR(svn_fs_fs__id_txn_parse(txn_id, *id_p));
1055289177Speter          svn_pool_destroy(subpool);
1056289177Speter          return SVN_NO_ERROR;
1057289177Speter        }
1058289177Speter      if (! APR_STATUS_IS_EEXIST(err->apr_err))
1059289177Speter        return svn_error_trace(err);
1060289177Speter      svn_error_clear(err);
1061289177Speter    }
1062289177Speter
1063289177Speter  return svn_error_createf(SVN_ERR_IO_UNIQUE_NAMES_EXHAUSTED,
1064289177Speter                           NULL,
1065289177Speter                           _("Unable to create transaction directory "
1066289177Speter                             "in '%s' for revision %ld"),
1067289177Speter                           svn_dirent_local_style(fs->path, pool),
1068289177Speter                           rev);
1069289177Speter}
1070289177Speter
1071289177Spetersvn_error_t *
1072289177Spetersvn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
1073289177Speter                      svn_fs_t *fs,
1074289177Speter                      svn_revnum_t rev,
1075289177Speter                      apr_pool_t *pool)
1076289177Speter{
1077289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
1078289177Speter  svn_fs_txn_t *txn;
1079289177Speter  fs_txn_data_t *ftd;
1080289177Speter  svn_fs_id_t *root_id;
1081289177Speter
1082289177Speter  txn = apr_pcalloc(pool, sizeof(*txn));
1083289177Speter  ftd = apr_pcalloc(pool, sizeof(*ftd));
1084289177Speter
1085289177Speter  /* Get the txn_id. */
1086289177Speter  if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
1087289177Speter    SVN_ERR(create_txn_dir(&txn->id, &ftd->txn_id, fs, rev, pool));
1088289177Speter  else
1089289177Speter    SVN_ERR(create_txn_dir_pre_1_5(&txn->id, &ftd->txn_id, fs, rev, pool));
1090289177Speter
1091289177Speter  txn->fs = fs;
1092289177Speter  txn->base_rev = rev;
1093289177Speter
1094289177Speter  txn->vtable = &txn_vtable;
1095289177Speter  txn->fsap_data = ftd;
1096289177Speter  *txn_p = txn;
1097289177Speter
1098289177Speter  /* Create a new root node for this transaction. */
1099289177Speter  SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, pool, pool));
1100289177Speter  SVN_ERR(create_new_txn_noderev_from_rev(fs, &ftd->txn_id, root_id, pool));
1101289177Speter
1102289177Speter  /* Create an empty rev file. */
1103289177Speter  SVN_ERR(svn_io_file_create_empty(
1104289177Speter                    svn_fs_fs__path_txn_proto_rev(fs, &ftd->txn_id, pool),
1105289177Speter                    pool));
1106289177Speter
1107289177Speter  /* Create an empty rev-lock file. */
1108289177Speter  SVN_ERR(svn_io_file_create_empty(
1109289177Speter               svn_fs_fs__path_txn_proto_rev_lock(fs, &ftd->txn_id, pool),
1110289177Speter               pool));
1111289177Speter
1112289177Speter  /* Create an empty changes file. */
1113289177Speter  SVN_ERR(svn_io_file_create_empty(path_txn_changes(fs, &ftd->txn_id, pool),
1114289177Speter                                   pool));
1115289177Speter
1116289177Speter  /* Create the next-ids file. */
1117289177Speter  return svn_io_file_create(path_txn_next_ids(fs, &ftd->txn_id, pool),
1118289177Speter                            "0 0\n", pool);
1119289177Speter}
1120289177Speter
1121289177Speter/* Store the property list for transaction TXN_ID in PROPLIST.
1122289177Speter   Perform temporary allocations in POOL. */
1123289177Speterstatic svn_error_t *
1124289177Speterget_txn_proplist(apr_hash_t *proplist,
1125289177Speter                 svn_fs_t *fs,
1126289177Speter                 const svn_fs_fs__id_part_t *txn_id,
1127289177Speter                 apr_pool_t *pool)
1128289177Speter{
1129289177Speter  svn_stream_t *stream;
1130289177Speter  svn_error_t *err;
1131289177Speter
1132289177Speter  /* Check for issue #3696. (When we find and fix the cause, we can change
1133289177Speter   * this to an assertion.) */
1134289177Speter  if (!txn_id || !svn_fs_fs__id_txn_used(txn_id))
1135289177Speter    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1136289177Speter                            _("Internal error: a null transaction id was "
1137289177Speter                              "passed to get_txn_proplist()"));
1138289177Speter
1139289177Speter  /* Open the transaction properties file. */
1140289177Speter  SVN_ERR(svn_stream_open_readonly(&stream, path_txn_props(fs, txn_id, pool),
1141289177Speter                                   pool, pool));
1142289177Speter
1143289177Speter  /* Read in the property list. */
1144289177Speter  err = svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool);
1145289177Speter  if (err)
1146289177Speter    {
1147289177Speter      svn_error_clear(svn_stream_close(stream));
1148289177Speter      return svn_error_quick_wrapf(err,
1149289177Speter               _("malformed property list in transaction '%s'"),
1150289177Speter               path_txn_props(fs, txn_id, pool));
1151289177Speter    }
1152289177Speter
1153289177Speter  return svn_stream_close(stream);
1154289177Speter}
1155289177Speter
1156289177Speter/* Save the property list PROPS as the revprops for transaction TXN_ID
1157289177Speter   in FS.  Perform temporary allocations in POOL. */
1158289177Speterstatic svn_error_t *
1159289177Speterset_txn_proplist(svn_fs_t *fs,
1160289177Speter                 const svn_fs_fs__id_part_t *txn_id,
1161289177Speter                 apr_hash_t *props,
1162289177Speter                 svn_boolean_t final,
1163289177Speter                 apr_pool_t *pool)
1164289177Speter{
1165289177Speter  svn_stringbuf_t *buf;
1166289177Speter  svn_stream_t *stream;
1167289177Speter
1168289177Speter  /* Write out the new file contents to BUF. */
1169289177Speter  buf = svn_stringbuf_create_ensure(1024, pool);
1170289177Speter  stream = svn_stream_from_stringbuf(buf, pool);
1171289177Speter  SVN_ERR(svn_hash_write2(props, stream, SVN_HASH_TERMINATOR, pool));
1172289177Speter  SVN_ERR(svn_stream_close(stream));
1173289177Speter
1174289177Speter  /* Open the transaction properties file and write new contents to it. */
1175289177Speter  SVN_ERR(svn_io_write_atomic((final
1176289177Speter                               ? path_txn_props_final(fs, txn_id, pool)
1177289177Speter                               : path_txn_props(fs, txn_id, pool)),
1178289177Speter                              buf->data, buf->len,
1179289177Speter                              NULL /* copy_perms_path */, pool));
1180289177Speter  return SVN_NO_ERROR;
1181289177Speter}
1182289177Speter
1183289177Speter
1184289177Spetersvn_error_t *
1185289177Spetersvn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
1186289177Speter                           const char *name,
1187289177Speter                           const svn_string_t *value,
1188289177Speter                           apr_pool_t *pool)
1189289177Speter{
1190289177Speter  apr_array_header_t *props = apr_array_make(pool, 1, sizeof(svn_prop_t));
1191289177Speter  svn_prop_t prop;
1192289177Speter
1193289177Speter  prop.name = name;
1194289177Speter  prop.value = value;
1195289177Speter  APR_ARRAY_PUSH(props, svn_prop_t) = prop;
1196289177Speter
1197289177Speter  return svn_fs_fs__change_txn_props(txn, props, pool);
1198289177Speter}
1199289177Speter
1200289177Spetersvn_error_t *
1201289177Spetersvn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
1202289177Speter                            const apr_array_header_t *props,
1203289177Speter                            apr_pool_t *pool)
1204289177Speter{
1205289177Speter  fs_txn_data_t *ftd = txn->fsap_data;
1206289177Speter  apr_hash_t *txn_prop = apr_hash_make(pool);
1207289177Speter  int i;
1208289177Speter  svn_error_t *err;
1209289177Speter
1210289177Speter  err = get_txn_proplist(txn_prop, txn->fs, &ftd->txn_id, pool);
1211289177Speter  /* Here - and here only - we need to deal with the possibility that the
1212289177Speter     transaction property file doesn't yet exist.  The rest of the
1213289177Speter     implementation assumes that the file exists, but we're called to set the
1214289177Speter     initial transaction properties as the transaction is being created. */
1215289177Speter  if (err && (APR_STATUS_IS_ENOENT(err->apr_err)))
1216289177Speter    svn_error_clear(err);
1217289177Speter  else if (err)
1218289177Speter    return svn_error_trace(err);
1219289177Speter
1220289177Speter  for (i = 0; i < props->nelts; i++)
1221289177Speter    {
1222289177Speter      svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
1223289177Speter
1224289177Speter      if (svn_hash_gets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE)
1225289177Speter          && !strcmp(prop->name, SVN_PROP_REVISION_DATE))
1226289177Speter        svn_hash_sets(txn_prop, SVN_FS__PROP_TXN_CLIENT_DATE,
1227289177Speter                      svn_string_create("1", pool));
1228289177Speter
1229289177Speter      svn_hash_sets(txn_prop, prop->name, prop->value);
1230289177Speter    }
1231289177Speter
1232289177Speter  /* Create a new version of the file and write out the new props. */
1233289177Speter  /* Open the transaction properties file. */
1234289177Speter  SVN_ERR(set_txn_proplist(txn->fs, &ftd->txn_id, txn_prop, FALSE, pool));
1235289177Speter
1236289177Speter  return SVN_NO_ERROR;
1237289177Speter}
1238289177Speter
1239289177Spetersvn_error_t *
1240289177Spetersvn_fs_fs__get_txn(transaction_t **txn_p,
1241289177Speter                   svn_fs_t *fs,
1242289177Speter                   const svn_fs_fs__id_part_t *txn_id,
1243289177Speter                   apr_pool_t *pool)
1244289177Speter{
1245289177Speter  transaction_t *txn;
1246289177Speter  node_revision_t *noderev;
1247289177Speter  svn_fs_id_t *root_id;
1248289177Speter
1249289177Speter  txn = apr_pcalloc(pool, sizeof(*txn));
1250289177Speter  root_id = svn_fs_fs__id_txn_create_root(txn_id, pool);
1251289177Speter
1252289177Speter  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, root_id, pool, pool));
1253289177Speter
1254289177Speter  txn->root_id = svn_fs_fs__id_copy(noderev->id, pool);
1255289177Speter  txn->base_id = svn_fs_fs__id_copy(noderev->predecessor_id, pool);
1256289177Speter  txn->copies = NULL;
1257289177Speter
1258289177Speter  *txn_p = txn;
1259289177Speter
1260289177Speter  return SVN_NO_ERROR;
1261289177Speter}
1262289177Speter
1263289177Speter/* Write out the currently available next node_id NODE_ID and copy_id
1264289177Speter   COPY_ID for transaction TXN_ID in filesystem FS.  The next node-id is
1265289177Speter   used both for creating new unique nodes for the given transaction, as
1266289177Speter   well as uniquifying representations.  Perform temporary allocations in
1267289177Speter   POOL. */
1268289177Speterstatic svn_error_t *
1269289177Speterwrite_next_ids(svn_fs_t *fs,
1270289177Speter               const svn_fs_fs__id_part_t *txn_id,
1271289177Speter               apr_uint64_t node_id,
1272289177Speter               apr_uint64_t copy_id,
1273289177Speter               apr_pool_t *pool)
1274289177Speter{
1275289177Speter  apr_file_t *file;
1276289177Speter  char buffer[2 * SVN_INT64_BUFFER_SIZE + 2];
1277289177Speter  char *p = buffer;
1278289177Speter
1279289177Speter  p += svn__ui64tobase36(p, node_id);
1280289177Speter  *(p++) = ' ';
1281289177Speter  p += svn__ui64tobase36(p, copy_id);
1282289177Speter  *(p++) = '\n';
1283289177Speter  *(p++) = '\0';
1284289177Speter
1285289177Speter  SVN_ERR(svn_io_file_open(&file,
1286289177Speter                           path_txn_next_ids(fs, txn_id, pool),
1287289177Speter                           APR_WRITE | APR_TRUNCATE,
1288289177Speter                           APR_OS_DEFAULT, pool));
1289289177Speter  SVN_ERR(svn_io_file_write_full(file, buffer, p - buffer, NULL, pool));
1290289177Speter  return svn_io_file_close(file, pool);
1291289177Speter}
1292289177Speter
1293289177Speter/* Find out what the next unique node-id and copy-id are for
1294289177Speter   transaction TXN_ID in filesystem FS.  Store the results in *NODE_ID
1295289177Speter   and *COPY_ID.  The next node-id is used both for creating new unique
1296289177Speter   nodes for the given transaction, as well as uniquifying representations.
1297289177Speter   Perform all allocations in POOL. */
1298289177Speterstatic svn_error_t *
1299289177Speterread_next_ids(apr_uint64_t *node_id,
1300289177Speter              apr_uint64_t *copy_id,
1301289177Speter              svn_fs_t *fs,
1302289177Speter              const svn_fs_fs__id_part_t *txn_id,
1303289177Speter              apr_pool_t *pool)
1304289177Speter{
1305289177Speter  svn_stringbuf_t *buf;
1306289177Speter  const char *str;
1307289177Speter  SVN_ERR(svn_fs_fs__read_content(&buf,
1308289177Speter                                  path_txn_next_ids(fs, txn_id, pool),
1309289177Speter                                  pool));
1310289177Speter
1311289177Speter  /* Parse this into two separate strings. */
1312289177Speter
1313289177Speter  str = buf->data;
1314289177Speter  *node_id = svn__base36toui64(&str, str);
1315289177Speter  if (*str != ' ')
1316289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1317289177Speter                            _("next-id file corrupt"));
1318289177Speter
1319289177Speter  ++str;
1320289177Speter  *copy_id = svn__base36toui64(&str, str);
1321289177Speter  if (*str != '\n')
1322289177Speter    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
1323289177Speter                            _("next-id file corrupt"));
1324289177Speter
1325289177Speter  return SVN_NO_ERROR;
1326289177Speter}
1327289177Speter
1328289177Speter/* Get a new and unique to this transaction node-id for transaction
1329289177Speter   TXN_ID in filesystem FS.  Store the new node-id in *NODE_ID_P.
1330289177Speter   Node-ids are guaranteed to be unique to this transction, but may
1331289177Speter   not necessarily be sequential.  Perform all allocations in POOL. */
1332289177Speterstatic svn_error_t *
1333289177Speterget_new_txn_node_id(svn_fs_fs__id_part_t *node_id_p,
1334289177Speter                    svn_fs_t *fs,
1335289177Speter                    const svn_fs_fs__id_part_t *txn_id,
1336289177Speter                    apr_pool_t *pool)
1337289177Speter{
1338289177Speter  apr_uint64_t node_id, copy_id;
1339289177Speter
1340289177Speter  /* First read in the current next-ids file. */
1341289177Speter  SVN_ERR(read_next_ids(&node_id, &copy_id, fs, txn_id, pool));
1342289177Speter
1343289177Speter  node_id_p->revision = SVN_INVALID_REVNUM;
1344289177Speter  node_id_p->number = node_id;
1345289177Speter
1346289177Speter  SVN_ERR(write_next_ids(fs, txn_id, ++node_id, copy_id, pool));
1347289177Speter
1348289177Speter  return SVN_NO_ERROR;
1349289177Speter}
1350289177Speter
1351289177Spetersvn_error_t *
1352289177Spetersvn_fs_fs__reserve_copy_id(svn_fs_fs__id_part_t *copy_id_p,
1353289177Speter                           svn_fs_t *fs,
1354289177Speter                           const svn_fs_fs__id_part_t *txn_id,
1355289177Speter                           apr_pool_t *pool)
1356289177Speter{
1357289177Speter  apr_uint64_t node_id, copy_id;
1358289177Speter
1359289177Speter  /* First read in the current next-ids file. */
1360289177Speter  SVN_ERR(read_next_ids(&node_id, &copy_id, fs, txn_id, pool));
1361289177Speter
1362289177Speter  /* this is an in-txn ID now */
1363289177Speter  copy_id_p->revision = SVN_INVALID_REVNUM;
1364289177Speter  copy_id_p->number = copy_id;
1365289177Speter
1366289177Speter  /* Update the ID counter file */
1367289177Speter  SVN_ERR(write_next_ids(fs, txn_id, node_id, ++copy_id, pool));
1368289177Speter
1369289177Speter  return SVN_NO_ERROR;
1370289177Speter}
1371289177Speter
1372289177Spetersvn_error_t *
1373289177Spetersvn_fs_fs__create_node(const svn_fs_id_t **id_p,
1374289177Speter                       svn_fs_t *fs,
1375289177Speter                       node_revision_t *noderev,
1376289177Speter                       const svn_fs_fs__id_part_t *copy_id,
1377289177Speter                       const svn_fs_fs__id_part_t *txn_id,
1378289177Speter                       apr_pool_t *pool)
1379289177Speter{
1380289177Speter  svn_fs_fs__id_part_t node_id;
1381289177Speter  const svn_fs_id_t *id;
1382289177Speter
1383289177Speter  /* Get a new node-id for this node. */
1384289177Speter  SVN_ERR(get_new_txn_node_id(&node_id, fs, txn_id, pool));
1385289177Speter
1386289177Speter  id = svn_fs_fs__id_txn_create(&node_id, copy_id, txn_id, pool);
1387289177Speter
1388289177Speter  noderev->id = id;
1389289177Speter
1390289177Speter  SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE, pool));
1391289177Speter
1392289177Speter  *id_p = id;
1393289177Speter
1394289177Speter  return SVN_NO_ERROR;
1395289177Speter}
1396289177Speter
1397289177Spetersvn_error_t *
1398289177Spetersvn_fs_fs__purge_txn(svn_fs_t *fs,
1399289177Speter                     const char *txn_id_str,
1400289177Speter                     apr_pool_t *pool)
1401289177Speter{
1402289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
1403289177Speter  svn_fs_fs__id_part_t txn_id;
1404289177Speter  SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, txn_id_str));
1405289177Speter
1406289177Speter  /* Remove the shared transaction object associated with this transaction. */
1407289177Speter  SVN_ERR(purge_shared_txn(fs, &txn_id, pool));
1408289177Speter  /* Remove the directory associated with this transaction. */
1409289177Speter  SVN_ERR(svn_io_remove_dir2(svn_fs_fs__path_txn_dir(fs, &txn_id, pool),
1410289177Speter                             FALSE, NULL, NULL, pool));
1411289177Speter  if (ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
1412289177Speter    {
1413289177Speter      /* Delete protorev and its lock, which aren't in the txn
1414289177Speter         directory.  It's OK if they don't exist (for example, if this
1415289177Speter         is post-commit and the proto-rev has been moved into
1416289177Speter         place). */
1417289177Speter      SVN_ERR(svn_io_remove_file2(
1418289177Speter                  svn_fs_fs__path_txn_proto_rev(fs, &txn_id, pool),
1419289177Speter                  TRUE, pool));
1420289177Speter      SVN_ERR(svn_io_remove_file2(
1421289177Speter                  svn_fs_fs__path_txn_proto_rev_lock(fs, &txn_id, pool),
1422289177Speter                  TRUE, pool));
1423289177Speter    }
1424289177Speter  return SVN_NO_ERROR;
1425289177Speter}
1426289177Speter
1427289177Speter
1428289177Spetersvn_error_t *
1429289177Spetersvn_fs_fs__abort_txn(svn_fs_txn_t *txn,
1430289177Speter                     apr_pool_t *pool)
1431289177Speter{
1432289177Speter  SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
1433289177Speter
1434289177Speter  /* Now, purge the transaction. */
1435289177Speter  SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
1436289177Speter            apr_psprintf(pool, _("Transaction '%s' cleanup failed"),
1437289177Speter                         txn->id));
1438289177Speter
1439289177Speter  return SVN_NO_ERROR;
1440289177Speter}
1441289177Speter
1442289177Speter/* Assign the UNIQUIFIER member of REP based on the current state of TXN_ID
1443289177Speter * in FS.  Allocate the uniquifier in POOL.
1444289177Speter */
1445289177Speterstatic svn_error_t *
1446289177Speterset_uniquifier(svn_fs_t *fs,
1447289177Speter               representation_t *rep,
1448289177Speter               apr_pool_t *pool)
1449289177Speter{
1450289177Speter  svn_fs_fs__id_part_t temp;
1451289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
1452289177Speter
1453289177Speter  if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
1454289177Speter    {
1455289177Speter      SVN_ERR(get_new_txn_node_id(&temp, fs, &rep->txn_id, pool));
1456289177Speter      rep->uniquifier.noderev_txn_id = rep->txn_id;
1457289177Speter      rep->uniquifier.number = temp.number;
1458289177Speter    }
1459289177Speter
1460289177Speter  return SVN_NO_ERROR;
1461289177Speter}
1462289177Speter
1463289177Speter/* Return TRUE if the TXN_ID member of REP is in use.
1464289177Speter */
1465289177Speterstatic svn_boolean_t
1466289177Speteris_txn_rep(const representation_t *rep)
1467289177Speter{
1468289177Speter  return svn_fs_fs__id_txn_used(&rep->txn_id);
1469289177Speter}
1470289177Speter
1471289177Speter/* Mark the TXN_ID member of REP as "unused".
1472289177Speter */
1473289177Speterstatic void
1474289177Speterreset_txn_in_rep(representation_t *rep)
1475289177Speter{
1476289177Speter  svn_fs_fs__id_txn_reset(&rep->txn_id);
1477289177Speter}
1478289177Speter
1479289177Spetersvn_error_t *
1480289177Spetersvn_fs_fs__set_entry(svn_fs_t *fs,
1481289177Speter                     const svn_fs_fs__id_part_t *txn_id,
1482289177Speter                     node_revision_t *parent_noderev,
1483289177Speter                     const char *name,
1484289177Speter                     const svn_fs_id_t *id,
1485289177Speter                     svn_node_kind_t kind,
1486289177Speter                     apr_pool_t *pool)
1487289177Speter{
1488289177Speter  representation_t *rep = parent_noderev->data_rep;
1489289177Speter  const char *filename
1490289177Speter    = svn_fs_fs__path_txn_node_children(fs, parent_noderev->id, pool);
1491289177Speter  apr_file_t *file;
1492289177Speter  svn_stream_t *out;
1493289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
1494289177Speter  apr_pool_t *subpool = svn_pool_create(pool);
1495289177Speter
1496289177Speter  if (!rep || !is_txn_rep(rep))
1497289177Speter    {
1498289177Speter      apr_array_header_t *entries;
1499289177Speter
1500289177Speter      /* Before we can modify the directory, we need to dump its old
1501289177Speter         contents into a mutable representation file. */
1502289177Speter      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
1503289177Speter                                          subpool, subpool));
1504289177Speter      SVN_ERR(svn_io_file_open(&file, filename,
1505289177Speter                               APR_WRITE | APR_CREATE | APR_BUFFERED,
1506289177Speter                               APR_OS_DEFAULT, pool));
1507289177Speter      out = svn_stream_from_aprfile2(file, TRUE, pool);
1508289177Speter      SVN_ERR(unparse_dir_entries(entries, out, subpool));
1509289177Speter
1510289177Speter      svn_pool_clear(subpool);
1511289177Speter
1512289177Speter      /* Mark the node-rev's data rep as mutable. */
1513289177Speter      rep = apr_pcalloc(pool, sizeof(*rep));
1514289177Speter      rep->revision = SVN_INVALID_REVNUM;
1515289177Speter      rep->txn_id = *txn_id;
1516289177Speter      SVN_ERR(set_uniquifier(fs, rep, pool));
1517289177Speter      parent_noderev->data_rep = rep;
1518289177Speter      SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
1519289177Speter                                           parent_noderev, FALSE, pool));
1520289177Speter    }
1521289177Speter  else
1522289177Speter    {
1523289177Speter      /* The directory rep is already mutable, so just open it for append. */
1524289177Speter      SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
1525289177Speter                               APR_OS_DEFAULT, pool));
1526289177Speter      out = svn_stream_from_aprfile2(file, TRUE, pool);
1527289177Speter    }
1528289177Speter
1529289177Speter  /* if we have a directory cache for this transaction, update it */
1530289177Speter  if (ffd->txn_dir_cache)
1531289177Speter    {
1532289177Speter      /* build parameters: (name, new entry) pair */
1533289177Speter      const char *key =
1534289177Speter          svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
1535289177Speter      replace_baton_t baton;
1536289177Speter
1537289177Speter      baton.name = name;
1538289177Speter      baton.new_entry = NULL;
1539289177Speter
1540289177Speter      if (id)
1541289177Speter        {
1542289177Speter          baton.new_entry = apr_pcalloc(subpool, sizeof(*baton.new_entry));
1543289177Speter          baton.new_entry->name = name;
1544289177Speter          baton.new_entry->kind = kind;
1545289177Speter          baton.new_entry->id = id;
1546289177Speter        }
1547289177Speter
1548289177Speter      /* actually update the cached directory (if cached) */
1549289177Speter      SVN_ERR(svn_cache__set_partial(ffd->txn_dir_cache, key,
1550289177Speter                                     svn_fs_fs__replace_dir_entry, &baton,
1551289177Speter                                     subpool));
1552289177Speter    }
1553289177Speter  svn_pool_clear(subpool);
1554289177Speter
1555289177Speter  /* Append an incremental hash entry for the entry change. */
1556289177Speter  if (id)
1557289177Speter    {
1558289177Speter      svn_fs_dirent_t entry;
1559289177Speter      entry.name = name;
1560289177Speter      entry.id = id;
1561289177Speter      entry.kind = kind;
1562289177Speter
1563289177Speter      SVN_ERR(unparse_dir_entry(&entry, out, subpool));
1564289177Speter    }
1565289177Speter  else
1566289177Speter    {
1567289177Speter      SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
1568289177Speter                                strlen(name), name));
1569289177Speter    }
1570289177Speter
1571289177Speter  SVN_ERR(svn_io_file_close(file, subpool));
1572289177Speter  svn_pool_destroy(subpool);
1573289177Speter  return SVN_NO_ERROR;
1574289177Speter}
1575289177Speter
1576289177Spetersvn_error_t *
1577289177Spetersvn_fs_fs__add_change(svn_fs_t *fs,
1578289177Speter                      const svn_fs_fs__id_part_t *txn_id,
1579289177Speter                      const char *path,
1580289177Speter                      const svn_fs_id_t *id,
1581289177Speter                      svn_fs_path_change_kind_t change_kind,
1582289177Speter                      svn_boolean_t text_mod,
1583289177Speter                      svn_boolean_t prop_mod,
1584289177Speter                      svn_boolean_t mergeinfo_mod,
1585289177Speter                      svn_node_kind_t node_kind,
1586289177Speter                      svn_revnum_t copyfrom_rev,
1587289177Speter                      const char *copyfrom_path,
1588289177Speter                      apr_pool_t *pool)
1589289177Speter{
1590289177Speter  apr_file_t *file;
1591289177Speter  svn_fs_path_change2_t *change;
1592289177Speter  apr_hash_t *changes = apr_hash_make(pool);
1593289177Speter
1594289177Speter  /* Not using APR_BUFFERED to append change in one atomic write operation. */
1595289177Speter  SVN_ERR(svn_io_file_open(&file, path_txn_changes(fs, txn_id, pool),
1596289177Speter                           APR_APPEND | APR_WRITE | APR_CREATE,
1597289177Speter                           APR_OS_DEFAULT, pool));
1598289177Speter
1599289177Speter  change = svn_fs__path_change_create_internal(id, change_kind, pool);
1600289177Speter  change->text_mod = text_mod;
1601289177Speter  change->prop_mod = prop_mod;
1602289177Speter  change->mergeinfo_mod = mergeinfo_mod
1603289177Speter                        ? svn_tristate_true
1604289177Speter                        : svn_tristate_false;
1605289177Speter  change->node_kind = node_kind;
1606289177Speter  change->copyfrom_known = TRUE;
1607289177Speter  change->copyfrom_rev = copyfrom_rev;
1608289177Speter  if (copyfrom_path)
1609289177Speter    change->copyfrom_path = apr_pstrdup(pool, copyfrom_path);
1610289177Speter
1611289177Speter  svn_hash_sets(changes, path, change);
1612289177Speter  SVN_ERR(svn_fs_fs__write_changes(svn_stream_from_aprfile2(file, TRUE, pool),
1613289177Speter                                   fs, changes, FALSE, pool));
1614289177Speter
1615289177Speter  return svn_io_file_close(file, pool);
1616289177Speter}
1617289177Speter
1618289177Speter/* If the transaction TXN_ID in FS uses logical addressing, store the
1619289177Speter * (ITEM_INDEX, OFFSET) pair in the txn's log-to-phys proto index file.
1620289177Speter * Use POOL for allocations.
1621289177Speter */
1622289177Speterstatic svn_error_t *
1623289177Speterstore_l2p_index_entry(svn_fs_t *fs,
1624289177Speter                      const svn_fs_fs__id_part_t *txn_id,
1625289177Speter                      apr_off_t offset,
1626289177Speter                      apr_uint64_t item_index,
1627289177Speter                      apr_pool_t *pool)
1628289177Speter{
1629289177Speter  if (svn_fs_fs__use_log_addressing(fs))
1630289177Speter    {
1631289177Speter      const char *path = svn_fs_fs__path_l2p_proto_index(fs, txn_id, pool);
1632289177Speter      apr_file_t *file;
1633289177Speter      SVN_ERR(svn_fs_fs__l2p_proto_index_open(&file, path, pool));
1634289177Speter      SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(file, offset,
1635289177Speter                                                   item_index, pool));
1636289177Speter      SVN_ERR(svn_io_file_close(file, pool));
1637289177Speter    }
1638289177Speter
1639289177Speter  return SVN_NO_ERROR;
1640289177Speter}
1641289177Speter
1642289177Speter/* If the transaction TXN_ID in FS uses logical addressing, store ENTRY
1643289177Speter * in the phys-to-log proto index file of transaction TXN_ID.
1644289177Speter * Use POOL for allocations.
1645289177Speter */
1646289177Speterstatic svn_error_t *
1647289177Speterstore_p2l_index_entry(svn_fs_t *fs,
1648289177Speter                      const svn_fs_fs__id_part_t *txn_id,
1649289177Speter                      svn_fs_fs__p2l_entry_t *entry,
1650289177Speter                      apr_pool_t *pool)
1651289177Speter{
1652289177Speter  if (svn_fs_fs__use_log_addressing(fs))
1653289177Speter    {
1654289177Speter      const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool);
1655289177Speter      apr_file_t *file;
1656289177Speter      SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool));
1657289177Speter      SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(file, entry, pool));
1658289177Speter      SVN_ERR(svn_io_file_close(file, pool));
1659289177Speter    }
1660289177Speter
1661289177Speter  return SVN_NO_ERROR;
1662289177Speter}
1663289177Speter
1664289177Speter/* Allocate an item index for the given MY_OFFSET in the transaction TXN_ID
1665289177Speter * of file system FS and return it in *ITEM_INDEX.  For old formats, it
1666289177Speter * will simply return the offset as item index; in new formats, it will
1667289177Speter * increment the txn's item index counter file and store the mapping in
1668289177Speter * the proto index file.  Use POOL for allocations.
1669289177Speter */
1670289177Speterstatic svn_error_t *
1671289177Speterallocate_item_index(apr_uint64_t *item_index,
1672289177Speter                    svn_fs_t *fs,
1673289177Speter                    const svn_fs_fs__id_part_t *txn_id,
1674289177Speter                    apr_off_t my_offset,
1675289177Speter                    apr_pool_t *pool)
1676289177Speter{
1677289177Speter  if (svn_fs_fs__use_log_addressing(fs))
1678289177Speter    {
1679289177Speter      apr_file_t *file;
1680289177Speter      char buffer[SVN_INT64_BUFFER_SIZE] = { 0 };
1681289177Speter      svn_boolean_t eof = FALSE;
1682289177Speter      apr_size_t to_write;
1683289177Speter      apr_size_t read;
1684289177Speter      apr_off_t offset = 0;
1685289177Speter
1686289177Speter      /* read number, increment it and write it back to disk */
1687289177Speter      SVN_ERR(svn_io_file_open(&file,
1688289177Speter                         svn_fs_fs__path_txn_item_index(fs, txn_id, pool),
1689289177Speter                         APR_READ | APR_WRITE | APR_CREATE | APR_BUFFERED,
1690289177Speter                         APR_OS_DEFAULT, pool));
1691289177Speter      SVN_ERR(svn_io_file_read_full2(file, buffer, sizeof(buffer)-1,
1692289177Speter                                     &read, &eof, pool));
1693289177Speter      if (read)
1694289177Speter        SVN_ERR(svn_cstring_atoui64(item_index, buffer));
1695289177Speter      else
1696289177Speter        *item_index = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
1697289177Speter
1698289177Speter      to_write = svn__ui64toa(buffer, *item_index + 1);
1699289177Speter      SVN_ERR(svn_io_file_seek(file, APR_SET, &offset, pool));
1700289177Speter      SVN_ERR(svn_io_file_write_full(file, buffer, to_write, NULL, pool));
1701289177Speter      SVN_ERR(svn_io_file_close(file, pool));
1702289177Speter
1703289177Speter      /* write log-to-phys index */
1704289177Speter      SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset, *item_index, pool));
1705289177Speter    }
1706289177Speter  else
1707289177Speter    {
1708289177Speter      *item_index = (apr_uint64_t)my_offset;
1709289177Speter    }
1710289177Speter
1711289177Speter  return SVN_NO_ERROR;
1712289177Speter}
1713289177Speter
1714289177Speter/* Baton used by fnv1a_write_handler to calculate the FNV checksum
1715289177Speter * before passing the data on to the INNER_STREAM.
1716289177Speter */
1717289177Spetertypedef struct fnv1a_stream_baton_t
1718289177Speter{
1719289177Speter  svn_stream_t *inner_stream;
1720289177Speter  svn_checksum_ctx_t *context;
1721289177Speter} fnv1a_stream_baton_t;
1722289177Speter
1723289177Speter/* Implement svn_write_fn_t.
1724289177Speter * Update checksum and pass data on to inner stream.
1725289177Speter */
1726289177Speterstatic svn_error_t *
1727289177Speterfnv1a_write_handler(void *baton,
1728289177Speter                    const char *data,
1729289177Speter                    apr_size_t *len)
1730289177Speter{
1731289177Speter  fnv1a_stream_baton_t *b = baton;
1732289177Speter
1733289177Speter  SVN_ERR(svn_checksum_update(b->context, data, *len));
1734289177Speter  SVN_ERR(svn_stream_write(b->inner_stream, data, len));
1735289177Speter
1736289177Speter  return SVN_NO_ERROR;
1737289177Speter}
1738289177Speter
1739289177Speter/* Return a stream that calculates a FNV checksum in *CONTEXT
1740289177Speter * over all data written to the stream and passes that data on
1741289177Speter * to INNER_STREAM.  Allocate objects in POOL.
1742289177Speter */
1743289177Speterstatic svn_stream_t *
1744289177Speterfnv1a_wrap_stream(svn_checksum_ctx_t **context,
1745289177Speter                  svn_stream_t *inner_stream,
1746289177Speter                  apr_pool_t *pool)
1747289177Speter{
1748289177Speter  svn_stream_t *outer_stream;
1749289177Speter
1750289177Speter  fnv1a_stream_baton_t *baton = apr_pcalloc(pool, sizeof(*baton));
1751289177Speter  baton->inner_stream = inner_stream;
1752289177Speter  baton->context = svn_checksum_ctx_create(svn_checksum_fnv1a_32x4, pool);
1753289177Speter  *context = baton->context;
1754289177Speter
1755289177Speter  outer_stream = svn_stream_create(baton, pool);
1756289177Speter  svn_stream_set_write(outer_stream, fnv1a_write_handler);
1757289177Speter
1758289177Speter  return outer_stream;
1759289177Speter}
1760289177Speter
1761289177Speter/* Set *DIGEST to the FNV checksum calculated in CONTEXT.
1762289177Speter * Use SCRATCH_POOL for temporary allocations.
1763289177Speter */
1764289177Speterstatic svn_error_t *
1765289177Speterfnv1a_checksum_finalize(apr_uint32_t *digest,
1766289177Speter                        svn_checksum_ctx_t *context,
1767289177Speter                        apr_pool_t *scratch_pool)
1768289177Speter{
1769289177Speter  svn_checksum_t *checksum;
1770289177Speter
1771289177Speter  SVN_ERR(svn_checksum_final(&checksum, context, scratch_pool));
1772289177Speter  SVN_ERR_ASSERT(checksum->kind == svn_checksum_fnv1a_32x4);
1773289177Speter  *digest = ntohl(*(const apr_uint32_t *)(checksum->digest));
1774289177Speter
1775289177Speter  return SVN_NO_ERROR;
1776289177Speter}
1777289177Speter
1778289177Speter/* This baton is used by the representation writing streams.  It keeps
1779289177Speter   track of the checksum information as well as the total size of the
1780289177Speter   representation so far. */
1781289177Speterstruct rep_write_baton
1782289177Speter{
1783289177Speter  /* The FS we are writing to. */
1784289177Speter  svn_fs_t *fs;
1785289177Speter
1786289177Speter  /* Actual file to which we are writing. */
1787289177Speter  svn_stream_t *rep_stream;
1788289177Speter
1789289177Speter  /* A stream from the delta combiner.  Data written here gets
1790289177Speter     deltified, then eventually written to rep_stream. */
1791289177Speter  svn_stream_t *delta_stream;
1792289177Speter
1793289177Speter  /* Where is this representation header stored. */
1794289177Speter  apr_off_t rep_offset;
1795289177Speter
1796289177Speter  /* Start of the actual data. */
1797289177Speter  apr_off_t delta_start;
1798289177Speter
1799289177Speter  /* How many bytes have been written to this rep already. */
1800289177Speter  svn_filesize_t rep_size;
1801289177Speter
1802289177Speter  /* The node revision for which we're writing out info. */
1803289177Speter  node_revision_t *noderev;
1804289177Speter
1805289177Speter  /* Actual output file. */
1806289177Speter  apr_file_t *file;
1807289177Speter  /* Lock 'cookie' used to unlock the output file once we've finished
1808289177Speter     writing to it. */
1809289177Speter  void *lockcookie;
1810289177Speter
1811289177Speter  svn_checksum_ctx_t *md5_checksum_ctx;
1812289177Speter  svn_checksum_ctx_t *sha1_checksum_ctx;
1813289177Speter
1814289177Speter  /* calculate a modified FNV-1a checksum of the on-disk representation */
1815289177Speter  svn_checksum_ctx_t *fnv1a_checksum_ctx;
1816289177Speter
1817289177Speter  /* Local / scratch pool, available for temporary allocations. */
1818289177Speter  apr_pool_t *scratch_pool;
1819289177Speter
1820289177Speter  /* Outer / result pool. */
1821289177Speter  apr_pool_t *result_pool;
1822289177Speter};
1823289177Speter
1824289177Speter/* Handler for the write method of the representation writable stream.
1825289177Speter   BATON is a rep_write_baton, DATA is the data to write, and *LEN is
1826289177Speter   the length of this data. */
1827289177Speterstatic svn_error_t *
1828289177Speterrep_write_contents(void *baton,
1829289177Speter                   const char *data,
1830289177Speter                   apr_size_t *len)
1831289177Speter{
1832289177Speter  struct rep_write_baton *b = baton;
1833289177Speter
1834289177Speter  SVN_ERR(svn_checksum_update(b->md5_checksum_ctx, data, *len));
1835289177Speter  SVN_ERR(svn_checksum_update(b->sha1_checksum_ctx, data, *len));
1836289177Speter  b->rep_size += *len;
1837289177Speter
1838289177Speter  /* If we are writing a delta, use that stream. */
1839289177Speter  if (b->delta_stream)
1840289177Speter    return svn_stream_write(b->delta_stream, data, len);
1841289177Speter  else
1842289177Speter    return svn_stream_write(b->rep_stream, data, len);
1843289177Speter}
1844289177Speter
1845289177Speter/* Set *SPANNED to the number of shards touched when walking WALK steps on
1846289177Speter * NODEREV's predecessor chain in FS.  Use POOL for temporary allocations.
1847289177Speter */
1848289177Speterstatic svn_error_t *
1849289177Spetershards_spanned(int *spanned,
1850289177Speter               svn_fs_t *fs,
1851289177Speter               node_revision_t *noderev,
1852289177Speter               int walk,
1853289177Speter               apr_pool_t *pool)
1854289177Speter{
1855289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
1856289177Speter  int shard_size = ffd->max_files_per_dir ? ffd->max_files_per_dir : 1;
1857289177Speter  apr_pool_t *iterpool;
1858289177Speter
1859289177Speter  int count = walk ? 1 : 0; /* The start of a walk already touches a shard. */
1860289177Speter  svn_revnum_t shard, last_shard = ffd->youngest_rev_cache / shard_size;
1861289177Speter  iterpool = svn_pool_create(pool);
1862289177Speter  while (walk-- && noderev->predecessor_count)
1863289177Speter    {
1864289177Speter      svn_pool_clear(iterpool);
1865289177Speter      SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs,
1866289177Speter                                           noderev->predecessor_id, pool,
1867289177Speter                                           iterpool));
1868289177Speter      shard = svn_fs_fs__id_rev(noderev->id) / shard_size;
1869289177Speter      if (shard != last_shard)
1870289177Speter        {
1871289177Speter          ++count;
1872289177Speter          last_shard = shard;
1873289177Speter        }
1874289177Speter    }
1875289177Speter  svn_pool_destroy(iterpool);
1876289177Speter
1877289177Speter  *spanned = count;
1878289177Speter  return SVN_NO_ERROR;
1879289177Speter}
1880289177Speter
1881289177Speter/* Given a node-revision NODEREV in filesystem FS, return the
1882289177Speter   representation in *REP to use as the base for a text representation
1883289177Speter   delta if PROPS is FALSE.  If PROPS has been set, a suitable props
1884289177Speter   base representation will be returned.  Perform temporary allocations
1885289177Speter   in *POOL. */
1886289177Speterstatic svn_error_t *
1887289177Speterchoose_delta_base(representation_t **rep,
1888289177Speter                  svn_fs_t *fs,
1889289177Speter                  node_revision_t *noderev,
1890289177Speter                  svn_boolean_t props,
1891289177Speter                  apr_pool_t *pool)
1892289177Speter{
1893289177Speter  /* The zero-based index (counting from the "oldest" end), along NODEREVs line
1894289177Speter   * predecessors, of the node-rev we will use as delta base. */
1895289177Speter  int count;
1896289177Speter  /* The length of the linear part of a delta chain.  (Delta chains use
1897289177Speter   * skip-delta bits for the high-order bits and are linear in the low-order
1898289177Speter   * bits.) */
1899289177Speter  int walk;
1900289177Speter  node_revision_t *base;
1901289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
1902289177Speter  apr_pool_t *iterpool;
1903289177Speter
1904289177Speter  /* If we have no predecessors, or that one is empty, then use the empty
1905289177Speter   * stream as a base. */
1906289177Speter  if (! noderev->predecessor_count)
1907289177Speter    {
1908289177Speter      *rep = NULL;
1909289177Speter      return SVN_NO_ERROR;
1910289177Speter    }
1911289177Speter
1912289177Speter  /* Flip the rightmost '1' bit of the predecessor count to determine
1913289177Speter     which file rev (counting from 0) we want to use.  (To see why
1914289177Speter     count & (count - 1) unsets the rightmost set bit, think about how
1915289177Speter     you decrement a binary number.) */
1916289177Speter  count = noderev->predecessor_count;
1917289177Speter  count = count & (count - 1);
1918289177Speter
1919289177Speter  /* Finding the delta base over a very long distance can become extremely
1920289177Speter     expensive for very deep histories, possibly causing client timeouts etc.
1921289177Speter     OTOH, this is a rare operation and its gains are minimal. Lets simply
1922289177Speter     start deltification anew close every other 1000 changes or so.  */
1923289177Speter  walk = noderev->predecessor_count - count;
1924289177Speter  if (walk > (int)ffd->max_deltification_walk)
1925289177Speter    {
1926289177Speter      *rep = NULL;
1927289177Speter      return SVN_NO_ERROR;
1928289177Speter    }
1929289177Speter
1930289177Speter  /* We use skip delta for limiting the number of delta operations
1931289177Speter     along very long node histories.  Close to HEAD however, we create
1932289177Speter     a linear history to minimize delta size.  */
1933289177Speter  if (walk < (int)ffd->max_linear_deltification)
1934289177Speter    {
1935289177Speter      int shards;
1936289177Speter      SVN_ERR(shards_spanned(&shards, fs, noderev, walk, pool));
1937289177Speter
1938289177Speter      /* We also don't want the linear deltification to span more shards
1939289177Speter         than if deltas we used in a simple skip-delta scheme. */
1940289177Speter      if ((1 << (--shards)) <= walk)
1941289177Speter        count = noderev->predecessor_count - 1;
1942289177Speter    }
1943289177Speter
1944289177Speter  /* Walk back a number of predecessors equal to the difference
1945289177Speter     between count and the original predecessor count.  (For example,
1946289177Speter     if noderev has ten predecessors and we want the eighth file rev,
1947289177Speter     walk back two predecessors.) */
1948289177Speter  base = noderev;
1949289177Speter  iterpool = svn_pool_create(pool);
1950289177Speter  while ((count++) < noderev->predecessor_count)
1951289177Speter    {
1952289177Speter      svn_pool_clear(iterpool);
1953289177Speter      SVN_ERR(svn_fs_fs__get_node_revision(&base, fs,
1954289177Speter                                           base->predecessor_id, pool,
1955289177Speter                                           iterpool));
1956289177Speter    }
1957289177Speter  svn_pool_destroy(iterpool);
1958289177Speter
1959289177Speter  /* return a suitable base representation */
1960289177Speter  *rep = props ? base->prop_rep : base->data_rep;
1961289177Speter
1962289177Speter  /* if we encountered a shared rep, its parent chain may be different
1963289177Speter   * from the node-rev parent chain. */
1964289177Speter  if (*rep)
1965289177Speter    {
1966289177Speter      int chain_length = 0;
1967289177Speter      int shard_count = 0;
1968289177Speter
1969289177Speter      /* Very short rep bases are simply not worth it as we are unlikely
1970289177Speter       * to re-coup the deltification space overhead of 20+ bytes. */
1971289177Speter      svn_filesize_t rep_size = (*rep)->expanded_size
1972289177Speter                              ? (*rep)->expanded_size
1973289177Speter                              : (*rep)->size;
1974289177Speter      if (rep_size < 64)
1975289177Speter        {
1976289177Speter          *rep = NULL;
1977289177Speter          return SVN_NO_ERROR;
1978289177Speter        }
1979289177Speter
1980289177Speter      /* Check whether the length of the deltification chain is acceptable.
1981289177Speter       * Otherwise, shared reps may form a non-skipping delta chain in
1982289177Speter       * extreme cases. */
1983289177Speter      SVN_ERR(svn_fs_fs__rep_chain_length(&chain_length, &shard_count,
1984289177Speter                                          *rep, fs, pool));
1985289177Speter
1986289177Speter      /* Some reasonable limit, depending on how acceptable longer linear
1987289177Speter       * chains are in this repo.  Also, allow for some minimal chain. */
1988289177Speter      if (chain_length >= 2 * (int)ffd->max_linear_deltification + 2)
1989289177Speter        *rep = NULL;
1990289177Speter      else
1991289177Speter        /* To make it worth opening additional shards / pack files, we
1992289177Speter         * require that the reps have a certain minimal size.  To deltify
1993289177Speter         * against a rep in different shard, the lower limit is 512 bytes
1994289177Speter         * and doubles with every extra shard to visit along the delta
1995289177Speter         * chain. */
1996289177Speter        if (   shard_count > 1
1997289177Speter            && ((svn_filesize_t)128 << shard_count) >= rep_size)
1998289177Speter          *rep = NULL;
1999289177Speter    }
2000289177Speter
2001289177Speter  return SVN_NO_ERROR;
2002289177Speter}
2003289177Speter
2004289177Speter/* Something went wrong and the pool for the rep write is being
2005289177Speter   cleared before we've finished writing the rep.  So we need
2006289177Speter   to remove the rep from the protorevfile and we need to unlock
2007289177Speter   the protorevfile. */
2008289177Speterstatic apr_status_t
2009289177Speterrep_write_cleanup(void *data)
2010289177Speter{
2011289177Speter  struct rep_write_baton *b = data;
2012289177Speter  svn_error_t *err;
2013289177Speter
2014289177Speter  /* Truncate and close the protorevfile. */
2015289177Speter  err = svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool);
2016289177Speter  err = svn_error_compose_create(err, svn_io_file_close(b->file,
2017289177Speter                                                        b->scratch_pool));
2018289177Speter
2019289177Speter  /* Remove our lock regardless of any preceding errors so that the
2020289177Speter     being_written flag is always removed and stays consistent with the
2021289177Speter     file lock which will be removed no matter what since the pool is
2022289177Speter     going away. */
2023289177Speter  err = svn_error_compose_create(err,
2024289177Speter                                 unlock_proto_rev(b->fs,
2025289177Speter                                     svn_fs_fs__id_txn_id(b->noderev->id),
2026289177Speter                                     b->lockcookie, b->scratch_pool));
2027289177Speter  if (err)
2028289177Speter    {
2029289177Speter      apr_status_t rc = err->apr_err;
2030289177Speter      svn_error_clear(err);
2031289177Speter      return rc;
2032289177Speter    }
2033289177Speter
2034289177Speter  return APR_SUCCESS;
2035289177Speter}
2036289177Speter
2037289177Speter/* Get a rep_write_baton and store it in *WB_P for the representation
2038289177Speter   indicated by NODEREV in filesystem FS.  Perform allocations in
2039289177Speter   POOL.  Only appropriate for file contents, not for props or
2040289177Speter   directory contents. */
2041289177Speterstatic svn_error_t *
2042289177Speterrep_write_get_baton(struct rep_write_baton **wb_p,
2043289177Speter                    svn_fs_t *fs,
2044289177Speter                    node_revision_t *noderev,
2045289177Speter                    apr_pool_t *pool)
2046289177Speter{
2047289177Speter  struct rep_write_baton *b;
2048289177Speter  apr_file_t *file;
2049289177Speter  representation_t *base_rep;
2050289177Speter  svn_stream_t *source;
2051289177Speter  svn_txdelta_window_handler_t wh;
2052289177Speter  void *whb;
2053289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
2054289177Speter  int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
2055289177Speter  svn_fs_fs__rep_header_t header = { 0 };
2056289177Speter
2057289177Speter  b = apr_pcalloc(pool, sizeof(*b));
2058289177Speter
2059289177Speter  b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
2060289177Speter  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
2061289177Speter
2062289177Speter  b->fs = fs;
2063289177Speter  b->result_pool = pool;
2064289177Speter  b->scratch_pool = svn_pool_create(pool);
2065289177Speter  b->rep_size = 0;
2066289177Speter  b->noderev = noderev;
2067289177Speter
2068289177Speter  /* Open the prototype rev file and seek to its end. */
2069289177Speter  SVN_ERR(get_writable_proto_rev(&file, &b->lockcookie,
2070289177Speter                                 fs, svn_fs_fs__id_txn_id(noderev->id),
2071289177Speter                                 b->scratch_pool));
2072289177Speter
2073289177Speter  b->file = file;
2074289177Speter  b->rep_stream = fnv1a_wrap_stream(&b->fnv1a_checksum_ctx,
2075289177Speter                                    svn_stream_from_aprfile2(file, TRUE,
2076289177Speter                                                             b->scratch_pool),
2077289177Speter                                    b->scratch_pool);
2078289177Speter
2079289177Speter  SVN_ERR(svn_fs_fs__get_file_offset(&b->rep_offset, file, b->scratch_pool));
2080289177Speter
2081289177Speter  /* Get the base for this delta. */
2082289177Speter  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->scratch_pool));
2083289177Speter  SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, TRUE,
2084289177Speter                                  b->scratch_pool));
2085289177Speter
2086289177Speter  /* Write out the rep header. */
2087289177Speter  if (base_rep)
2088289177Speter    {
2089289177Speter      header.base_revision = base_rep->revision;
2090289177Speter      header.base_item_index = base_rep->item_index;
2091289177Speter      header.base_length = base_rep->size;
2092289177Speter      header.type = svn_fs_fs__rep_delta;
2093289177Speter    }
2094289177Speter  else
2095289177Speter    {
2096289177Speter      header.type = svn_fs_fs__rep_self_delta;
2097289177Speter    }
2098289177Speter  SVN_ERR(svn_fs_fs__write_rep_header(&header, b->rep_stream,
2099289177Speter                                      b->scratch_pool));
2100289177Speter
2101289177Speter  /* Now determine the offset of the actual svndiff data. */
2102289177Speter  SVN_ERR(svn_fs_fs__get_file_offset(&b->delta_start, file,
2103289177Speter                                     b->scratch_pool));
2104289177Speter
2105289177Speter  /* Cleanup in case something goes wrong. */
2106289177Speter  apr_pool_cleanup_register(b->scratch_pool, b, rep_write_cleanup,
2107289177Speter                            apr_pool_cleanup_null);
2108289177Speter
2109289177Speter  /* Prepare to write the svndiff data. */
2110289177Speter  svn_txdelta_to_svndiff3(&wh,
2111289177Speter                          &whb,
2112289177Speter                          b->rep_stream,
2113289177Speter                          diff_version,
2114289177Speter                          ffd->delta_compression_level,
2115289177Speter                          pool);
2116289177Speter
2117289177Speter  b->delta_stream = svn_txdelta_target_push(wh, whb, source,
2118289177Speter                                            b->scratch_pool);
2119289177Speter
2120289177Speter  *wb_p = b;
2121289177Speter
2122289177Speter  return SVN_NO_ERROR;
2123289177Speter}
2124289177Speter
2125289177Speter/* For REP->SHA1_CHECKSUM, try to find an already existing representation
2126289177Speter   in FS and return it in *OUT_REP.  If no such representation exists or
2127289177Speter   if rep sharing has been disabled for FS, NULL will be returned.  Since
2128289177Speter   there may be new duplicate representations within the same uncommitted
2129289177Speter   revision, those can be passed in REPS_HASH (maps a sha1 digest onto
2130289177Speter   representation_t*), otherwise pass in NULL for REPS_HASH.
2131289177Speter   Use RESULT_POOL for *OLD_REP  allocations and SCRATCH_POOL for temporaries.
2132289177Speter   The lifetime of *OLD_REP is limited by both, RESULT_POOL and REP lifetime.
2133289177Speter */
2134289177Speterstatic svn_error_t *
2135289177Speterget_shared_rep(representation_t **old_rep,
2136289177Speter               svn_fs_t *fs,
2137289177Speter               representation_t *rep,
2138289177Speter               apr_hash_t *reps_hash,
2139289177Speter               apr_pool_t *result_pool,
2140289177Speter               apr_pool_t *scratch_pool)
2141289177Speter{
2142289177Speter  svn_error_t *err;
2143289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
2144289177Speter
2145289177Speter  /* Return NULL, if rep sharing has been disabled. */
2146289177Speter  *old_rep = NULL;
2147289177Speter  if (!ffd->rep_sharing_allowed)
2148289177Speter    return SVN_NO_ERROR;
2149289177Speter
2150289177Speter  /* Check and see if we already have a representation somewhere that's
2151289177Speter     identical to the one we just wrote out.  Start with the hash lookup
2152289177Speter     because it is cheepest. */
2153289177Speter  if (reps_hash)
2154289177Speter    *old_rep = apr_hash_get(reps_hash,
2155289177Speter                            rep->sha1_digest,
2156289177Speter                            APR_SHA1_DIGESTSIZE);
2157289177Speter
2158289177Speter  /* If we haven't found anything yet, try harder and consult our DB. */
2159289177Speter  if (*old_rep == NULL)
2160289177Speter    {
2161289177Speter      svn_checksum_t checksum;
2162289177Speter      checksum.digest = rep->sha1_digest;
2163289177Speter      checksum.kind = svn_checksum_sha1;
2164289177Speter      err = svn_fs_fs__get_rep_reference(old_rep, fs, &checksum, result_pool);
2165289177Speter      /* ### Other error codes that we shouldn't mask out? */
2166289177Speter      if (err == SVN_NO_ERROR)
2167289177Speter        {
2168289177Speter          if (*old_rep)
2169289177Speter            SVN_ERR(svn_fs_fs__check_rep(*old_rep, fs, NULL, scratch_pool));
2170289177Speter        }
2171289177Speter      else if (err->apr_err == SVN_ERR_FS_CORRUPT
2172289177Speter               || SVN_ERROR_IN_CATEGORY(err->apr_err,
2173289177Speter                                        SVN_ERR_MALFUNC_CATEGORY_START))
2174289177Speter        {
2175289177Speter          /* Fatal error; don't mask it.
2176289177Speter
2177289177Speter             In particular, this block is triggered when the rep-cache refers
2178289177Speter             to revisions in the future.  We signal that as a corruption situation
2179289177Speter             since, once those revisions are less than youngest (because of more
2180289177Speter             commits), the rep-cache would be invalid.
2181289177Speter           */
2182289177Speter          SVN_ERR(err);
2183289177Speter        }
2184289177Speter      else
2185289177Speter        {
2186289177Speter          /* Something's wrong with the rep-sharing index.  We can continue
2187289177Speter             without rep-sharing, but warn.
2188289177Speter           */
2189289177Speter          (fs->warning)(fs->warning_baton, err);
2190289177Speter          svn_error_clear(err);
2191289177Speter          *old_rep = NULL;
2192289177Speter        }
2193289177Speter    }
2194289177Speter
2195289177Speter  /* look for intra-revision matches (usually data reps but not limited
2196289177Speter     to them in case props happen to look like some data rep)
2197289177Speter   */
2198289177Speter  if (*old_rep == NULL && is_txn_rep(rep))
2199289177Speter    {
2200289177Speter      svn_node_kind_t kind;
2201289177Speter      const char *file_name
2202289177Speter        = path_txn_sha1(fs, &rep->txn_id, rep->sha1_digest, scratch_pool);
2203289177Speter
2204289177Speter      /* in our txn, is there a rep file named with the wanted SHA1?
2205289177Speter         If so, read it and use that rep.
2206289177Speter       */
2207289177Speter      SVN_ERR(svn_io_check_path(file_name, &kind, scratch_pool));
2208289177Speter      if (kind == svn_node_file)
2209289177Speter        {
2210289177Speter          svn_stringbuf_t *rep_string;
2211289177Speter          SVN_ERR(svn_stringbuf_from_file2(&rep_string, file_name,
2212289177Speter                                           scratch_pool));
2213289177Speter          SVN_ERR(svn_fs_fs__parse_representation(old_rep, rep_string,
2214289177Speter                                                  result_pool, scratch_pool));
2215289177Speter        }
2216289177Speter    }
2217289177Speter
2218289177Speter  if (!*old_rep)
2219289177Speter    return SVN_NO_ERROR;
2220289177Speter
2221289177Speter  /* We don't want 0-length PLAIN representations to replace non-0-length
2222289177Speter     ones (see issue #4554).  Take into account that EXPANDED_SIZE may be
2223289177Speter     0 in which case we have to check the on-disk SIZE.  Also, this doubles
2224289177Speter     as a simple guard against general rep-cache induced corruption. */
2225289177Speter  if (   ((*old_rep)->expanded_size != rep->expanded_size)
2226289177Speter      || ((rep->expanded_size == 0) && ((*old_rep)->size != rep->size)))
2227289177Speter    {
2228289177Speter      *old_rep = NULL;
2229289177Speter    }
2230289177Speter  else
2231289177Speter    {
2232289177Speter      /* Add information that is missing in the cached data.
2233289177Speter         Use the old rep for this content. */
2234289177Speter      memcpy((*old_rep)->md5_digest, rep->md5_digest, sizeof(rep->md5_digest));
2235289177Speter      (*old_rep)->uniquifier = rep->uniquifier;
2236289177Speter    }
2237289177Speter
2238289177Speter  return SVN_NO_ERROR;
2239289177Speter}
2240289177Speter
2241289177Speter/* Copy the hash sum calculation results from MD5_CTX, SHA1_CTX into REP.
2242289177Speter * Use POOL for allocations.
2243289177Speter */
2244289177Speterstatic svn_error_t *
2245289177Speterdigests_final(representation_t *rep,
2246289177Speter              const svn_checksum_ctx_t *md5_ctx,
2247289177Speter              const svn_checksum_ctx_t *sha1_ctx,
2248289177Speter              apr_pool_t *pool)
2249289177Speter{
2250289177Speter  svn_checksum_t *checksum;
2251289177Speter
2252289177Speter  SVN_ERR(svn_checksum_final(&checksum, md5_ctx, pool));
2253289177Speter  memcpy(rep->md5_digest, checksum->digest, svn_checksum_size(checksum));
2254289177Speter  SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, pool));
2255289177Speter  rep->has_sha1 = checksum != NULL;
2256289177Speter  if (rep->has_sha1)
2257289177Speter    memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
2258289177Speter
2259289177Speter  return SVN_NO_ERROR;
2260289177Speter}
2261289177Speter
2262289177Speter/* Close handler for the representation write stream.  BATON is a
2263289177Speter   rep_write_baton.  Writes out a new node-rev that correctly
2264289177Speter   references the representation we just finished writing. */
2265289177Speterstatic svn_error_t *
2266289177Speterrep_write_contents_close(void *baton)
2267289177Speter{
2268289177Speter  struct rep_write_baton *b = baton;
2269289177Speter  representation_t *rep;
2270289177Speter  representation_t *old_rep;
2271289177Speter  apr_off_t offset;
2272289177Speter
2273289177Speter  rep = apr_pcalloc(b->result_pool, sizeof(*rep));
2274289177Speter
2275289177Speter  /* Close our delta stream so the last bits of svndiff are written
2276289177Speter     out. */
2277289177Speter  if (b->delta_stream)
2278289177Speter    SVN_ERR(svn_stream_close(b->delta_stream));
2279289177Speter
2280289177Speter  /* Determine the length of the svndiff data. */
2281289177Speter  SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
2282289177Speter  rep->size = offset - b->delta_start;
2283289177Speter
2284289177Speter  /* Fill in the rest of the representation field. */
2285289177Speter  rep->expanded_size = b->rep_size;
2286289177Speter  rep->txn_id = *svn_fs_fs__id_txn_id(b->noderev->id);
2287289177Speter  SVN_ERR(set_uniquifier(b->fs, rep, b->scratch_pool));
2288289177Speter  rep->revision = SVN_INVALID_REVNUM;
2289289177Speter
2290289177Speter  /* Finalize the checksum. */
2291289177Speter  SVN_ERR(digests_final(rep, b->md5_checksum_ctx, b->sha1_checksum_ctx,
2292289177Speter                        b->result_pool));
2293289177Speter
2294289177Speter  /* Check and see if we already have a representation somewhere that's
2295289177Speter     identical to the one we just wrote out. */
2296289177Speter  SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->result_pool,
2297289177Speter                         b->scratch_pool));
2298289177Speter
2299289177Speter  if (old_rep)
2300289177Speter    {
2301289177Speter      /* We need to erase from the protorev the data we just wrote. */
2302289177Speter      SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->scratch_pool));
2303289177Speter
2304289177Speter      /* Use the old rep for this content. */
2305289177Speter      b->noderev->data_rep = old_rep;
2306289177Speter    }
2307289177Speter  else
2308289177Speter    {
2309289177Speter      /* Write out our cosmetic end marker. */
2310289177Speter      SVN_ERR(svn_stream_puts(b->rep_stream, "ENDREP\n"));
2311289177Speter      SVN_ERR(allocate_item_index(&rep->item_index, b->fs, &rep->txn_id,
2312289177Speter                                  b->rep_offset, b->scratch_pool));
2313289177Speter
2314289177Speter      b->noderev->data_rep = rep;
2315289177Speter    }
2316289177Speter
2317289177Speter  /* Remove cleanup callback. */
2318289177Speter  apr_pool_cleanup_kill(b->scratch_pool, b, rep_write_cleanup);
2319289177Speter
2320289177Speter  /* Write out the new node-rev information. */
2321289177Speter  SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev,
2322289177Speter                                       FALSE, b->scratch_pool));
2323289177Speter  if (!old_rep)
2324289177Speter    {
2325289177Speter      svn_fs_fs__p2l_entry_t entry;
2326289177Speter
2327289177Speter      entry.offset = b->rep_offset;
2328289177Speter      SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
2329289177Speter      entry.size = offset - b->rep_offset;
2330289177Speter      entry.type = SVN_FS_FS__ITEM_TYPE_FILE_REP;
2331289177Speter      entry.item.revision = SVN_INVALID_REVNUM;
2332289177Speter      entry.item.number = rep->item_index;
2333289177Speter      SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2334289177Speter                                      b->fnv1a_checksum_ctx,
2335289177Speter                                      b->scratch_pool));
2336289177Speter
2337289177Speter      SVN_ERR(store_p2l_index_entry(b->fs, &rep->txn_id, &entry,
2338289177Speter                                    b->scratch_pool));
2339289177Speter    }
2340289177Speter
2341289177Speter  SVN_ERR(svn_io_file_close(b->file, b->scratch_pool));
2342309512Speter
2343309512Speter  /* Write the sha1->rep mapping *after* we successfully written node
2344309512Speter   * revision to disk. */
2345309512Speter  if (!old_rep)
2346309512Speter    SVN_ERR(store_sha1_rep_mapping(b->fs, b->noderev, b->scratch_pool));
2347309512Speter
2348289177Speter  SVN_ERR(unlock_proto_rev(b->fs, &rep->txn_id, b->lockcookie,
2349289177Speter                           b->scratch_pool));
2350289177Speter  svn_pool_destroy(b->scratch_pool);
2351289177Speter
2352289177Speter  return SVN_NO_ERROR;
2353289177Speter}
2354289177Speter
2355289177Speter/* Store a writable stream in *CONTENTS_P that will receive all data
2356289177Speter   written and store it as the file data representation referenced by
2357289177Speter   NODEREV in filesystem FS.  Perform temporary allocations in
2358289177Speter   POOL.  Only appropriate for file data, not props or directory
2359289177Speter   contents. */
2360289177Speterstatic svn_error_t *
2361289177Speterset_representation(svn_stream_t **contents_p,
2362289177Speter                   svn_fs_t *fs,
2363289177Speter                   node_revision_t *noderev,
2364289177Speter                   apr_pool_t *pool)
2365289177Speter{
2366289177Speter  struct rep_write_baton *wb;
2367289177Speter
2368289177Speter  if (! svn_fs_fs__id_is_txn(noderev->id))
2369289177Speter    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2370289177Speter                             _("Attempted to write to non-transaction '%s'"),
2371289177Speter                             svn_fs_fs__id_unparse(noderev->id, pool)->data);
2372289177Speter
2373289177Speter  SVN_ERR(rep_write_get_baton(&wb, fs, noderev, pool));
2374289177Speter
2375289177Speter  *contents_p = svn_stream_create(wb, pool);
2376289177Speter  svn_stream_set_write(*contents_p, rep_write_contents);
2377289177Speter  svn_stream_set_close(*contents_p, rep_write_contents_close);
2378289177Speter
2379289177Speter  return SVN_NO_ERROR;
2380289177Speter}
2381289177Speter
2382289177Spetersvn_error_t *
2383289177Spetersvn_fs_fs__set_contents(svn_stream_t **stream,
2384289177Speter                        svn_fs_t *fs,
2385289177Speter                        node_revision_t *noderev,
2386289177Speter                        apr_pool_t *pool)
2387289177Speter{
2388289177Speter  if (noderev->kind != svn_node_file)
2389289177Speter    return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL,
2390289177Speter                            _("Can't set text contents of a directory"));
2391289177Speter
2392289177Speter  return set_representation(stream, fs, noderev, pool);
2393289177Speter}
2394289177Speter
2395289177Spetersvn_error_t *
2396289177Spetersvn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
2397289177Speter                            svn_fs_t *fs,
2398289177Speter                            const svn_fs_id_t *old_idp,
2399289177Speter                            node_revision_t *new_noderev,
2400289177Speter                            const svn_fs_fs__id_part_t *copy_id,
2401289177Speter                            const svn_fs_fs__id_part_t *txn_id,
2402289177Speter                            apr_pool_t *pool)
2403289177Speter{
2404289177Speter  const svn_fs_id_t *id;
2405289177Speter
2406289177Speter  if (! copy_id)
2407289177Speter    copy_id = svn_fs_fs__id_copy_id(old_idp);
2408289177Speter  id = svn_fs_fs__id_txn_create(svn_fs_fs__id_node_id(old_idp), copy_id,
2409289177Speter                                txn_id, pool);
2410289177Speter
2411289177Speter  new_noderev->id = id;
2412289177Speter
2413289177Speter  if (! new_noderev->copyroot_path)
2414289177Speter    {
2415289177Speter      new_noderev->copyroot_path = apr_pstrdup(pool,
2416289177Speter                                               new_noderev->created_path);
2417289177Speter      new_noderev->copyroot_rev = svn_fs_fs__id_rev(new_noderev->id);
2418289177Speter    }
2419289177Speter
2420289177Speter  SVN_ERR(svn_fs_fs__put_node_revision(fs, new_noderev->id, new_noderev, FALSE,
2421289177Speter                                       pool));
2422289177Speter
2423289177Speter  *new_id_p = id;
2424289177Speter
2425289177Speter  return SVN_NO_ERROR;
2426289177Speter}
2427289177Speter
2428289177Spetersvn_error_t *
2429289177Spetersvn_fs_fs__set_proplist(svn_fs_t *fs,
2430289177Speter                        node_revision_t *noderev,
2431289177Speter                        apr_hash_t *proplist,
2432289177Speter                        apr_pool_t *pool)
2433289177Speter{
2434289177Speter  const char *filename
2435289177Speter    = svn_fs_fs__path_txn_node_props(fs, noderev->id, pool);
2436289177Speter  apr_file_t *file;
2437289177Speter  svn_stream_t *out;
2438289177Speter
2439289177Speter  /* Dump the property list to the mutable property file. */
2440289177Speter  SVN_ERR(svn_io_file_open(&file, filename,
2441289177Speter                           APR_WRITE | APR_CREATE | APR_TRUNCATE
2442289177Speter                           | APR_BUFFERED, APR_OS_DEFAULT, pool));
2443289177Speter  out = svn_stream_from_aprfile2(file, TRUE, pool);
2444289177Speter  SVN_ERR(svn_hash_write2(proplist, out, SVN_HASH_TERMINATOR, pool));
2445289177Speter  SVN_ERR(svn_io_file_close(file, pool));
2446289177Speter
2447289177Speter  /* Mark the node-rev's prop rep as mutable, if not already done. */
2448289177Speter  if (!noderev->prop_rep || !is_txn_rep(noderev->prop_rep))
2449289177Speter    {
2450289177Speter      noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
2451289177Speter      noderev->prop_rep->txn_id = *svn_fs_fs__id_txn_id(noderev->id);
2452289177Speter      SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE,
2453289177Speter                                           pool));
2454289177Speter    }
2455289177Speter
2456289177Speter  return SVN_NO_ERROR;
2457289177Speter}
2458289177Speter
2459289177Speter/* This baton is used by the stream created for write_container_rep. */
2460289177Speterstruct write_container_baton
2461289177Speter{
2462289177Speter  svn_stream_t *stream;
2463289177Speter
2464289177Speter  apr_size_t size;
2465289177Speter
2466289177Speter  svn_checksum_ctx_t *md5_ctx;
2467289177Speter  svn_checksum_ctx_t *sha1_ctx;
2468289177Speter};
2469289177Speter
2470289177Speter/* The handler for the write_container_rep stream.  BATON is a
2471289177Speter   write_container_baton, DATA has the data to write and *LEN is the number
2472289177Speter   of bytes to write. */
2473289177Speterstatic svn_error_t *
2474289177Speterwrite_container_handler(void *baton,
2475289177Speter                        const char *data,
2476289177Speter                        apr_size_t *len)
2477289177Speter{
2478289177Speter  struct write_container_baton *whb = baton;
2479289177Speter
2480289177Speter  SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
2481289177Speter  SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
2482289177Speter
2483289177Speter  SVN_ERR(svn_stream_write(whb->stream, data, len));
2484289177Speter  whb->size += *len;
2485289177Speter
2486289177Speter  return SVN_NO_ERROR;
2487289177Speter}
2488289177Speter
2489289177Speter/* Callback function type.  Write the data provided by BATON into STREAM. */
2490289177Spetertypedef svn_error_t *
2491289177Speter(* collection_writer_t)(svn_stream_t *stream, void *baton, apr_pool_t *pool);
2492289177Speter
2493289177Speter/* Implement collection_writer_t writing the C string->svn_string_t hash
2494289177Speter   given as BATON. */
2495289177Speterstatic svn_error_t *
2496289177Speterwrite_hash_to_stream(svn_stream_t *stream,
2497289177Speter                     void *baton,
2498289177Speter                     apr_pool_t *pool)
2499289177Speter{
2500289177Speter  apr_hash_t *hash = baton;
2501289177Speter  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
2502289177Speter
2503289177Speter  return SVN_NO_ERROR;
2504289177Speter}
2505289177Speter
2506289177Speter/* Implement collection_writer_t writing the svn_fs_dirent_t* array given
2507289177Speter   as BATON. */
2508289177Speterstatic svn_error_t *
2509289177Speterwrite_directory_to_stream(svn_stream_t *stream,
2510289177Speter                          void *baton,
2511289177Speter                          apr_pool_t *pool)
2512289177Speter{
2513289177Speter  apr_array_header_t *dir = baton;
2514289177Speter  SVN_ERR(unparse_dir_entries(dir, stream, pool));
2515289177Speter
2516289177Speter  return SVN_NO_ERROR;
2517289177Speter}
2518289177Speter
2519289177Speter/* Write out the COLLECTION as a text representation to file FILE using
2520289177Speter   WRITER.  In the process, record position, the total size of the dump and
2521289177Speter   MD5 as well as SHA1 in REP.   Add the representation of type ITEM_TYPE to
2522289177Speter   the indexes if necessary.  If rep sharing has been enabled and REPS_HASH
2523289177Speter   is not NULL, it will be used in addition to the on-disk cache to find
2524289177Speter   earlier reps with the same content.  When such existing reps can be
2525289177Speter   found, we will truncate the one just written from the file and return
2526289177Speter   the existing rep.  Perform temporary allocations in SCRATCH_POOL. */
2527289177Speterstatic svn_error_t *
2528289177Speterwrite_container_rep(representation_t *rep,
2529289177Speter                    apr_file_t *file,
2530289177Speter                    void *collection,
2531289177Speter                    collection_writer_t writer,
2532289177Speter                    svn_fs_t *fs,
2533289177Speter                    apr_hash_t *reps_hash,
2534289177Speter                    apr_uint32_t item_type,
2535289177Speter                    apr_pool_t *scratch_pool)
2536289177Speter{
2537289177Speter  svn_stream_t *stream;
2538289177Speter  struct write_container_baton *whb;
2539289177Speter  svn_checksum_ctx_t *fnv1a_checksum_ctx;
2540289177Speter  representation_t *old_rep;
2541289177Speter  apr_off_t offset = 0;
2542289177Speter
2543289177Speter  SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2544289177Speter
2545289177Speter  whb = apr_pcalloc(scratch_pool, sizeof(*whb));
2546289177Speter
2547289177Speter  whb->stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
2548289177Speter                                  svn_stream_from_aprfile2(file, TRUE,
2549289177Speter                                                           scratch_pool),
2550289177Speter                                  scratch_pool);
2551289177Speter  whb->size = 0;
2552289177Speter  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
2553289177Speter  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
2554289177Speter
2555289177Speter  stream = svn_stream_create(whb, scratch_pool);
2556289177Speter  svn_stream_set_write(stream, write_container_handler);
2557289177Speter
2558289177Speter  SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
2559289177Speter
2560289177Speter  SVN_ERR(writer(stream, collection, scratch_pool));
2561289177Speter
2562289177Speter  /* Store the results. */
2563289177Speter  SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
2564289177Speter
2565289177Speter  /* Check and see if we already have a representation somewhere that's
2566289177Speter     identical to the one we just wrote out. */
2567289177Speter  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool,
2568289177Speter                         scratch_pool));
2569289177Speter
2570289177Speter  if (old_rep)
2571289177Speter    {
2572289177Speter      /* We need to erase from the protorev the data we just wrote. */
2573289177Speter      SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
2574289177Speter
2575289177Speter      /* Use the old rep for this content. */
2576289177Speter      memcpy(rep, old_rep, sizeof (*rep));
2577289177Speter    }
2578289177Speter  else
2579289177Speter    {
2580289177Speter      svn_fs_fs__p2l_entry_t entry;
2581289177Speter
2582289177Speter      /* Write out our cosmetic end marker. */
2583289177Speter      SVN_ERR(svn_stream_puts(whb->stream, "ENDREP\n"));
2584289177Speter
2585289177Speter      SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
2586289177Speter                                  offset, scratch_pool));
2587289177Speter
2588289177Speter      entry.offset = offset;
2589289177Speter      SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2590289177Speter      entry.size = offset - entry.offset;
2591289177Speter      entry.type = item_type;
2592289177Speter      entry.item.revision = SVN_INVALID_REVNUM;
2593289177Speter      entry.item.number = rep->item_index;
2594289177Speter      SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2595289177Speter                                      fnv1a_checksum_ctx,
2596289177Speter                                      scratch_pool));
2597289177Speter
2598289177Speter      SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
2599289177Speter
2600289177Speter      /* update the representation */
2601289177Speter      rep->size = whb->size;
2602289177Speter      rep->expanded_size = whb->size;
2603289177Speter    }
2604289177Speter
2605289177Speter  return SVN_NO_ERROR;
2606289177Speter}
2607289177Speter
2608289177Speter/* Write out the COLLECTION pertaining to the NODEREV in FS as a deltified
2609289177Speter   text representation to file FILE using WRITER.  In the process, record the
2610289177Speter   total size and the md5 digest in REP and add the representation of type
2611289177Speter   ITEM_TYPE to the indexes if necessary.  If rep sharing has been enabled and
2612289177Speter   REPS_HASH is not NULL, it will be used in addition to the on-disk cache to
2613289177Speter   find earlier reps with the same content.  When such existing reps can be
2614289177Speter   found, we will truncate the one just written from the file and return the
2615289177Speter   existing rep.
2616289177Speter
2617289177Speter   If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume
2618289177Speter   that we want to a props representation as the base for our delta.
2619289177Speter   Perform temporary allocations in SCRATCH_POOL.
2620289177Speter */
2621289177Speterstatic svn_error_t *
2622289177Speterwrite_container_delta_rep(representation_t *rep,
2623289177Speter                          apr_file_t *file,
2624289177Speter                          void *collection,
2625289177Speter                          collection_writer_t writer,
2626289177Speter                          svn_fs_t *fs,
2627289177Speter                          node_revision_t *noderev,
2628289177Speter                          apr_hash_t *reps_hash,
2629289177Speter                          apr_uint32_t item_type,
2630289177Speter                          apr_pool_t *scratch_pool)
2631289177Speter{
2632289177Speter  svn_txdelta_window_handler_t diff_wh;
2633289177Speter  void *diff_whb;
2634289177Speter
2635289177Speter  svn_stream_t *file_stream;
2636289177Speter  svn_stream_t *stream;
2637289177Speter  representation_t *base_rep;
2638289177Speter  representation_t *old_rep;
2639289177Speter  svn_checksum_ctx_t *fnv1a_checksum_ctx;
2640289177Speter  svn_stream_t *source;
2641289177Speter  svn_fs_fs__rep_header_t header = { 0 };
2642289177Speter
2643289177Speter  apr_off_t rep_end = 0;
2644289177Speter  apr_off_t delta_start = 0;
2645289177Speter  apr_off_t offset = 0;
2646289177Speter
2647289177Speter  struct write_container_baton *whb;
2648289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
2649289177Speter  int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
2650289177Speter  svn_boolean_t is_props = (item_type == SVN_FS_FS__ITEM_TYPE_FILE_PROPS)
2651289177Speter                        || (item_type == SVN_FS_FS__ITEM_TYPE_DIR_PROPS);
2652289177Speter
2653289177Speter  /* Get the base for this delta. */
2654289177Speter  SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool));
2655289177Speter  SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, FALSE, scratch_pool));
2656289177Speter
2657289177Speter  SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2658289177Speter
2659289177Speter  /* Write out the rep header. */
2660289177Speter  if (base_rep)
2661289177Speter    {
2662289177Speter      header.base_revision = base_rep->revision;
2663289177Speter      header.base_item_index = base_rep->item_index;
2664289177Speter      header.base_length = base_rep->size;
2665289177Speter      header.type = svn_fs_fs__rep_delta;
2666289177Speter    }
2667289177Speter  else
2668289177Speter    {
2669289177Speter      header.type = svn_fs_fs__rep_self_delta;
2670289177Speter    }
2671289177Speter
2672289177Speter  file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
2673289177Speter                                  svn_stream_from_aprfile2(file, TRUE,
2674289177Speter                                                           scratch_pool),
2675289177Speter                                  scratch_pool);
2676289177Speter  SVN_ERR(svn_fs_fs__write_rep_header(&header, file_stream, scratch_pool));
2677289177Speter  SVN_ERR(svn_fs_fs__get_file_offset(&delta_start, file, scratch_pool));
2678289177Speter
2679289177Speter  /* Prepare to write the svndiff data. */
2680289177Speter  svn_txdelta_to_svndiff3(&diff_wh,
2681289177Speter                          &diff_whb,
2682289177Speter                          file_stream,
2683289177Speter                          diff_version,
2684289177Speter                          ffd->delta_compression_level,
2685289177Speter                          scratch_pool);
2686289177Speter
2687289177Speter  whb = apr_pcalloc(scratch_pool, sizeof(*whb));
2688289177Speter  whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source,
2689289177Speter                                        scratch_pool);
2690289177Speter  whb->size = 0;
2691289177Speter  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
2692289177Speter  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
2693289177Speter
2694289177Speter  /* serialize the hash */
2695289177Speter  stream = svn_stream_create(whb, scratch_pool);
2696289177Speter  svn_stream_set_write(stream, write_container_handler);
2697289177Speter
2698289177Speter  SVN_ERR(writer(stream, collection, scratch_pool));
2699289177Speter  SVN_ERR(svn_stream_close(whb->stream));
2700289177Speter
2701289177Speter  /* Store the results. */
2702289177Speter  SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
2703289177Speter
2704289177Speter  /* Check and see if we already have a representation somewhere that's
2705289177Speter     identical to the one we just wrote out. */
2706289177Speter  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, scratch_pool,
2707289177Speter                         scratch_pool));
2708289177Speter
2709289177Speter  if (old_rep)
2710289177Speter    {
2711289177Speter      /* We need to erase from the protorev the data we just wrote. */
2712289177Speter      SVN_ERR(svn_io_file_trunc(file, offset, scratch_pool));
2713289177Speter
2714289177Speter      /* Use the old rep for this content. */
2715289177Speter      memcpy(rep, old_rep, sizeof (*rep));
2716289177Speter    }
2717289177Speter  else
2718289177Speter    {
2719289177Speter      svn_fs_fs__p2l_entry_t entry;
2720289177Speter
2721289177Speter      /* Write out our cosmetic end marker. */
2722289177Speter      SVN_ERR(svn_fs_fs__get_file_offset(&rep_end, file, scratch_pool));
2723289177Speter      SVN_ERR(svn_stream_puts(file_stream, "ENDREP\n"));
2724289177Speter
2725289177Speter      SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
2726289177Speter                                  offset, scratch_pool));
2727289177Speter
2728289177Speter      entry.offset = offset;
2729289177Speter      SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
2730289177Speter      entry.size = offset - entry.offset;
2731289177Speter      entry.type = item_type;
2732289177Speter      entry.item.revision = SVN_INVALID_REVNUM;
2733289177Speter      entry.item.number = rep->item_index;
2734289177Speter      SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
2735289177Speter                                      fnv1a_checksum_ctx,
2736289177Speter                                      scratch_pool));
2737289177Speter
2738289177Speter      SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
2739289177Speter
2740289177Speter      /* update the representation */
2741289177Speter      rep->expanded_size = whb->size;
2742289177Speter      rep->size = rep_end - delta_start;
2743289177Speter    }
2744289177Speter
2745289177Speter  return SVN_NO_ERROR;
2746289177Speter}
2747289177Speter
2748289177Speter/* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
2749289177Speter   of (not yet committed) revision REV in FS.  Use POOL for temporary
2750289177Speter   allocations.
2751289177Speter
2752289177Speter   If you change this function, consider updating svn_fs_fs__verify() too.
2753289177Speter */
2754289177Speterstatic svn_error_t *
2755289177Spetervalidate_root_noderev(svn_fs_t *fs,
2756289177Speter                      node_revision_t *root_noderev,
2757289177Speter                      svn_revnum_t rev,
2758289177Speter                      apr_pool_t *pool)
2759289177Speter{
2760289177Speter  svn_revnum_t head_revnum = rev-1;
2761289177Speter  int head_predecessor_count;
2762289177Speter
2763289177Speter  SVN_ERR_ASSERT(rev > 0);
2764289177Speter
2765289177Speter  /* Compute HEAD_PREDECESSOR_COUNT. */
2766289177Speter  {
2767289177Speter    svn_fs_root_t *head_revision;
2768289177Speter    const svn_fs_id_t *head_root_id;
2769289177Speter    node_revision_t *head_root_noderev;
2770289177Speter
2771289177Speter    /* Get /@HEAD's noderev. */
2772289177Speter    SVN_ERR(svn_fs_fs__revision_root(&head_revision, fs, head_revnum, pool));
2773289177Speter    SVN_ERR(svn_fs_fs__node_id(&head_root_id, head_revision, "/", pool));
2774289177Speter    SVN_ERR(svn_fs_fs__get_node_revision(&head_root_noderev, fs, head_root_id,
2775289177Speter                                         pool, pool));
2776289177Speter    head_predecessor_count = head_root_noderev->predecessor_count;
2777289177Speter  }
2778289177Speter
2779289177Speter  /* Check that the root noderev's predecessor count equals REV.
2780289177Speter
2781289177Speter     This kind of corruption was seen on svn.apache.org (both on
2782289177Speter     the root noderev and on other fspaths' noderevs); see
2783289177Speter     issue #4129.
2784289177Speter
2785289177Speter     Normally (rev == root_noderev->predecessor_count), but here we
2786289177Speter     use a more roundabout check that should only trigger on new instances
2787289177Speter     of the corruption, rather then trigger on each and every new commit
2788289177Speter     to a repository that has triggered the bug somewhere in its root
2789289177Speter     noderev's history.
2790289177Speter   */
2791289177Speter  if (root_noderev->predecessor_count != -1
2792289177Speter      && (root_noderev->predecessor_count - head_predecessor_count)
2793289177Speter         != (rev - head_revnum))
2794289177Speter    {
2795289177Speter      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
2796289177Speter                               _("predecessor count for "
2797289177Speter                                 "the root node-revision is wrong: "
2798289177Speter                                 "found (%d+%ld != %d), committing r%ld"),
2799289177Speter                                 head_predecessor_count,
2800289177Speter                                 rev - head_revnum, /* This is equal to 1. */
2801289177Speter                                 root_noderev->predecessor_count,
2802289177Speter                                 rev);
2803289177Speter    }
2804289177Speter
2805289177Speter  return SVN_NO_ERROR;
2806289177Speter}
2807289177Speter
2808289177Speter/* Given the potentially txn-local id PART, update that to a permanent ID
2809289177Speter * based on the REVISION currently being written and the START_ID for that
2810289177Speter * revision.  Use the repo FORMAT to decide which implementation to use.
2811289177Speter */
2812289177Speterstatic void
2813289177Speterget_final_id(svn_fs_fs__id_part_t *part,
2814289177Speter             svn_revnum_t revision,
2815289177Speter             apr_uint64_t start_id,
2816289177Speter             int format)
2817289177Speter{
2818289177Speter  if (part->revision == SVN_INVALID_REVNUM)
2819289177Speter    {
2820289177Speter      if (format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
2821289177Speter        {
2822289177Speter          part->revision = revision;
2823289177Speter        }
2824289177Speter      else
2825289177Speter        {
2826289177Speter          part->revision = 0;
2827289177Speter          part->number += start_id;
2828289177Speter        }
2829289177Speter    }
2830289177Speter}
2831289177Speter
2832289177Speter/* Copy a node-revision specified by id ID in fileystem FS from a
2833289177Speter   transaction into the proto-rev-file FILE.  Set *NEW_ID_P to a
2834289177Speter   pointer to the new node-id which will be allocated in POOL.
2835289177Speter   If this is a directory, copy all children as well.
2836289177Speter
2837289177Speter   START_NODE_ID and START_COPY_ID are
2838289177Speter   the first available node and copy ids for this filesystem, for older
2839289177Speter   FS formats.
2840289177Speter
2841289177Speter   REV is the revision number that this proto-rev-file will represent.
2842289177Speter
2843289177Speter   INITIAL_OFFSET is the offset of the proto-rev-file on entry to
2844289177Speter   commit_body.
2845289177Speter
2846289177Speter   If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
2847289177Speter   REPS_POOL) of each data rep that is new in this revision.
2848289177Speter
2849289177Speter   If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
2850289177Speter   of the representations of each property rep that is new in this
2851289177Speter   revision.
2852289177Speter
2853289177Speter   AT_ROOT is true if the node revision being written is the root
2854289177Speter   node-revision.  It is only controls additional sanity checking
2855289177Speter   logic.
2856289177Speter
2857289177Speter   Temporary allocations are also from POOL. */
2858289177Speterstatic svn_error_t *
2859289177Speterwrite_final_rev(const svn_fs_id_t **new_id_p,
2860289177Speter                apr_file_t *file,
2861289177Speter                svn_revnum_t rev,
2862289177Speter                svn_fs_t *fs,
2863289177Speter                const svn_fs_id_t *id,
2864289177Speter                apr_uint64_t start_node_id,
2865289177Speter                apr_uint64_t start_copy_id,
2866289177Speter                apr_off_t initial_offset,
2867289177Speter                apr_array_header_t *reps_to_cache,
2868289177Speter                apr_hash_t *reps_hash,
2869289177Speter                apr_pool_t *reps_pool,
2870289177Speter                svn_boolean_t at_root,
2871289177Speter                apr_pool_t *pool)
2872289177Speter{
2873289177Speter  node_revision_t *noderev;
2874289177Speter  apr_off_t my_offset;
2875289177Speter  const svn_fs_id_t *new_id;
2876289177Speter  svn_fs_fs__id_part_t node_id, copy_id, rev_item;
2877289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
2878289177Speter  const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__id_txn_id(id);
2879289177Speter  svn_stream_t *file_stream;
2880289177Speter  svn_checksum_ctx_t *fnv1a_checksum_ctx;
2881289177Speter  apr_pool_t *subpool;
2882289177Speter
2883289177Speter  *new_id_p = NULL;
2884289177Speter
2885289177Speter  /* Check to see if this is a transaction node. */
2886289177Speter  if (! svn_fs_fs__id_is_txn(id))
2887289177Speter    return SVN_NO_ERROR;
2888289177Speter
2889289177Speter  subpool = svn_pool_create(pool);
2890289177Speter  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, subpool));
2891289177Speter
2892289177Speter  if (noderev->kind == svn_node_dir)
2893289177Speter    {
2894289177Speter      apr_array_header_t *entries;
2895289177Speter      int i;
2896289177Speter
2897289177Speter      /* This is a directory.  Write out all the children first. */
2898289177Speter
2899289177Speter      SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool,
2900289177Speter                                          subpool));
2901289177Speter      for (i = 0; i < entries->nelts; ++i)
2902289177Speter        {
2903289177Speter          svn_fs_dirent_t *dirent
2904289177Speter            = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
2905289177Speter
2906289177Speter          svn_pool_clear(subpool);
2907289177Speter          SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
2908289177Speter                                  start_node_id, start_copy_id, initial_offset,
2909289177Speter                                  reps_to_cache, reps_hash, reps_pool, FALSE,
2910289177Speter                                  subpool));
2911289177Speter          if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
2912289177Speter            dirent->id = svn_fs_fs__id_copy(new_id, pool);
2913289177Speter        }
2914289177Speter
2915289177Speter      if (noderev->data_rep && is_txn_rep(noderev->data_rep))
2916289177Speter        {
2917289177Speter          /* Write out the contents of this directory as a text rep. */
2918289177Speter          noderev->data_rep->revision = rev;
2919289177Speter          if (ffd->deltify_directories)
2920289177Speter            SVN_ERR(write_container_delta_rep(noderev->data_rep, file,
2921289177Speter                                              entries,
2922289177Speter                                              write_directory_to_stream,
2923289177Speter                                              fs, noderev, NULL,
2924289177Speter                                              SVN_FS_FS__ITEM_TYPE_DIR_REP,
2925289177Speter                                              pool));
2926289177Speter          else
2927289177Speter            SVN_ERR(write_container_rep(noderev->data_rep, file, entries,
2928289177Speter                                        write_directory_to_stream, fs, NULL,
2929289177Speter                                        SVN_FS_FS__ITEM_TYPE_DIR_REP, pool));
2930289177Speter
2931289177Speter          reset_txn_in_rep(noderev->data_rep);
2932289177Speter        }
2933289177Speter    }
2934289177Speter  else
2935289177Speter    {
2936289177Speter      /* This is a file.  We should make sure the data rep, if it
2937289177Speter         exists in a "this" state, gets rewritten to our new revision
2938289177Speter         num. */
2939289177Speter
2940289177Speter      if (noderev->data_rep && is_txn_rep(noderev->data_rep))
2941289177Speter        {
2942289177Speter          reset_txn_in_rep(noderev->data_rep);
2943289177Speter          noderev->data_rep->revision = rev;
2944289177Speter
2945289177Speter          if (!svn_fs_fs__use_log_addressing(fs))
2946289177Speter            {
2947289177Speter              /* See issue 3845.  Some unknown mechanism caused the
2948289177Speter                 protorev file to get truncated, so check for that
2949289177Speter                 here.  */
2950289177Speter              if (noderev->data_rep->item_index + noderev->data_rep->size
2951289177Speter                  > initial_offset)
2952289177Speter                return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
2953289177Speter                                        _("Truncated protorev file detected"));
2954289177Speter            }
2955289177Speter        }
2956289177Speter    }
2957289177Speter
2958289177Speter  svn_pool_destroy(subpool);
2959289177Speter
2960289177Speter  /* Fix up the property reps. */
2961289177Speter  if (noderev->prop_rep && is_txn_rep(noderev->prop_rep))
2962289177Speter    {
2963289177Speter      apr_hash_t *proplist;
2964289177Speter      apr_uint32_t item_type = noderev->kind == svn_node_dir
2965289177Speter                             ? SVN_FS_FS__ITEM_TYPE_DIR_PROPS
2966289177Speter                             : SVN_FS_FS__ITEM_TYPE_FILE_PROPS;
2967289177Speter      SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
2968289177Speter
2969289177Speter      noderev->prop_rep->revision = rev;
2970289177Speter
2971289177Speter      if (ffd->deltify_properties)
2972289177Speter        SVN_ERR(write_container_delta_rep(noderev->prop_rep, file, proplist,
2973289177Speter                                          write_hash_to_stream, fs, noderev,
2974289177Speter                                          reps_hash, item_type, pool));
2975289177Speter      else
2976289177Speter        SVN_ERR(write_container_rep(noderev->prop_rep, file, proplist,
2977289177Speter                                    write_hash_to_stream, fs, reps_hash,
2978289177Speter                                    item_type, pool));
2979289177Speter
2980289177Speter      reset_txn_in_rep(noderev->prop_rep);
2981289177Speter    }
2982289177Speter
2983289177Speter  /* Convert our temporary ID into a permanent revision one. */
2984289177Speter  node_id = *svn_fs_fs__id_node_id(noderev->id);
2985289177Speter  get_final_id(&node_id, rev, start_node_id, ffd->format);
2986289177Speter  copy_id = *svn_fs_fs__id_copy_id(noderev->id);
2987289177Speter  get_final_id(&copy_id, rev, start_copy_id, ffd->format);
2988289177Speter
2989289177Speter  if (noderev->copyroot_rev == SVN_INVALID_REVNUM)
2990289177Speter    noderev->copyroot_rev = rev;
2991289177Speter
2992289177Speter  /* root nodes have a fixed ID in log addressing mode */
2993289177Speter  SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
2994289177Speter  if (svn_fs_fs__use_log_addressing(fs) && at_root)
2995289177Speter    {
2996289177Speter      /* reference the root noderev from the log-to-phys index */
2997289177Speter      rev_item.number = SVN_FS_FS__ITEM_INDEX_ROOT_NODE;
2998289177Speter      SVN_ERR(store_l2p_index_entry(fs, txn_id, my_offset,
2999289177Speter                                    rev_item.number, pool));
3000289177Speter    }
3001289177Speter  else
3002289177Speter    SVN_ERR(allocate_item_index(&rev_item.number, fs, txn_id,
3003289177Speter                                my_offset, pool));
3004289177Speter
3005289177Speter  rev_item.revision = rev;
3006289177Speter  new_id = svn_fs_fs__id_rev_create(&node_id, &copy_id, &rev_item, pool);
3007289177Speter
3008289177Speter  noderev->id = new_id;
3009289177Speter
3010289177Speter  if (ffd->rep_sharing_allowed)
3011289177Speter    {
3012289177Speter      /* Save the data representation's hash in the rep cache. */
3013289177Speter      if (   noderev->data_rep && noderev->kind == svn_node_file
3014289177Speter          && noderev->data_rep->revision == rev)
3015289177Speter        {
3016289177Speter          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
3017289177Speter          APR_ARRAY_PUSH(reps_to_cache, representation_t *)
3018289177Speter            = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
3019289177Speter        }
3020289177Speter
3021289177Speter      if (noderev->prop_rep && noderev->prop_rep->revision == rev)
3022289177Speter        {
3023289177Speter          /* Add new property reps to hash and on-disk cache. */
3024289177Speter          representation_t *copy
3025289177Speter            = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
3026289177Speter
3027289177Speter          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
3028289177Speter          APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
3029289177Speter
3030289177Speter          apr_hash_set(reps_hash,
3031289177Speter                        copy->sha1_digest,
3032289177Speter                        APR_SHA1_DIGESTSIZE,
3033289177Speter                        copy);
3034289177Speter        }
3035289177Speter    }
3036289177Speter
3037289177Speter  /* don't serialize SHA1 for dirs to disk (waste of space) */
3038289177Speter  if (noderev->data_rep && noderev->kind == svn_node_dir)
3039289177Speter    noderev->data_rep->has_sha1 = FALSE;
3040289177Speter
3041289177Speter  /* don't serialize SHA1 for props to disk (waste of space) */
3042289177Speter  if (noderev->prop_rep)
3043289177Speter    noderev->prop_rep->has_sha1 = FALSE;
3044289177Speter
3045289177Speter  /* Workaround issue #4031: is-fresh-txn-root in revision files. */
3046289177Speter  noderev->is_fresh_txn_root = FALSE;
3047289177Speter
3048289177Speter  /* Write out our new node-revision. */
3049289177Speter  if (at_root)
3050289177Speter    SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
3051289177Speter
3052289177Speter  file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
3053289177Speter                                  svn_stream_from_aprfile2(file, TRUE, pool),
3054289177Speter                                  pool);
3055289177Speter  SVN_ERR(svn_fs_fs__write_noderev(file_stream, noderev, ffd->format,
3056289177Speter                                   svn_fs_fs__fs_supports_mergeinfo(fs),
3057289177Speter                                   pool));
3058289177Speter
3059289177Speter  /* reference the root noderev from the log-to-phys index */
3060289177Speter  if (svn_fs_fs__use_log_addressing(fs))
3061289177Speter    {
3062289177Speter      svn_fs_fs__p2l_entry_t entry;
3063289177Speter      rev_item.revision = SVN_INVALID_REVNUM;
3064289177Speter
3065289177Speter      entry.offset = my_offset;
3066289177Speter      SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
3067289177Speter      entry.size = my_offset - entry.offset;
3068289177Speter      entry.type = SVN_FS_FS__ITEM_TYPE_NODEREV;
3069289177Speter      entry.item = rev_item;
3070289177Speter      SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
3071289177Speter                                      fnv1a_checksum_ctx,
3072289177Speter                                      pool));
3073289177Speter
3074289177Speter      SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool));
3075289177Speter    }
3076289177Speter
3077289177Speter  /* Return our ID that references the revision file. */
3078289177Speter  *new_id_p = noderev->id;
3079289177Speter
3080289177Speter  return SVN_NO_ERROR;
3081289177Speter}
3082289177Speter
3083289177Speter/* Write the changed path info CHANGED_PATHS from transaction TXN_ID to the
3084289177Speter   permanent rev-file FILE in filesystem FS.  *OFFSET_P is set the to offset
3085289177Speter   in the file of the beginning of this information.  Perform temporary
3086289177Speter   allocations in POOL. */
3087289177Speterstatic svn_error_t *
3088289177Speterwrite_final_changed_path_info(apr_off_t *offset_p,
3089289177Speter                              apr_file_t *file,
3090289177Speter                              svn_fs_t *fs,
3091289177Speter                              const svn_fs_fs__id_part_t *txn_id,
3092289177Speter                              apr_hash_t *changed_paths,
3093289177Speter                              apr_pool_t *pool)
3094289177Speter{
3095289177Speter  apr_off_t offset;
3096289177Speter  svn_stream_t *stream;
3097289177Speter  svn_checksum_ctx_t *fnv1a_checksum_ctx;
3098289177Speter
3099289177Speter  SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
3100289177Speter
3101289177Speter  /* write to target file & calculate checksum */
3102289177Speter  stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
3103289177Speter                             svn_stream_from_aprfile2(file, TRUE, pool),
3104289177Speter                             pool);
3105289177Speter  SVN_ERR(svn_fs_fs__write_changes(stream, fs, changed_paths, TRUE, pool));
3106289177Speter
3107289177Speter  *offset_p = offset;
3108289177Speter
3109289177Speter  /* reference changes from the indexes */
3110289177Speter  if (svn_fs_fs__use_log_addressing(fs))
3111289177Speter    {
3112289177Speter      svn_fs_fs__p2l_entry_t entry;
3113289177Speter
3114289177Speter      entry.offset = offset;
3115289177Speter      SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
3116289177Speter      entry.size = offset - entry.offset;
3117289177Speter      entry.type = SVN_FS_FS__ITEM_TYPE_CHANGES;
3118289177Speter      entry.item.revision = SVN_INVALID_REVNUM;
3119289177Speter      entry.item.number = SVN_FS_FS__ITEM_INDEX_CHANGES;
3120289177Speter      SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
3121289177Speter                                      fnv1a_checksum_ctx,
3122289177Speter                                      pool));
3123289177Speter
3124289177Speter      SVN_ERR(store_p2l_index_entry(fs, txn_id, &entry, pool));
3125289177Speter      SVN_ERR(store_l2p_index_entry(fs, txn_id, entry.offset,
3126289177Speter                                    SVN_FS_FS__ITEM_INDEX_CHANGES, pool));
3127289177Speter    }
3128289177Speter
3129289177Speter  return SVN_NO_ERROR;
3130289177Speter}
3131289177Speter
3132289177Speter/* Open a new svn_fs_t handle to FS, set that handle's concept of "current
3133289177Speter   youngest revision" to NEW_REV, and call svn_fs_fs__verify_root() on
3134289177Speter   NEW_REV's revision root.
3135289177Speter
3136289177Speter   Intended to be called as the very last step in a commit before 'current'
3137289177Speter   is bumped.  This implies that we are holding the write lock. */
3138289177Speterstatic svn_error_t *
3139289177Speterverify_as_revision_before_current_plus_plus(svn_fs_t *fs,
3140289177Speter                                            svn_revnum_t new_rev,
3141289177Speter                                            apr_pool_t *pool)
3142289177Speter{
3143289177Speter#ifdef SVN_DEBUG
3144289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
3145289177Speter  svn_fs_t *ft; /* fs++ == ft */
3146289177Speter  svn_fs_root_t *root;
3147289177Speter  fs_fs_data_t *ft_ffd;
3148289177Speter  apr_hash_t *fs_config;
3149289177Speter
3150289177Speter  SVN_ERR_ASSERT(ffd->svn_fs_open_);
3151289177Speter
3152289177Speter  /* make sure FT does not simply return data cached by other instances
3153289177Speter   * but actually retrieves it from disk at least once.
3154289177Speter   */
3155289177Speter  fs_config = apr_hash_make(pool);
3156289177Speter  svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
3157289177Speter                           svn_uuid_generate(pool));
3158289177Speter  SVN_ERR(ffd->svn_fs_open_(&ft, fs->path,
3159289177Speter                            fs_config,
3160289177Speter                            pool,
3161289177Speter                            pool));
3162289177Speter  ft_ffd = ft->fsap_data;
3163289177Speter  /* Don't let FT consult rep-cache.db, either. */
3164289177Speter  ft_ffd->rep_sharing_allowed = FALSE;
3165289177Speter
3166289177Speter  /* Time travel! */
3167289177Speter  ft_ffd->youngest_rev_cache = new_rev;
3168289177Speter
3169289177Speter  SVN_ERR(svn_fs_fs__revision_root(&root, ft, new_rev, pool));
3170289177Speter  SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
3171289177Speter  SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
3172289177Speter  SVN_ERR(svn_fs_fs__verify_root(root, pool));
3173289177Speter#endif /* SVN_DEBUG */
3174289177Speter
3175289177Speter  return SVN_NO_ERROR;
3176289177Speter}
3177289177Speter
3178289177Speter/* Update the 'current' file to hold the correct next node and copy_ids
3179289177Speter   from transaction TXN_ID in filesystem FS.  The current revision is
3180289177Speter   set to REV.  Perform temporary allocations in POOL. */
3181289177Speterstatic svn_error_t *
3182289177Speterwrite_final_current(svn_fs_t *fs,
3183289177Speter                    const svn_fs_fs__id_part_t *txn_id,
3184289177Speter                    svn_revnum_t rev,
3185289177Speter                    apr_uint64_t start_node_id,
3186289177Speter                    apr_uint64_t start_copy_id,
3187289177Speter                    apr_pool_t *pool)
3188289177Speter{
3189289177Speter  apr_uint64_t txn_node_id;
3190289177Speter  apr_uint64_t txn_copy_id;
3191289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
3192289177Speter
3193289177Speter  if (ffd->format >= SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
3194289177Speter    return svn_fs_fs__write_current(fs, rev, 0, 0, pool);
3195289177Speter
3196289177Speter  /* To find the next available ids, we add the id that used to be in
3197289177Speter     the 'current' file, to the next ids from the transaction file. */
3198289177Speter  SVN_ERR(read_next_ids(&txn_node_id, &txn_copy_id, fs, txn_id, pool));
3199289177Speter
3200289177Speter  start_node_id += txn_node_id;
3201289177Speter  start_copy_id += txn_copy_id;
3202289177Speter
3203289177Speter  return svn_fs_fs__write_current(fs, rev, start_node_id, start_copy_id,
3204289177Speter                                  pool);
3205289177Speter}
3206289177Speter
3207289177Speter/* Verify that the user registered with FS has all the locks necessary to
3208289177Speter   permit all the changes associated with TXN_NAME.
3209289177Speter   The FS write lock is assumed to be held by the caller. */
3210289177Speterstatic svn_error_t *
3211289177Speterverify_locks(svn_fs_t *fs,
3212289177Speter             const svn_fs_fs__id_part_t *txn_id,
3213289177Speter             apr_hash_t *changed_paths,
3214289177Speter             apr_pool_t *pool)
3215289177Speter{
3216289177Speter  apr_pool_t *iterpool;
3217289177Speter  apr_array_header_t *changed_paths_sorted;
3218289177Speter  svn_stringbuf_t *last_recursed = NULL;
3219289177Speter  int i;
3220289177Speter
3221289177Speter  /* Make an array of the changed paths, and sort them depth-first-ily.  */
3222289177Speter  changed_paths_sorted = svn_sort__hash(changed_paths,
3223289177Speter                                        svn_sort_compare_items_as_paths,
3224289177Speter                                        pool);
3225289177Speter
3226289177Speter  /* Now, traverse the array of changed paths, verify locks.  Note
3227289177Speter     that if we need to do a recursive verification a path, we'll skip
3228289177Speter     over children of that path when we get to them. */
3229289177Speter  iterpool = svn_pool_create(pool);
3230289177Speter  for (i = 0; i < changed_paths_sorted->nelts; i++)
3231289177Speter    {
3232289177Speter      const svn_sort__item_t *item;
3233289177Speter      const char *path;
3234289177Speter      svn_fs_path_change2_t *change;
3235289177Speter      svn_boolean_t recurse = TRUE;
3236289177Speter
3237289177Speter      svn_pool_clear(iterpool);
3238289177Speter
3239289177Speter      item = &APR_ARRAY_IDX(changed_paths_sorted, i, svn_sort__item_t);
3240289177Speter
3241289177Speter      /* Fetch the change associated with our path.  */
3242289177Speter      path = item->key;
3243289177Speter      change = item->value;
3244289177Speter
3245289177Speter      /* If this path has already been verified as part of a recursive
3246289177Speter         check of one of its parents, no need to do it again.  */
3247289177Speter      if (last_recursed
3248289177Speter          && svn_fspath__skip_ancestor(last_recursed->data, path))
3249289177Speter        continue;
3250289177Speter
3251289177Speter      /* What does it mean to succeed at lock verification for a given
3252289177Speter         path?  For an existing file or directory getting modified
3253289177Speter         (text, props), it means we hold the lock on the file or
3254289177Speter         directory.  For paths being added or removed, we need to hold
3255289177Speter         the locks for that path and any children of that path.
3256289177Speter
3257289177Speter         WHEW!  We have no reliable way to determine the node kind
3258289177Speter         of deleted items, but fortunately we are going to do a
3259289177Speter         recursive check on deleted paths regardless of their kind.  */
3260289177Speter      if (change->change_kind == svn_fs_path_change_modify)
3261289177Speter        recurse = FALSE;
3262289177Speter      SVN_ERR(svn_fs_fs__allow_locked_operation(path, fs, recurse, TRUE,
3263289177Speter                                                iterpool));
3264289177Speter
3265289177Speter      /* If we just did a recursive check, remember the path we
3266289177Speter         checked (so children can be skipped).  */
3267289177Speter      if (recurse)
3268289177Speter        {
3269289177Speter          if (! last_recursed)
3270289177Speter            last_recursed = svn_stringbuf_create(path, pool);
3271289177Speter          else
3272289177Speter            svn_stringbuf_set(last_recursed, path);
3273289177Speter        }
3274289177Speter    }
3275289177Speter  svn_pool_destroy(iterpool);
3276289177Speter  return SVN_NO_ERROR;
3277289177Speter}
3278289177Speter
3279289177Speter/* Return in *PATH the path to a file containing the properties that
3280289177Speter   make up the final revision properties file.  This involves setting
3281289177Speter   svn:date and removing any temporary properties associated with the
3282289177Speter   commit flags. */
3283289177Speterstatic svn_error_t *
3284289177Speterwrite_final_revprop(const char **path,
3285289177Speter                    svn_fs_txn_t *txn,
3286289177Speter                    const svn_fs_fs__id_part_t *txn_id,
3287289177Speter                    apr_pool_t *pool)
3288289177Speter{
3289289177Speter  apr_hash_t *txnprops;
3290289177Speter  svn_boolean_t final_mods = FALSE;
3291289177Speter  svn_string_t date;
3292289177Speter  svn_string_t *client_date;
3293289177Speter
3294289177Speter  SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, txn, pool));
3295289177Speter
3296289177Speter  /* Remove any temporary txn props representing 'flags'. */
3297289177Speter  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
3298289177Speter    {
3299289177Speter      svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL);
3300289177Speter      final_mods = TRUE;
3301289177Speter    }
3302289177Speter
3303289177Speter  if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
3304289177Speter    {
3305289177Speter      svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL);
3306289177Speter      final_mods = TRUE;
3307289177Speter    }
3308289177Speter
3309289177Speter  client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE);
3310289177Speter  if (client_date)
3311289177Speter    {
3312289177Speter      svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE, NULL);
3313289177Speter      final_mods = TRUE;
3314289177Speter    }
3315289177Speter
3316289177Speter  /* Update commit time to ensure that svn:date revprops remain ordered if
3317289177Speter     requested. */
3318289177Speter  if (!client_date || strcmp(client_date->data, "1"))
3319289177Speter    {
3320289177Speter      date.data = svn_time_to_cstring(apr_time_now(), pool);
3321289177Speter      date.len = strlen(date.data);
3322289177Speter      svn_hash_sets(txnprops, SVN_PROP_REVISION_DATE, &date);
3323289177Speter      final_mods = TRUE;
3324289177Speter    }
3325289177Speter
3326289177Speter  if (final_mods)
3327289177Speter    {
3328289177Speter      SVN_ERR(set_txn_proplist(txn->fs, txn_id, txnprops, TRUE, pool));
3329289177Speter      *path = path_txn_props_final(txn->fs, txn_id, pool);
3330289177Speter    }
3331289177Speter  else
3332289177Speter    {
3333289177Speter      *path = path_txn_props(txn->fs, txn_id, pool);
3334289177Speter    }
3335289177Speter
3336289177Speter  return SVN_NO_ERROR;
3337289177Speter}
3338289177Speter
3339289177Spetersvn_error_t *
3340289177Spetersvn_fs_fs__add_index_data(svn_fs_t *fs,
3341289177Speter                          apr_file_t *file,
3342289177Speter                          const char *l2p_proto_index,
3343289177Speter                          const char *p2l_proto_index,
3344289177Speter                          svn_revnum_t revision,
3345289177Speter                          apr_pool_t *pool)
3346289177Speter{
3347289177Speter  apr_off_t l2p_offset;
3348289177Speter  apr_off_t p2l_offset;
3349289177Speter  svn_stringbuf_t *footer;
3350289177Speter  unsigned char footer_length;
3351289177Speter  svn_checksum_t *l2p_checksum;
3352289177Speter  svn_checksum_t *p2l_checksum;
3353289177Speter
3354289177Speter  /* Append the actual index data to the pack file. */
3355289177Speter  l2p_offset = 0;
3356289177Speter  SVN_ERR(svn_io_file_seek(file, APR_END, &l2p_offset, pool));
3357289177Speter  SVN_ERR(svn_fs_fs__l2p_index_append(&l2p_checksum, fs, file,
3358289177Speter                                      l2p_proto_index, revision,
3359289177Speter                                      pool, pool));
3360289177Speter
3361289177Speter  p2l_offset = 0;
3362289177Speter  SVN_ERR(svn_io_file_seek(file, APR_END, &p2l_offset, pool));
3363289177Speter  SVN_ERR(svn_fs_fs__p2l_index_append(&p2l_checksum, fs, file,
3364289177Speter                                      p2l_proto_index, revision,
3365289177Speter                                      pool, pool));
3366289177Speter
3367289177Speter  /* Append footer. */
3368289177Speter  footer = svn_fs_fs__unparse_footer(l2p_offset, l2p_checksum,
3369289177Speter                                     p2l_offset, p2l_checksum, pool, pool);
3370289177Speter  SVN_ERR(svn_io_file_write_full(file, footer->data, footer->len, NULL,
3371289177Speter                                 pool));
3372289177Speter
3373289177Speter  footer_length = footer->len;
3374289177Speter  SVN_ERR_ASSERT(footer_length == footer->len);
3375289177Speter  SVN_ERR(svn_io_file_write_full(file, &footer_length, 1, NULL, pool));
3376289177Speter
3377289177Speter  return SVN_NO_ERROR;
3378289177Speter}
3379289177Speter
3380289177Speter/* Baton used for commit_body below. */
3381289177Speterstruct commit_baton {
3382289177Speter  svn_revnum_t *new_rev_p;
3383289177Speter  svn_fs_t *fs;
3384289177Speter  svn_fs_txn_t *txn;
3385289177Speter  apr_array_header_t *reps_to_cache;
3386289177Speter  apr_hash_t *reps_hash;
3387289177Speter  apr_pool_t *reps_pool;
3388289177Speter};
3389289177Speter
3390289177Speter/* The work-horse for svn_fs_fs__commit, called with the FS write lock.
3391289177Speter   This implements the svn_fs_fs__with_write_lock() 'body' callback
3392289177Speter   type.  BATON is a 'struct commit_baton *'. */
3393289177Speterstatic svn_error_t *
3394289177Spetercommit_body(void *baton, apr_pool_t *pool)
3395289177Speter{
3396289177Speter  struct commit_baton *cb = baton;
3397289177Speter  fs_fs_data_t *ffd = cb->fs->fsap_data;
3398289177Speter  const char *old_rev_filename, *rev_filename, *proto_filename;
3399289177Speter  const char *revprop_filename, *final_revprop;
3400289177Speter  const svn_fs_id_t *root_id, *new_root_id;
3401289177Speter  apr_uint64_t start_node_id;
3402289177Speter  apr_uint64_t start_copy_id;
3403289177Speter  svn_revnum_t old_rev, new_rev;
3404289177Speter  apr_file_t *proto_file;
3405289177Speter  void *proto_file_lockcookie;
3406289177Speter  apr_off_t initial_offset, changed_path_offset;
3407289177Speter  const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__txn_get_id(cb->txn);
3408289177Speter  apr_hash_t *changed_paths;
3409289177Speter
3410289177Speter  /* Re-Read the current repository format.  All our repo upgrade and
3411289177Speter     config evaluation strategies are such that existing information in
3412289177Speter     FS and FFD remains valid.
3413289177Speter
3414289177Speter     Although we don't recommend upgrading hot repositories, people may
3415289177Speter     still do it and we must make sure to either handle them gracefully
3416289177Speter     or to error out.
3417289177Speter
3418289177Speter     Committing pre-format 3 txns will fail after upgrade to format 3+
3419289177Speter     because the proto-rev cannot be found; no further action needed.
3420289177Speter     Upgrades from pre-f7 to f7+ means a potential change in addressing
3421289177Speter     mode for the final rev.  We must be sure to detect that cause because
3422289177Speter     the failure would only manifest once the new revision got committed.
3423289177Speter   */
3424289177Speter  SVN_ERR(svn_fs_fs__read_format_file(cb->fs, pool));
3425289177Speter
3426289177Speter  /* Read the current youngest revision and, possibly, the next available
3427289177Speter     node id and copy id (for old format filesystems).  Update the cached
3428289177Speter     value for the youngest revision, because we have just checked it. */
3429289177Speter  SVN_ERR(svn_fs_fs__read_current(&old_rev, &start_node_id, &start_copy_id,
3430289177Speter                                  cb->fs, pool));
3431289177Speter  ffd->youngest_rev_cache = old_rev;
3432289177Speter
3433289177Speter  /* Check to make sure this transaction is based off the most recent
3434289177Speter     revision. */
3435289177Speter  if (cb->txn->base_rev != old_rev)
3436289177Speter    return svn_error_create(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
3437289177Speter                            _("Transaction out of date"));
3438289177Speter
3439289177Speter  /* We need the changes list for verification as well as for writing it
3440289177Speter     to the final rev file. */
3441289177Speter  SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, cb->fs, txn_id,
3442289177Speter                                       pool));
3443289177Speter
3444289177Speter  /* Locks may have been added (or stolen) between the calling of
3445289177Speter     previous svn_fs.h functions and svn_fs_commit_txn(), so we need
3446289177Speter     to re-examine every changed-path in the txn and re-verify all
3447289177Speter     discovered locks. */
3448289177Speter  SVN_ERR(verify_locks(cb->fs, txn_id, changed_paths, pool));
3449289177Speter
3450289177Speter  /* We are going to be one better than this puny old revision. */
3451289177Speter  new_rev = old_rev + 1;
3452289177Speter
3453289177Speter  /* Get a write handle on the proto revision file. */
3454289177Speter  SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
3455289177Speter                                 cb->fs, txn_id, pool));
3456289177Speter  SVN_ERR(svn_fs_fs__get_file_offset(&initial_offset, proto_file, pool));
3457289177Speter
3458289177Speter  /* Write out all the node-revisions and directory contents. */
3459289177Speter  root_id = svn_fs_fs__id_txn_create_root(txn_id, pool);
3460289177Speter  SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
3461289177Speter                          start_node_id, start_copy_id, initial_offset,
3462289177Speter                          cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
3463289177Speter                          TRUE, pool));
3464289177Speter
3465289177Speter  /* Write the changed-path information. */
3466289177Speter  SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
3467289177Speter                                        cb->fs, txn_id, changed_paths,
3468289177Speter                                        pool));
3469289177Speter
3470289177Speter  if (svn_fs_fs__use_log_addressing(cb->fs))
3471289177Speter    {
3472289177Speter      /* Append the index data to the rev file. */
3473289177Speter      SVN_ERR(svn_fs_fs__add_index_data(cb->fs, proto_file,
3474289177Speter                      svn_fs_fs__path_l2p_proto_index(cb->fs, txn_id, pool),
3475289177Speter                      svn_fs_fs__path_p2l_proto_index(cb->fs, txn_id, pool),
3476289177Speter                      new_rev, pool));
3477289177Speter    }
3478289177Speter  else
3479289177Speter    {
3480289177Speter      /* Write the final line. */
3481289177Speter
3482289177Speter      svn_stringbuf_t *trailer
3483289177Speter        = svn_fs_fs__unparse_revision_trailer
3484289177Speter                  ((apr_off_t)svn_fs_fs__id_item(new_root_id),
3485289177Speter                   changed_path_offset,
3486289177Speter                   pool);
3487289177Speter      SVN_ERR(svn_io_file_write_full(proto_file, trailer->data, trailer->len,
3488289177Speter                                     NULL, pool));
3489289177Speter    }
3490289177Speter
3491289177Speter  SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
3492289177Speter  SVN_ERR(svn_io_file_close(proto_file, pool));
3493289177Speter
3494289177Speter  /* We don't unlock the prototype revision file immediately to avoid a
3495289177Speter     race with another caller writing to the prototype revision file
3496289177Speter     before we commit it. */
3497289177Speter
3498289177Speter  /* Create the shard for the rev and revprop file, if we're sharding and
3499289177Speter     this is the first revision of a new shard.  We don't care if this
3500289177Speter     fails because the shard already existed for some reason. */
3501289177Speter  if (ffd->max_files_per_dir && new_rev % ffd->max_files_per_dir == 0)
3502289177Speter    {
3503289177Speter      /* Create the revs shard. */
3504289177Speter        {
3505289177Speter          const char *new_dir
3506289177Speter            = svn_fs_fs__path_rev_shard(cb->fs, new_rev, pool);
3507289177Speter          svn_error_t *err
3508289177Speter            = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
3509289177Speter          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3510289177Speter            return svn_error_trace(err);
3511289177Speter          svn_error_clear(err);
3512289177Speter          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3513289177Speter                                                    PATH_REVS_DIR,
3514289177Speter                                                    pool),
3515289177Speter                                    new_dir, pool));
3516289177Speter        }
3517289177Speter
3518289177Speter      /* Create the revprops shard. */
3519289177Speter      SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev));
3520289177Speter        {
3521289177Speter          const char *new_dir
3522289177Speter            = svn_fs_fs__path_revprops_shard(cb->fs, new_rev, pool);
3523289177Speter          svn_error_t *err
3524289177Speter            = svn_io_dir_make(new_dir, APR_OS_DEFAULT, pool);
3525289177Speter          if (err && !APR_STATUS_IS_EEXIST(err->apr_err))
3526289177Speter            return svn_error_trace(err);
3527289177Speter          svn_error_clear(err);
3528289177Speter          SVN_ERR(svn_io_copy_perms(svn_dirent_join(cb->fs->path,
3529289177Speter                                                    PATH_REVPROPS_DIR,
3530289177Speter                                                    pool),
3531289177Speter                                    new_dir, pool));
3532289177Speter        }
3533289177Speter    }
3534289177Speter
3535289177Speter  /* Move the finished rev file into place.
3536289177Speter
3537289177Speter     ### This "breaks" the transaction by removing the protorev file
3538289177Speter     ### but the revision is not yet complete.  If this commit does
3539289177Speter     ### not complete for any reason the transaction will be lost. */
3540289177Speter  old_rev_filename = svn_fs_fs__path_rev_absolute(cb->fs, old_rev, pool);
3541289177Speter  rev_filename = svn_fs_fs__path_rev(cb->fs, new_rev, pool);
3542289177Speter  proto_filename = svn_fs_fs__path_txn_proto_rev(cb->fs, txn_id, pool);
3543289177Speter  SVN_ERR(svn_fs_fs__move_into_place(proto_filename, rev_filename,
3544289177Speter                                     old_rev_filename, pool));
3545289177Speter
3546289177Speter  /* Now that we've moved the prototype revision file out of the way,
3547289177Speter     we can unlock it (since further attempts to write to the file
3548289177Speter     will fail as it no longer exists).  We must do this so that we can
3549289177Speter     remove the transaction directory later. */
3550289177Speter  SVN_ERR(unlock_proto_rev(cb->fs, txn_id, proto_file_lockcookie, pool));
3551289177Speter
3552289177Speter  /* Move the revprops file into place. */
3553289177Speter  SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev));
3554289177Speter  SVN_ERR(write_final_revprop(&revprop_filename, cb->txn, txn_id, pool));
3555289177Speter  final_revprop = svn_fs_fs__path_revprops(cb->fs, new_rev, pool);
3556289177Speter  SVN_ERR(svn_fs_fs__move_into_place(revprop_filename, final_revprop,
3557289177Speter                                     old_rev_filename, pool));
3558289177Speter
3559289177Speter  /* Update the 'current' file. */
3560289177Speter  SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
3561289177Speter  SVN_ERR(write_final_current(cb->fs, txn_id, new_rev, start_node_id,
3562289177Speter                              start_copy_id, pool));
3563289177Speter
3564289177Speter  /* At this point the new revision is committed and globally visible
3565289177Speter     so let the caller know it succeeded by giving it the new revision
3566289177Speter     number, which fulfills svn_fs_commit_txn() contract.  Any errors
3567289177Speter     after this point do not change the fact that a new revision was
3568289177Speter     created. */
3569289177Speter  *cb->new_rev_p = new_rev;
3570289177Speter
3571289177Speter  ffd->youngest_rev_cache = new_rev;
3572289177Speter
3573289177Speter  /* Remove this transaction directory. */
3574289177Speter  SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
3575289177Speter
3576289177Speter  return SVN_NO_ERROR;
3577289177Speter}
3578289177Speter
3579289177Speter/* Add the representations in REPS_TO_CACHE (an array of representation_t *)
3580289177Speter * to the rep-cache database of FS. */
3581289177Speterstatic svn_error_t *
3582289177Speterwrite_reps_to_cache(svn_fs_t *fs,
3583289177Speter                    const apr_array_header_t *reps_to_cache,
3584289177Speter                    apr_pool_t *scratch_pool)
3585289177Speter{
3586289177Speter  int i;
3587289177Speter
3588289177Speter  for (i = 0; i < reps_to_cache->nelts; i++)
3589289177Speter    {
3590289177Speter      representation_t *rep = APR_ARRAY_IDX(reps_to_cache, i, representation_t *);
3591289177Speter
3592289177Speter      SVN_ERR(svn_fs_fs__set_rep_reference(fs, rep, scratch_pool));
3593289177Speter    }
3594289177Speter
3595289177Speter  return SVN_NO_ERROR;
3596289177Speter}
3597289177Speter
3598289177Spetersvn_error_t *
3599289177Spetersvn_fs_fs__commit(svn_revnum_t *new_rev_p,
3600289177Speter                  svn_fs_t *fs,
3601289177Speter                  svn_fs_txn_t *txn,
3602289177Speter                  apr_pool_t *pool)
3603289177Speter{
3604289177Speter  struct commit_baton cb;
3605289177Speter  fs_fs_data_t *ffd = fs->fsap_data;
3606289177Speter
3607289177Speter  cb.new_rev_p = new_rev_p;
3608289177Speter  cb.fs = fs;
3609289177Speter  cb.txn = txn;
3610289177Speter
3611289177Speter  if (ffd->rep_sharing_allowed)
3612289177Speter    {
3613289177Speter      cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
3614289177Speter      cb.reps_hash = apr_hash_make(pool);
3615289177Speter      cb.reps_pool = pool;
3616289177Speter    }
3617289177Speter  else
3618289177Speter    {
3619289177Speter      cb.reps_to_cache = NULL;
3620289177Speter      cb.reps_hash = NULL;
3621289177Speter      cb.reps_pool = NULL;
3622289177Speter    }
3623289177Speter
3624289177Speter  SVN_ERR(svn_fs_fs__with_write_lock(fs, commit_body, &cb, pool));
3625289177Speter
3626289177Speter  /* At this point, *NEW_REV_P has been set, so errors below won't affect
3627289177Speter     the success of the commit.  (See svn_fs_commit_txn().)  */
3628289177Speter
3629289177Speter  if (ffd->rep_sharing_allowed)
3630289177Speter    {
3631309512Speter      svn_error_t *err;
3632309512Speter
3633289177Speter      SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
3634289177Speter
3635289177Speter      /* Write new entries to the rep-sharing database.
3636289177Speter       *
3637289177Speter       * We use an sqlite transaction to speed things up;
3638289177Speter       * see <http://www.sqlite.org/faq.html#q19>.
3639289177Speter       */
3640289177Speter      /* ### A commit that touches thousands of files will starve other
3641289177Speter             (reader/writer) commits for the duration of the below call.
3642289177Speter             Maybe write in batches? */
3643309512Speter      SVN_ERR(svn_sqlite__begin_transaction(ffd->rep_cache_db));
3644309512Speter      err = write_reps_to_cache(fs, cb.reps_to_cache, pool);
3645309512Speter      err = svn_sqlite__finish_transaction(ffd->rep_cache_db, err);
3646309512Speter
3647309512Speter      if (svn_error_find_cause(err, SVN_SQLITE__ERR_ROLLBACK_FAILED))
3648309512Speter        {
3649309512Speter          /* Failed rollback means that our db connection is unusable, and
3650309512Speter             the only thing we can do is close it.  The connection will be
3651309512Speter             reopened during the next operation with rep-cache.db. */
3652309512Speter          return svn_error_trace(
3653309512Speter              svn_error_compose_create(err,
3654309512Speter                                       svn_fs_fs__close_rep_cache(fs)));
3655309512Speter        }
3656309512Speter      else if (err)
3657309512Speter        return svn_error_trace(err);
3658289177Speter    }
3659289177Speter
3660289177Speter  return SVN_NO_ERROR;
3661289177Speter}
3662289177Speter
3663289177Speter
3664289177Spetersvn_error_t *
3665289177Spetersvn_fs_fs__list_transactions(apr_array_header_t **names_p,
3666289177Speter                             svn_fs_t *fs,
3667289177Speter                             apr_pool_t *pool)
3668289177Speter{
3669289177Speter  const char *txn_dir;
3670289177Speter  apr_hash_t *dirents;
3671289177Speter  apr_hash_index_t *hi;
3672289177Speter  apr_array_header_t *names;
3673289177Speter  apr_size_t ext_len = strlen(PATH_EXT_TXN);
3674289177Speter
3675289177Speter  names = apr_array_make(pool, 1, sizeof(const char *));
3676289177Speter
3677289177Speter  /* Get the transactions directory. */
3678289177Speter  txn_dir = svn_fs_fs__path_txns_dir(fs, pool);
3679289177Speter
3680289177Speter  /* Now find a listing of this directory. */
3681289177Speter  SVN_ERR(svn_io_get_dirents3(&dirents, txn_dir, TRUE, pool, pool));
3682289177Speter
3683289177Speter  /* Loop through all the entries and return anything that ends with '.txn'. */
3684289177Speter  for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
3685289177Speter    {
3686289177Speter      const char *name = apr_hash_this_key(hi);
3687289177Speter      apr_ssize_t klen = apr_hash_this_key_len(hi);
3688289177Speter      const char *id;
3689289177Speter
3690289177Speter      /* The name must end with ".txn" to be considered a transaction. */
3691289177Speter      if ((apr_size_t) klen <= ext_len
3692289177Speter          || (strcmp(name + klen - ext_len, PATH_EXT_TXN)) != 0)
3693289177Speter        continue;
3694289177Speter
3695289177Speter      /* Truncate the ".txn" extension and store the ID. */
3696289177Speter      id = apr_pstrndup(pool, name, strlen(name) - ext_len);
3697289177Speter      APR_ARRAY_PUSH(names, const char *) = id;
3698289177Speter    }
3699289177Speter
3700289177Speter  *names_p = names;
3701289177Speter
3702289177Speter  return SVN_NO_ERROR;
3703289177Speter}
3704289177Speter
3705289177Spetersvn_error_t *
3706289177Spetersvn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
3707289177Speter                    svn_fs_t *fs,
3708289177Speter                    const char *name,
3709289177Speter                    apr_pool_t *pool)
3710289177Speter{
3711289177Speter  svn_fs_txn_t *txn;
3712289177Speter  fs_txn_data_t *ftd;
3713289177Speter  svn_node_kind_t kind;
3714289177Speter  transaction_t *local_txn;
3715289177Speter  svn_fs_fs__id_part_t txn_id;
3716289177Speter
3717289177Speter  SVN_ERR(svn_fs_fs__id_txn_parse(&txn_id, name));
3718289177Speter
3719289177Speter  /* First check to see if the directory exists. */
3720289177Speter  SVN_ERR(svn_io_check_path(svn_fs_fs__path_txn_dir(fs, &txn_id, pool),
3721289177Speter                            &kind, pool));
3722289177Speter
3723289177Speter  /* Did we find it? */
3724289177Speter  if (kind != svn_node_dir)
3725289177Speter    return svn_error_createf(SVN_ERR_FS_NO_SUCH_TRANSACTION, NULL,
3726289177Speter                             _("No such transaction '%s'"),
3727289177Speter                             name);
3728289177Speter
3729289177Speter  txn = apr_pcalloc(pool, sizeof(*txn));
3730289177Speter  ftd = apr_pcalloc(pool, sizeof(*ftd));
3731289177Speter  ftd->txn_id = txn_id;
3732289177Speter
3733289177Speter  /* Read in the root node of this transaction. */
3734289177Speter  txn->id = apr_pstrdup(pool, name);
3735289177Speter  txn->fs = fs;
3736289177Speter
3737289177Speter  SVN_ERR(svn_fs_fs__get_txn(&local_txn, fs, &txn_id, pool));
3738289177Speter
3739289177Speter  txn->base_rev = svn_fs_fs__id_rev(local_txn->base_id);
3740289177Speter
3741289177Speter  txn->vtable = &txn_vtable;
3742289177Speter  txn->fsap_data = ftd;
3743289177Speter  *txn_p = txn;
3744289177Speter
3745289177Speter  return SVN_NO_ERROR;
3746289177Speter}
3747289177Speter
3748289177Spetersvn_error_t *
3749289177Spetersvn_fs_fs__txn_proplist(apr_hash_t **table_p,
3750289177Speter                        svn_fs_txn_t *txn,
3751289177Speter                        apr_pool_t *pool)
3752289177Speter{
3753289177Speter  apr_hash_t *proplist = apr_hash_make(pool);
3754289177Speter  SVN_ERR(get_txn_proplist(proplist, txn->fs, svn_fs_fs__txn_get_id(txn),
3755289177Speter                           pool));
3756289177Speter  *table_p = proplist;
3757289177Speter
3758289177Speter  return SVN_NO_ERROR;
3759289177Speter}
3760289177Speter
3761289177Speter
3762289177Spetersvn_error_t *
3763289177Spetersvn_fs_fs__delete_node_revision(svn_fs_t *fs,
3764289177Speter                                const svn_fs_id_t *id,
3765289177Speter                                apr_pool_t *pool)
3766289177Speter{
3767289177Speter  node_revision_t *noderev;
3768289177Speter
3769289177Speter  SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, id, pool, pool));
3770289177Speter
3771289177Speter  /* Delete any mutable property representation. */
3772289177Speter  if (noderev->prop_rep && is_txn_rep(noderev->prop_rep))
3773289177Speter    SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_props(fs, id, pool),
3774289177Speter                                FALSE, pool));
3775289177Speter
3776289177Speter  /* Delete any mutable data representation. */
3777289177Speter  if (noderev->data_rep && is_txn_rep(noderev->data_rep)
3778289177Speter      && noderev->kind == svn_node_dir)
3779289177Speter    {
3780289177Speter      fs_fs_data_t *ffd = fs->fsap_data;
3781289177Speter      SVN_ERR(svn_io_remove_file2(svn_fs_fs__path_txn_node_children(fs, id,
3782289177Speter                                                                    pool),
3783289177Speter                                  FALSE, pool));
3784289177Speter
3785289177Speter      /* remove the corresponding entry from the cache, if such exists */
3786289177Speter      if (ffd->txn_dir_cache)
3787289177Speter        {
3788289177Speter          const char *key = svn_fs_fs__id_unparse(id, pool)->data;
3789289177Speter          SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL, pool));
3790289177Speter        }
3791289177Speter    }
3792289177Speter
3793289177Speter  return svn_io_remove_file2(svn_fs_fs__path_txn_node_rev(fs, id, pool),
3794289177Speter                             FALSE, pool);
3795289177Speter}
3796289177Speter
3797289177Speter
3798289177Speter
3799289177Speter/*** Transactions ***/
3800289177Speter
3801289177Spetersvn_error_t *
3802289177Spetersvn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
3803289177Speter                       const svn_fs_id_t **base_root_id_p,
3804289177Speter                       svn_fs_t *fs,
3805289177Speter                       const svn_fs_fs__id_part_t *txn_id,
3806289177Speter                       apr_pool_t *pool)
3807289177Speter{
3808289177Speter  transaction_t *txn;
3809289177Speter  SVN_ERR(svn_fs_fs__get_txn(&txn, fs, txn_id, pool));
3810289177Speter  *root_id_p = txn->root_id;
3811289177Speter  *base_root_id_p = txn->base_id;
3812289177Speter  return SVN_NO_ERROR;
3813289177Speter}
3814289177Speter
3815289177Speter
3816289177Speter/* Generic transaction operations.  */
3817289177Speter
3818289177Spetersvn_error_t *
3819289177Spetersvn_fs_fs__txn_prop(svn_string_t **value_p,
3820289177Speter                    svn_fs_txn_t *txn,
3821289177Speter                    const char *propname,
3822289177Speter                    apr_pool_t *pool)
3823289177Speter{
3824289177Speter  apr_hash_t *table;
3825289177Speter  svn_fs_t *fs = txn->fs;
3826289177Speter
3827289177Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
3828289177Speter  SVN_ERR(svn_fs_fs__txn_proplist(&table, txn, pool));
3829289177Speter
3830289177Speter  *value_p = svn_hash_gets(table, propname);
3831289177Speter
3832289177Speter  return SVN_NO_ERROR;
3833289177Speter}
3834289177Speter
3835289177Spetersvn_error_t *
3836289177Spetersvn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
3837289177Speter                     svn_fs_t *fs,
3838289177Speter                     svn_revnum_t rev,
3839289177Speter                     apr_uint32_t flags,
3840289177Speter                     apr_pool_t *pool)
3841289177Speter{
3842289177Speter  svn_string_t date;
3843289177Speter  fs_txn_data_t *ftd;
3844289177Speter  apr_hash_t *props = apr_hash_make(pool);
3845289177Speter
3846289177Speter  SVN_ERR(svn_fs__check_fs(fs, TRUE));
3847289177Speter
3848289177Speter  SVN_ERR(svn_fs_fs__create_txn(txn_p, fs, rev, pool));
3849289177Speter
3850289177Speter  /* Put a datestamp on the newly created txn, so we always know
3851289177Speter     exactly how old it is.  (This will help sysadmins identify
3852289177Speter     long-abandoned txns that may need to be manually removed.)  When
3853289177Speter     a txn is promoted to a revision, this property will be
3854289177Speter     automatically overwritten with a revision datestamp. */
3855289177Speter  date.data = svn_time_to_cstring(apr_time_now(), pool);
3856289177Speter  date.len = strlen(date.data);
3857289177Speter
3858289177Speter  svn_hash_sets(props, SVN_PROP_REVISION_DATE, &date);
3859289177Speter
3860289177Speter  /* Set temporary txn props that represent the requested 'flags'
3861289177Speter     behaviors. */
3862289177Speter  if (flags & SVN_FS_TXN_CHECK_OOD)
3863289177Speter    svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_OOD,
3864289177Speter                  svn_string_create("true", pool));
3865289177Speter
3866289177Speter  if (flags & SVN_FS_TXN_CHECK_LOCKS)
3867289177Speter    svn_hash_sets(props, SVN_FS__PROP_TXN_CHECK_LOCKS,
3868289177Speter                  svn_string_create("true", pool));
3869289177Speter
3870289177Speter  if (flags & SVN_FS_TXN_CLIENT_DATE)
3871289177Speter    svn_hash_sets(props, SVN_FS__PROP_TXN_CLIENT_DATE,
3872289177Speter                  svn_string_create("0", pool));
3873289177Speter
3874289177Speter  ftd = (*txn_p)->fsap_data;
3875289177Speter  return svn_error_trace(set_txn_proplist(fs, &ftd->txn_id, props, FALSE,
3876289177Speter                                          pool));
3877289177Speter}
3878