~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/libsvn_fs_fs/transaction.c

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

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