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

« back to all changes in this revision

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